From 8c9c4810b4098cf810b16184bc0115405a1a30b3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 18 Jan 2017 10:03:05 -0500 Subject: [PATCH 01/20] Interim checkin of new Case infrastructure for locking/services --- .../sleuthkit/autopsy/actions/ExitAction.java | 18 +- .../autopsy/casemodule/Bundle.properties | 2 +- .../autopsy/casemodule/Bundle_ja.properties | 2 +- .../sleuthkit/autopsy/casemodule/Case.java | 1513 ++++++++++------- .../autopsy/casemodule/CaseActionHelper.java | 78 + .../autopsy/casemodule/CaseCloseAction.java | 81 +- .../autopsy/casemodule/CaseMetadata.java | 47 +- .../autopsy/casemodule/CaseNewAction.java | 13 +- .../autopsy/casemodule/CaseOpenAction.java | 116 +- .../casemodule/LoggingProgressIndicator.java | 48 +- .../ModalDialogProgressIndicator.java | 150 ++ .../casemodule/NewCaseWizardAction.java | 53 +- .../casemodule/NewCaseWizardPanel1.java | 1 - .../casemodule/OpenRecentCasePanel.java | 47 +- .../autopsy/casemodule/ProgressPanel.form | 52 + .../autopsy/casemodule/ProgressPanel.java | 97 ++ .../autopsy/casemodule/RecentItems.java | 105 +- .../casemodule/SingleUserCaseConverter.java | 1 + .../casemodule/TestAutopsyService.java | 73 + .../CoordinationService.java | 12 + .../ProgressIndicator.java | 33 +- .../autopsy/corecomponents/Installer.java | 47 +- .../hashdatabase/SilentProgressIndicator.java | 60 + .../autoingest/AutoIngestCaseManager.java | 125 ++ ...asePanel.form => AutoIngestCasePanel.form} | 18 +- ...asePanel.java => AutoIngestCasePanel.java} | 100 +- .../autoingest/AutoIngestManager.java | 8 +- .../experimental/autoingest/Bundle.properties | 18 +- .../autoingest/ReviewModeCaseManager.java | 163 -- .../configuration/StartupWindow.java | 6 +- 30 files changed, 1838 insertions(+), 1249 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/ModalDialogProgressIndicator.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/TestAutopsyService.java create mode 100644 Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{ReviewModeCasePanel.form => AutoIngestCasePanel.form} (96%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{ReviewModeCasePanel.java => AutoIngestCasePanel.java} (88%) delete mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java index 15cacf490d..5db65c0ca6 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-201y Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,26 +26,26 @@ import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionRegistration; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.coreutils.Logger; +/** + * RJCTODO + */ @ActionRegistration(displayName = "Exit", iconInMenu = true) @ActionReference(path = "Menu/Case", position = 1000, separatorBefore = 999) @ActionID(id = "org.sleuthkit.autopsy.casemodule.ExitAction", category = "Case") - final public class ExitAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { try { - Case currentCase = Case.getCurrentCase(); - if (currentCase != null) { - currentCase.closeCase(); - } - } catch (Exception ex) { - Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Had a problem closing the case.", ex); //NON-NLS + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + // RJCTODO: Pop up here + Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case", ex); //NON-NLS } finally { LifecycleManager.getDefault().exit(); } } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 12184925e9..a3fbca6426 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -133,7 +133,6 @@ Case.GetCaseTypeGivenPath.Failure=Unable to get case type Case.metaDataFileCorrupt.exception.msg=The case metadata file (.aut) is corrupted. Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk. Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1} -Case.exception.errorLocking=Unable to open case being updated by another user. CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\ Case Name\: {0}\n\ Case Directory\: {1} @@ -241,3 +240,4 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close +ProgressPanel.progressMessage.text=Message diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index b033efa9ca..54716ba8cb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -1,4 +1,4 @@ -CTL_AddImage=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0... +Case.closeCase.exception.msgCTL_AddImage=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0... CTL_AddImageButton=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0 CTL_CaseCloseAct=\u30b1\u30fc\u30b9\u3092\u9589\u3058\u308b CTL_CaseNewAction=\u65b0\u898f\u30b1\u30fc\u30b9... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 6410a269ce..bbc2da5dee 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,9 +20,12 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Cursor; import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; +import java.io.IOException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; @@ -33,21 +36,20 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.MissingResourceException; import java.util.Set; import java.util.TimeZone; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -64,12 +66,16 @@ import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService.CaseContext; import org.sleuthkit.autopsy.corecomponentinterfaces.CoreComponentControl; +import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.DriveUtils; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; @@ -83,6 +89,7 @@ import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.modules.hashdatabase.SilentProgressIndicator; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; @@ -91,17 +98,40 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskException; /** * An Autopsy case. Currently, only one case at a time may be open. */ public class Case implements SleuthkitCase.ErrorObserver { + private static final int NAME_LOCK_TIMOUT_HOURS = 12; + private static final int DIR_LOCK_TIMOUT_HOURS = 12; + private static final int RESOURCE_LOCK_TIMOUT_HOURS = 12; + private static final int MAX_SANITIZED_CASE_NAME_LEN = 47; + private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS + private static final String CACHE_FOLDER = "Cache"; //NON-NLS + private static final String EXPORT_FOLDER = "Export"; //NON-NLS + private static final String LOG_FOLDER = "Log"; //NON-NLS + private static final String REPORTS_FOLDER = "Reports"; //NON-NLS + private static final String TEMP_FOLDER = "Temp"; //NON-NLS + private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; + static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS // RJCTODO + private static final Logger LOGGER = Logger.getLogger(Case.class.getName()); + private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); + private static String appName; + private static Case currentCase; + private static CoordinationService.Lock currentCaseDirLock; + private static ExecutorService singleThreadedExecutor; + private final CaseMetadata caseMetadata; + private final SleuthkitCase db; + private final Services services; + private CollaborationMonitor collaborationMonitor; + private boolean hasDataSources; + private volatile IntervalErrorReportData tskErrorReporter; + /** * An enumeration of case types. */ - @NbBundle.Messages({"Case_caseType_singleUser=Single-user case", "Case_caseType_multiUser=Multi-user case"}) public enum CaseType { SINGLE_USER_CASE("Single-user case"), //NON-NLS @@ -110,12 +140,21 @@ public class Case implements SleuthkitCase.ErrorObserver { private final String typeName; /** - * Constructs a case type. + * Gets a case type from a case type name string. * - * @param typeName The type name. + * @param typeName The case type name string. + * + * @return */ - private CaseType(String typeName) { - this.typeName = typeName; + public static CaseType fromString(String typeName) { + if (typeName != null) { + for (CaseType c : CaseType.values()) { + if (typeName.equalsIgnoreCase(c.toString())) { + return c; + } + } + } + return null; } /** @@ -133,6 +172,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * * @return The dis[play name. */ + @Messages({"Case_caseType_singleUser=Single-user case", "Case_caseType_multiUser=Multi-user case"}) String getLocalizedDisplayName() { if (fromString(typeName) == SINGLE_USER_CASE) { return Bundle.Case_caseType_singleUser(); @@ -142,21 +182,12 @@ public class Case implements SleuthkitCase.ErrorObserver { } /** - * Gets a case type from a case type name string + * Constructs a case type. * - * @param typeName The case type name string. - * - * @return + * @param typeName The type name. */ - public static CaseType fromString(String typeName) { - if (typeName != null) { - for (CaseType c : CaseType.values()) { - if (typeName.equalsIgnoreCase(c.typeName)) { - return c; - } - } - } - return null; + private CaseType(String typeName) { + this.typeName = typeName; } /** @@ -177,7 +208,7 @@ public class Case implements SleuthkitCase.ErrorObserver { }; /** - * An enumeration of events (property change events) a case may publish + * An enumeration of the events (property change events) a case may publish * (fire). */ public enum Events { @@ -278,38 +309,6 @@ public class Case implements SleuthkitCase.ErrorObserver { CONTENT_TAG_DELETED; }; - private static final int MAX_SANITIZED_CASE_NAME_LEN = 47; - private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS - private static final String CACHE_FOLDER = "Cache"; //NON-NLS - private static final String EXPORT_FOLDER = "Export"; //NON-NLS - private static final String LOG_FOLDER = "Log"; //NON-NLS - private static final String REPORTS_FOLDER = "Reports"; //NON-NLS - private static final String TEMP_FOLDER = "Temp"; //NON-NLS - static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS - private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; - private static final Logger logger = Logger.getLogger(Case.class.getName()); - private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); - private static String appName; - private static Case currentCase; - private static CoordinationService.Lock currentCaseLock; - private static ExecutorService currentCaseExecutor; - private final CaseMetadata caseMetadata; - private final SleuthkitCase db; - private final Services services; - private CollaborationMonitor collaborationMonitor; - private boolean hasDataSources; - private volatile IntervalErrorReportData tskErrorReporter; - - /** - * Constructs an Autopsy case. Currently, only one case at a time may be - * open. - */ - private Case(CaseMetadata caseMetadata, SleuthkitCase db) { - this.caseMetadata = caseMetadata; - this.db = db; - this.services = new Services(db); - } - /** * Adds a subscriber to all case events. To subscribe to only specific * events, use one of the overloads of addEventSubscriber. @@ -374,11 +373,229 @@ public class Case implements SleuthkitCase.ErrorObserver { eventPublisher.removeSubscriber(eventNames, subscriber); } + /** + * Gets the application name. + * + * @return The application name. + */ + // RJCTODO: Comment on funky way this works, deprecate and make it return Autopsy, make a private method that does funkiness + public static String getAppName() { + if ((appName == null) || appName.isEmpty()) { + appName = WindowManager.getDefault().getMainWindow().getTitle(); + } + return appName; + } + + /** + * Checks if a case name is valid, i.e., does not include any characters + * that cannot be used in file names. + * + * @param caseName The case name. + * + * @return True or false. + */ + public static boolean isValidName(String caseName) { + /* + * TODO(JIRA-2221): This should incorporate the validity checks of + * sanitizeCaseName. RJCTODO: This is no longer necessary, kill off this + * story + */ + return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":") + || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"") + || caseName.contains("<") || caseName.contains(">") || caseName.contains("|")); + } + + /** + * Creates a new Autopsy case and makes it the current case. + * + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. + * @param caseDisplayName The display name of case, which may be changed + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. + * @param caseType The type of case (single-user or multi-user). + * + * @throws CaseActionException if there is a problem creating the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + */ + @Messages({ + "Case.creationException.illegalCaseName=Could not create case: case name contains illegal characters.", + "# {0} - exception message", "Case.creationException.couldNotCreateCase=Could not create case: {0}" + }) + public static void createCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + + /* + * Clean up the display name for the case to make a suitable case name. + */ + String caseName; + try { + caseName = sanitizeCaseName(caseDisplayName); + } catch (IllegalCaseNameException ex) { + throw new CaseActionException(Bundle.Case_creationException_iilegalCaseName(), ex); + } + LOGGER.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS + + /* + * Set up either a visual progress indicator or a logging progress + * indicator, depending on whether a GUI is present. + */ + CancelButtonListener listener = new CancelButtonListener(); + ProgressIndicator progressIndicator; + if (RuntimeProperties.coreComponentsAreActive()) { + progressIndicator = new ModalDialogProgressIndicator("Creating Case", new String[]{"Cancel"}, "Cancel", null, listener); // RJCTODO: bundle message + } else { + progressIndicator = new LoggingProgressIndicator(); + } + progressIndicator.start("Acquiring locks"); // RJCTODO: Bundle message + + /* + * Creating a case is always done in the same non-UI thread that will be + * used later to close the case. If the case is a multi-user case, this + * ensures that case directory lock is released in the same thread in + * which it was acquired, as is required by the coordination service. + */ + try { + Future future = getSingleThreadedExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == caseType) { + createCase(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType); + } else { + /* + * First, acquire an exclusive case name lock to prevent two + * nodes from creating the same case at the same time. Next, + * acquire a shared case directory lock that will be held as + * long as this node has this case open, in order to prevent + * deletion of the case by another node. Finally, acquire an + * exclusive case resources lock to allow only one node at a + * time to create/open/upgrade case resources. + */ + progressIndicator.start("Acquiring locks"); // RJCTODO: Bundle message + try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) { + assert (null != nameLock); + acquireSharedCaseDirLock(caseDir); + try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseName)) { + assert (null != resourcesLock); + createCase(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType); + } + } + } + return null; + }); + if (RuntimeProperties.coreComponentsAreActive()) { + listener.setCaseActionFuture(future); + ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); + } + future.get(); + } catch (InterruptedException | ExecutionException ex) { + if (CaseType.SINGLE_USER_CASE == caseType) { + releaseSharedCaseDirLock(caseName); + } + if (ex instanceof InterruptedException) { + throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase("Interrupted during locks acquisition"), ex); //RJCTODO + } else { + /* + * The methods called within the task MUST throw a + * CaseActionException with a user-friendly error message + * suitable for substitution in the error message below. + * + * RJCTODO: Add TODO comment referencing JIRA + */ + throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(ex.getCause().getMessage()), ex); + } + } finally { + progressIndicator.finish(""); + if (RuntimeProperties.coreComponentsAreActive()) { + ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); + } + } + } + + /** + * Opens an existing Autopsy case and makes it the current case. + * + * @param caseMetadataFilePath The path of the case metadata (.aut) file. + * + * @throws CaseActionException if there is a problem opening the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + */ + @Messages({ + "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}" + }) + public static void openCurrentCase(String caseMetadataFilePath) throws CaseActionException { + LOGGER.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS + try { + if (!caseMetadataFilePath.endsWith(CaseMetadata.getFileExtension())) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CaseMetadata.getFileExtension())); + } + CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); + + /* + * Creating a case is always done in the same non-UI thread that + * will be used later to close the case. If the case is a multi-user + * case, this ensures that case directory lock is released in the + * same thread in which it was acquired, as is required by the + * coordination service. + */ + CaseType caseType = metadata.getCaseType(); + String caseName = metadata.getCaseName(); + try { + Future future = getSingleThreadedExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == caseType) { + openCase(metadata); + } else { + /* + * First, acquire a shared case directory lock that will + * be held as long as this node has this case open, in + * order to prevent deletion of the case by another + * node. Next, acquire an exclusive case resources lock + * to allow only one node at a time to + * create/open/upgrade case resources. + */ + acquireSharedCaseDirLock(metadata.getCaseDirectory()); + try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseName())) { + assert (null != resourcesLock); + openCase(metadata); + } + } + return null; + }); + future.get(); + } catch (InterruptedException | ExecutionException ex) { + if (CaseType.SINGLE_USER_CASE == caseType) { + releaseSharedCaseDirLock(caseName); + } + if (ex instanceof ExecutionException) { + /* + * The methods called within the task MUST throw a + * CaseActionException with a user-friendly error message + * suitable for substitution in the error message below. + * + * RJCTODO: Add TODO comment referencing JIRA + */ + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); + } else { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); + } + } + + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Failed to access case metadata"), ex); + } + } + /** * Checks if case is currently open. * * @return True or false. */ + // RJCTODO: Deprecate this, it cannot work public static boolean isCaseOpen() { return currentCase != null; } @@ -398,6 +615,64 @@ public class Case implements SleuthkitCase.ErrorObserver { } } + /** + * Closes the current case if there is a current case. + * + * @throws CaseActionException + */ + @Messages({ + "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}" + }) + public static void closeCurrentCase() throws CaseActionException { + if (null != currentCase) { + try { + /* + * Closing a case is always done in the same non-UI thread that + * opened/created the case. If the case is a multi-user case, + * this ensures that case directory lock is released in the same + * thread in which it was acquired, as is required by the + * coordination service. + */ + Future future = getSingleThreadedExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == currentCase.getCaseType()) { + closeTheCase(); + } else { + String caseName = currentCase.getCaseMetadata().getCaseName(); + /* + * Only one node at a time is allowed to close a case, + * so acquire an exclusive case resources lock. + */ + try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseName)) { + assert (null != resourcesLock); + closeTheCase(); + } finally { + /* + * Always release the case directory lock that was + * acquired when the case was opened. + */ + releaseSharedCaseDirLock(caseName); + } + } + return null; + }); + future.get(); + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof ExecutionException) { + /* + * The methods called within the task MUST throw a + * CaseActionException with a user-friendly error message + * suitable for substitution in the error message below. + * + * RJCTODO: Add TODO comment referencing JIRA + */ + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); + } else { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); + } + } + } + } + /** * Gets the case database. * @@ -416,15 +691,6 @@ public class Case implements SleuthkitCase.ErrorObserver { return services; } - /** - * Gets the case metadata. - * - * @return A CaseMetaData object. - */ - CaseMetadata getCaseMetadata() { - return caseMetadata; - } - /** * Gets the case type. * @@ -452,33 +718,6 @@ public class Case implements SleuthkitCase.ErrorObserver { return getCaseMetadata().getCaseName(); } - /** - * Updates the case name. - * - * This should not be called from the EDT. - * - * @param oldCaseName The old case name. - * @param oldPath The old path name. - * @param newCaseName The new case name. - * @param newPath The new case path. - */ - void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException { - try { - caseMetadata.setCaseName(newCaseName); - eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName)); - SwingUtilities.invokeLater(() -> { - try { - RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case - addCaseNameToMainWindowTitle(newCaseName); - } catch (Exception ex) { - Logger.getLogger(Case.class.getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS - } - }); - } catch (Exception ex) { - throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex); - } - } - /** * Gets the case number. * @@ -604,20 +843,6 @@ public class Case implements SleuthkitCase.ErrorObserver { } } - /** - * Gets the path to the specified subdirectory of the case directory, - * creating it if it does not already exist. - * - * @return The absolute path to the specified subdirectory. - */ - private String getOrCreateSubdirectory(String subDirectoryName) { - File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile(); - if (!subDirectory.exists()) { - subDirectory.mkdirs(); - } - return subDirectory.toString(); - } - /** * Gets the data sources for the case. * @@ -649,7 +874,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } } } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS } return timezones; } @@ -674,7 +899,7 @@ public class Case implements SleuthkitCase.ErrorObserver { try { hasDataSources = (getDataSources().size() > 0); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS } } return hasDataSources; @@ -837,40 +1062,38 @@ public class Case implements SleuthkitCase.ErrorObserver { } /** - * Closes this Autopsy case. + * Gets the case metadata. * - * @throws CaseActionException if there is a problem closing the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @return A CaseMetaData object. */ - public void closeCase() throws CaseActionException { - - // The unlock must happen on the same thread that created the lock - Future future = getCurrentCaseExecutor().submit(() -> { - try{ - if (currentCaseLock != null) { - currentCaseLock.release(); - currentCaseLock = null; - } - } catch (CoordinationService.CoordinationServiceException exx) { - logger.log(Level.SEVERE, String.format("Error releasing shared lock"), exx); - } - return null; - }); - try{ - future.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); - } - - changeCurrentCase(null); - + CaseMetadata getCaseMetadata() { + return caseMetadata; + } + + /** + * Updates the case name. + * + * This should not be called from the EDT. + * + * @param oldCaseName The old case name. + * @param oldPath The old path name. + * @param newCaseName The new case name. + * @param newPath The new case path. + */ + void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException { try { - services.close(); - this.db.close(); - } catch (Exception e) { - throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.closeCase.exception.msg"), e); + caseMetadata.setCaseName(newCaseName); + eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName)); + SwingUtilities.invokeLater(() -> { + try { + RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case + addCaseNameToMainWindowTitle(newCaseName); + } catch (Exception ex) { + Logger.getLogger(Case.class.getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS + } + }); + } catch (CaseMetadataException ex) { + throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex); } } @@ -883,7 +1106,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * @throws CaseActionException exception throw if case could not be deleted */ void deleteCase(File caseDir) throws CaseActionException { - logger.log(Level.INFO, "Deleting case.\ncaseDir: {0}", caseDir); //NON-NLS + LOGGER.log(Level.INFO, "Deleting case.\ncaseDir: {0}", caseDir); //NON-NLS try { boolean result = deleteCaseDirectory(caseDir); @@ -895,218 +1118,13 @@ public class Case implements SleuthkitCase.ErrorObserver { NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg", caseDir)); } - } catch (Exception ex) { - logger.log(Level.SEVERE, "Error deleting the current case dir: " + caseDir, ex); //NON-NLS + } catch (MissingResourceException | CaseActionException ex) { + LOGGER.log(Level.SEVERE, "Error deleting the current case dir: " + caseDir, ex); //NON-NLS throw new CaseActionException( NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg2", caseDir), ex); } } - /** - * Gets the application name. - * - * @return The application name. - */ - public static String getAppName() { - if ((appName == null) || appName.equals("")) { - appName = WindowManager.getDefault().getMainWindow().getTitle(); - } - return appName; - } - - /** - * Checks if a string is a valid case name. - * - * TODO( AUT-2221): This should incorporate the vlaidity checks of - * sanitizeCaseName. - * - * @param caseName The candidate string. - * - * @return True or false. - */ - public static boolean isValidName(String caseName) { - return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":") - || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"") - || caseName.contains("<") || caseName.contains(">") || caseName.contains("|")); - } - - /** - * Creates a new single-user Autopsy case. - * - * @param caseDir The full path of the case directory. It will be created - * if it doesn't already exist; if it exists, it should - * have been created using Case.createCaseDirectory to - * ensure that the required sub-directories were created. - * @param caseName The name of case. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the - * empty string. - * - * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. - */ - public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException { - create(caseDir, caseName, caseNumber, examiner, CaseType.SINGLE_USER_CASE); - } - - /** - * Creates a new Autopsy case. - * - * @param caseDir The full path of the case directory. It will be created - * if it doesn't already exist; if it exists, it should - * have been created using Case.createCaseDirectory() to - * ensure that the required sub-directories were created. - * @param caseName The name of case. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the - * empty string. - * @param caseType The type of case (single-user or multi-user). - * - * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. - */ - @Messages({"Case.creationException=Could not create case: failed to create case metadata file."}) - public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { - logger.log(Level.INFO, "Attempting to create case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS - - CoordinationService.Lock exclusiveResourceLock = null; - boolean caseCreatedSuccessfully = false; - - /* - * Create case directory if it doesn't already exist. - */ - if (new File(caseDir).exists() == false) { - Case.createCaseDirectory(caseDir, caseType); - } - - /* - * Sanitize the case name, create a unique keyword search index name, - * and create a standard (single-user) or unique (multi-user) case - * database name. - */ - String santizedCaseName = sanitizeCaseName(caseName); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); - Date date = new Date(); - String indexName = santizedCaseName + "_" + dateFormat.format(date); - String dbName = null; - if (caseType == CaseType.SINGLE_USER_CASE) { - dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS - } else if (caseType == CaseType.MULTI_USER_CASE) { - dbName = indexName; - } - - try{ - /* If this is a multi-user case, acquire two locks: - * - a shared lock on the case to prevent it from being deleted while open - * - an exclusive lock to prevent multiple clients from executing openCase simultaneously - */ - if(caseType == CaseType.MULTI_USER_CASE){ - try{ - - // The shared lock uses case directory - // The shared lock needs to be created on a special thread so it can be released - // from the same thread. - Future future = getCurrentCaseExecutor().submit(() -> { - currentCaseLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir); - if (null == currentCaseLock) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); - } - return null; - }); - future.get(); - - // The exclusive lock uses the unique case name. - // This lock does not need to be on a special thread since it will be released before - // leaving this method - exclusiveResourceLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE, - dbName, 12, TimeUnit.HOURS); - if (null == exclusiveResourceLock) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); - } - } catch (Exception ex){ - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); - } - } - - /* - * Create the case metadata (.aut) file. - */ - CaseMetadata metadata; - try { - metadata = new CaseMetadata(caseDir, caseType, caseName, caseNumber, examiner, dbName, indexName); - } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_creationException(), ex); - } - - /* - * Create the case database. - */ - SleuthkitCase db = null; - try { - if (caseType == CaseType.SINGLE_USER_CASE) { - db = SleuthkitCase.newCase(dbName); - } else if (caseType == CaseType.MULTI_USER_CASE) { - db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - /* - * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly - * messages, so propagate the exception message. - */ - throw new CaseActionException(ex.getMessage(), ex); //NON-NLS - } catch (UserPreferencesException ex) { - logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); - } - - Case newCase = new Case(metadata, db); - changeCurrentCase(newCase); - caseCreatedSuccessfully = true; - - logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS - } finally { - // Release the exclusive resource lock - try { - if (exclusiveResourceLock != null) { - exclusiveResourceLock.release(); - } - } catch (CoordinationService.CoordinationServiceException exx) { - logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseName), exx); - } - - // If an error occurred while opening the case, release the shared case lock as well - if(! caseCreatedSuccessfully){ - Future future = getCurrentCaseExecutor().submit(() -> { - try { - if (currentCaseLock != null) { - currentCaseLock.release(); - currentCaseLock = null; - } - } catch (CoordinationService.CoordinationServiceException exx) { - logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseName), exx); - } - return null; - }); - try{ - future.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); - } - } - } - } - /** * Sanitizes the case name for PostgreSQL database, Solr cores, and ActiveMQ * topics. Makes it plain-vanilla enough that each item should be able to @@ -1131,8 +1149,11 @@ public class Case implements SleuthkitCase.ErrorObserver { * @param caseName A candidate case name. * * @return The sanitized case name. + * + * @throws org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException */ - static String sanitizeCaseName(String caseName) { + // RJCTODO: Get Eugene to Update and also reference story + static String sanitizeCaseName(String caseName) throws IllegalCaseNameException { String result; @@ -1159,7 +1180,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } if (result.isEmpty()) { - result = "case"; //NON-NLS + throw new IllegalCaseNameException(String.format("Failed to sanitize case name '%s'", caseName)); } return result; @@ -1176,10 +1197,12 @@ public class Case implements SleuthkitCase.ErrorObserver { static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException { File caseDirF = new File(caseDir); + if (caseDirF.exists()) { if (caseDirF.isFile()) { throw new CaseActionException( NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir)); + } else if (!caseDirF.canRead() || !caseDirF.canWrite()) { throw new CaseActionException( NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir)); @@ -1188,6 +1211,7 @@ public class Case implements SleuthkitCase.ErrorObserver { try { boolean result = (caseDirF).mkdirs(); // create root case Directory + if (result == false) { throw new CaseActionException( NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir)); @@ -1211,6 +1235,7 @@ public class Case implements SleuthkitCase.ErrorObserver { final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER; result = new File(modulesOutDir).mkdir(); + if (result == false) { throw new CaseActionException( NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", @@ -1219,187 +1244,30 @@ public class Case implements SleuthkitCase.ErrorObserver { final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER; result = new File(reportsOutDir).mkdir(); + if (result == false) { throw new CaseActionException( NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", modulesOutDir)); + } - } catch (Exception e) { + } catch (MissingResourceException | CaseActionException e) { throw new CaseActionException( NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e); } } /** - * Opens an existing Autopsy case. + * Deletes a case directory. * - * @param caseMetadataFilePath The path of the case metadata (.aut) file. + * @param casePath A case directory path. * - * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @return True if the deletion succeeded, false otherwise. */ - public static void open(String caseMetadataFilePath) throws CaseActionException { - logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS - CoordinationService.Lock exclusiveResourceLock = null; - boolean caseOpenedSuccessfully = false; - - /* - * Verify the extension of the case metadata file. - */ - if (!caseMetadataFilePath.endsWith(CaseMetadata.getFileExtension())) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CaseMetadata.getFileExtension())); - } - - try { - /* - * Get the case metadata required to open the case database. - */ - CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); - CaseType caseType = metadata.getCaseType(); - - /* - * If this is a multi-user case, acquire two locks: - * - a shared lock on the case to prevent it from being deleted while open - * - an exclusive lock to prevent multiple clients from executing openCase simultaneously - */ - if(caseType == CaseType.MULTI_USER_CASE){ - try{ - - // The shared lock uses the case directory - // The shared lock needs to be created on a special thread so it can be released - // from the same thread. - Future future = getCurrentCaseExecutor().submit(() -> { - currentCaseLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); - if (null == currentCaseLock) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); - } - return null; - }); - future.get(); - - // The exclusive lock uses the unique case name - // This lock does not need to be on a special thread since it will be released before - // leaving this method - exclusiveResourceLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE, - metadata.getCaseDatabaseName(), 12, TimeUnit.HOURS); - if (null == exclusiveResourceLock) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); - } - } catch (Exception ex){ - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); - } - } - - /* - * Open the case database. - */ - SleuthkitCase db; - if (caseType == CaseType.SINGLE_USER_CASE) { - String dbPath = metadata.getCaseDatabasePath(); //NON-NLS - db = SleuthkitCase.openCase(dbPath); - } else { - if (!UserPreferences.getIsMultiUserModeEnabled()) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled")); - } - try { - db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); - } catch (UserPreferencesException ex) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); - } - } - - /* - * Check for the presence of the UI and do things that can only be - * done with user interaction. - */ - if (RuntimeProperties.coreComponentsAreActive()) { - /* - * If the case database was upgraded for a new schema, notify - * the user. - */ - if (null != db.getBackupDatabasePath()) { - SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", db.getBackupDatabasePath()), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), - JOptionPane.INFORMATION_MESSAGE); - }); - } - - /* - * Look for the files for the data sources listed in the case - * database and give the user the opportunity to locate any that - * are missing. - */ - Map imgPaths = getImagePaths(db); - for (Map.Entry entry : imgPaths.entrySet()) { - long obj_id = entry.getKey(); - String path = entry.getValue(); - boolean fileExists = (new File(path).isFile() || driveExists(path)); - if (!fileExists) { - int ret = JOptionPane.showConfirmDialog( - WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", getAppName(), path), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), - JOptionPane.YES_NO_OPTION); - if (ret == JOptionPane.YES_OPTION) { - MissingImageDialog.makeDialog(obj_id, db); - } else { - logger.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS - } - } - } - } - Case openedCase = new Case(metadata, db); - changeCurrentCase(openedCase); - caseOpenedSuccessfully = true; - - } catch (CaseMetadataException ex) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.metaDataFileCorrupt.exception.msg"), ex); //NON-NLS - } catch (TskCoreException ex) { - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - /* - * SleuthkitCase.openCase throws TskCoreExceptions with - * user-friendly messages, so propagate the exception message. - */ - throw new CaseActionException(ex.getMessage(), ex); - } finally { - // Release the exclusive resource lock - try { - if (exclusiveResourceLock != null) { - exclusiveResourceLock.release(); - exclusiveResourceLock = null; - } - } catch (CoordinationService.CoordinationServiceException exx) { - logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseMetadataFilePath), exx); - } - - // If an error occurred while opening the case, release the shared case lock as well - if(! caseOpenedSuccessfully){ - Future future = getCurrentCaseExecutor().submit(() -> { - try { - if (currentCaseLock != null) { - currentCaseLock.release(); - currentCaseLock = null; - } - } catch (CoordinationService.CoordinationServiceException exx) { - logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseMetadataFilePath), exx); - } - return null; - }); - try{ - future.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); - } - } - } + static boolean deleteCaseDirectory(File casePath) { + LOGGER.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS + return FileUtil.deleteDir(casePath); } /** @@ -1418,12 +1286,316 @@ public class Case implements SleuthkitCase.ErrorObserver { imgPaths.put(entry.getKey(), entry.getValue().get(0)); } } - } catch (TskException ex) { - logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS } return imgPaths; } + /** + * Creates a new Autopsy case. + * + * @param caseDir The full path of the case directory. + * @param caseName The name of case. + * @param caseDisplayName The name of case. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. + * @param caseType The type of case (single-user or multi-user). + * + * @throws CaseActionException if there is a problem creating the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + */ + @Messages({"Case.creationException.couldNotCreateMetadataFile=Could not create case: failed to create case metadata file."}) + private static void createCase(String caseDir, String caseName, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + /* + * Create the case directory, if it does not already exist. + * + * CAUTION: The reason for the check for the existence of the case + * directory is not at all obvious. It reflects the assumption that if + * the case directory already exists, it is because the case is being + * created using the the "New Case" wizard, which separates the creation + * of the case directory from the creation of the case, with the idea + * that if the case directory cannot be created, the user can be asked + * to supply a different case directory path. This of course creates + * subtle and undetectable coupling between this code and the wizard + * code. The desired effect could be accomplished more simply and safely + * by having this method throw a specific exception to indicate that the + * case directory could not be created. A FEW specific exception types + * would in turn allow us to put localized, user-friendly messages in + * the GUI instead of putting user-friendly, localized messages in the + * exceptions, which cause them to appear in the logs, where it would be + * better to have English for readability by the broadest group of + * developers. + * + * TODO (JIRA-2180): Fix the problem described above. + */ + if (new File(caseDir).exists() == false) { + Case.createCaseDirectory(caseDir, caseType); + } + + /* + * Create a unique keyword search index name, and create a standard + * (single-user) or unique (multi-user) case database name. + * + * TODO (JIRA-2207): The Case class should not be responsible for + * creating and storing (in the case metadata) unique text index names + * for Autopsy services. The SolrSearchService, for example, should + * handle this internally and deletion of Solr cores when a case is + * deleted should be refactored out of the AutoIngestManager. + */ + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); + Date date = new Date(); + String indexName = caseName + "_" + dateFormat.format(date); + String dbName = null; + if (caseType == CaseType.SINGLE_USER_CASE) { + dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS + } else if (caseType == CaseType.MULTI_USER_CASE) { + dbName = indexName; + } + + /* + * Create the case metadata (.aut) file. + * + * TODO (JIRA-2207): See above. + */ + CaseMetadata metadata; + try { + metadata = new CaseMetadata(caseDir, caseType, caseName, caseDisplayName, caseNumber, examiner, dbName, indexName); + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotCreateMetadataFile(), ex); + } + + /* + * Create the case database. + */ + SleuthkitCase db = null; + try { + if (caseType == CaseType.SINGLE_USER_CASE) { + db = SleuthkitCase.newCase(dbName); + } else if (caseType == CaseType.MULTI_USER_CASE) { + db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); + } + } catch (TskCoreException ex) { + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + /* + * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly + * messages, so propagate the exception message. + */ + throw new CaseActionException(String.format("Error creating the case database %s for %s ", dbName, caseName), ex); //NON-NLS RJCTODO + + } catch (UserPreferencesException ex) { + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); // RJCTODO + } + + /* + * Create the case and make it the current case. + */ + Case newCase = new Case(metadata, db); + changeCurrentCase(newCase); + + LOGGER.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + } + + /** + * Opens an existing Autopsy case. + * + * @param caseMetadataFilePath The path of the case metadata (.aut) file. + * + * @throws CaseActionException if there is a problem opening the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + */ + private static void openCase(CaseMetadata metadata) throws CaseActionException { + /* + * Open the case database. + */ + SleuthkitCase db = null; + try { + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { + db = SleuthkitCase.openCase(metadata.getCaseDatabasePath()); + } else if (UserPreferences.getIsMultiUserModeEnabled()) { + try { + db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); + + } catch (UserPreferencesException ex) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); // RJCTODO: What does this say? + + } + } else { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled")); + + } + } catch (TskCoreException ex) { + // RJCTODO: Need proper exception message + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + } + + /* + * Check for the presence of the UI and do things that can only be done + * with user interaction. + */ + if (RuntimeProperties.coreComponentsAreActive()) { + /* + * If the case database was upgraded for a new schema, notify the + * user. + */ + final String backupDbPath = db.getBackupDatabasePath(); + + if (null != backupDbPath) { + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), + JOptionPane.INFORMATION_MESSAGE); + }); + } + + /* + * Look for the files for the data sources listed in the case + * database and give the user the opportunity to locate any that are + * missing. + */ + Map imgPaths = getImagePaths(db); + for (Map.Entry entry : imgPaths.entrySet()) { + long obj_id = entry.getKey(); + String path = entry.getValue(); + boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); + + if (!fileExists) { + int ret = JOptionPane.showConfirmDialog( + WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", getAppName(), path), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), + JOptionPane.YES_NO_OPTION); + if (ret == JOptionPane.YES_OPTION) { + MissingImageDialog.makeDialog(obj_id, db); + } else { + LOGGER.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS + } + } + } + } + Case openedCase = new Case(metadata, db); + changeCurrentCase(openedCase); + LOGGER.log(Level.INFO, "Opened case {0} in directory = {1}", new Object[]{metadata.getCaseName(), metadata.getCaseDirectory()}); //NON-NLS RJCTODO + } + + /** + * Closes the current case. + * + * @throws CaseActionException + */ + private static void closeTheCase() throws CaseActionException { + if (null != currentCase) { + Case caseToClose = currentCase; + changeCurrentCase(null); // RJCTODO: Refactor + try { + caseToClose.getServices().close(); + caseToClose.db.close(); + } catch (IOException ex) { + throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(ex.getMessage()), ex); + } + } + } + + /** + * Acquires an exclusive case name lock. + * + * @param caseName The case name (not the case display name, which can be + * changed by a user). + * + * @return The lock. + * + * @throws CaseActionException with a user-friendly message if the lock + * cannot be acquired. + */ + @Messages({"Case.creationException.couldNotAcquireNameLock=Failed to get lock on case name."}) + private static CoordinationService.Lock acquireExclusiveCaseNameLock(String caseName) throws CaseActionException { + try { + Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, NAME_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); + if (null == lock) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock()); + } + return lock; + + } catch (InterruptedException | CoordinationServiceException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); + } + } + + /** + * Acquires a shared case directory lock for the current case. + * + * @param caseDir The full path of the case directory. + * + * @throws CaseActionException with a user-friendly message if the lock + * cannot be acquired. + */ + @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."}) + private static void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { + try { + currentCaseDirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); + if (null == currentCaseDirLock) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); + } + } catch (InterruptedException | CoordinationServiceException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); + } + } + + /** + * Releases a shared case directory lock for the current case. + * + * @param caseDir The full path of the case directory. + */ + private static void releaseSharedCaseDirLock(String caseDir) { + // RJCTODO: Guard currentCaseLock? + if (currentCaseDirLock != null) { + try { + currentCaseDirLock.release(); + currentCaseDirLock = null; + } catch (CoordinationService.CoordinationServiceException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex); + } + } + } + + /** + * Acquires an exclusive case resources lock. + * + * @param caseName The case name (not the case display name, which can be + * changed by a user). + * + * @return The lock. + * + * @throws CaseActionException with a user-friendly message if the lock + * cannot be acquired. + */ + @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources."}) + private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseName) throws CaseActionException { + try { + String resourcesNodeName = caseName + "_resources"; + Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, resourcesNodeName, RESOURCE_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); + if (null == lock) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock()); + } + return lock; + + } catch (InterruptedException | CoordinationServiceException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex); + } + } + /** * Updates the current case to the given case, firing property change events * and updating the UI. @@ -1446,6 +1618,20 @@ public class Case implements SleuthkitCase.ErrorObserver { oldCase.tskErrorReporter = null; } eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), oldCase, null)); + + AutopsyService.CaseContext context = new AutopsyService.CaseContext(oldCase, new LoggingProgressIndicator()); + String serviceName = ""; + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { + try { + serviceName = service.getServiceName(); + if (!serviceName.equals("Solr Keyword Search Service")) { + service.closeCaseResources(context); + } + } catch (AutopsyService.AutopsyServiceException ex) { + Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to close case resources", serviceName), ex); + } + } + if (CaseType.MULTI_USER_CASE == oldCase.getCaseType()) { if (null != oldCase.collaborationMonitor) { oldCase.collaborationMonitor.shutdown(); @@ -1460,6 +1646,7 @@ public class Case implements SleuthkitCase.ErrorObserver { // sanity check if (null != currentCase.tskErrorReporter) { currentCase.tskErrorReporter.shutdown(); + } // start listening for TSK errors for the new case currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECS_BETWEEN_TSK_ERROR_REPORTS, @@ -1479,21 +1666,23 @@ public class Case implements SleuthkitCase.ErrorObserver { eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, newCase.getTextIndexName())); currentCase.collaborationMonitor = new CollaborationMonitor(); } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) { - logger.log(Level.SEVERE, "Failed to setup for collaboration", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Failed to setup for collaboration", ex); //NON-NLS MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")); } } + // RJCTODO: Remove, mention to Eugene. AutopsyService.CaseContext context = new AutopsyService.CaseContext(Case.currentCase, new LoggingProgressIndicator()); String serviceName = ""; - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class + )) { try { serviceName = service.getServiceName(); if (!serviceName.equals("Solr Keyword Search Service")) { service.openCaseResources(context); } } catch (AutopsyService.AutopsyServiceException ex) { - Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", serviceName), ex); + Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to open case resources", serviceName), ex); } } @@ -1515,7 +1704,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * case. */ private static void completeCaseChange(Case newCase) { - logger.log(Level.INFO, "Changing Case to: {0}", newCase); //NON-NLS + LOGGER.log(Level.INFO, "Changing Case to: {0}", newCase); //NON-NLS if (newCase != null) { // new case is open // clear the temp folder when the case is created / opened @@ -1524,11 +1713,20 @@ public class Case implements SleuthkitCase.ErrorObserver { if (RuntimeProperties.coreComponentsAreActive()) { // enable these menus SwingUtilities.invokeLater(() -> { - CallableSystemAction.get(AddImageAction.class).setEnabled(true); - CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); - CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu - CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); + CallableSystemAction.get(AddImageAction.class + ).setEnabled(true); + CallableSystemAction + .get(CaseCloseAction.class + ).setEnabled(true); + CallableSystemAction + .get(CasePropertiesAction.class + ).setEnabled(true); + CallableSystemAction + .get(CaseDeleteAction.class + ).setEnabled(true); // Delete Case menu + CallableSystemAction + .get(OpenTimelineAction.class + ).setEnabled(true); if (newCase.hasData()) { // open all top components @@ -1554,11 +1752,21 @@ public class Case implements SleuthkitCase.ErrorObserver { CoreComponentControl.closeCoreWindows(); // disable these menus - CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu - CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu - CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu - CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); + CallableSystemAction + .get(AddImageAction.class + ).setEnabled(false); // Add Image menu + CallableSystemAction + .get(CaseCloseAction.class + ).setEnabled(false); // Case Close menu + CallableSystemAction + .get(CasePropertiesAction.class + ).setEnabled(false); // Case Properties menu + CallableSystemAction + .get(CaseDeleteAction.class + ).setEnabled(false); // Delete Case menu + CallableSystemAction + .get(OpenTimelineAction.class + ).setEnabled(false); } //clear pending notifications @@ -1573,7 +1781,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } //log memory usage after case changed - logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo()); + LOGGER.log(Level.INFO, PlatformUtil.getAllMemUsageInfo()); } @@ -1602,89 +1810,150 @@ public class Case implements SleuthkitCase.ErrorObserver { * @param newCaseName The name of the case. */ private static void addCaseNameToMainWindowTitle(String newCaseName) { - if (!newCaseName.equals("")) { + if (!newCaseName.isEmpty()) { Frame f = WindowManager.getDefault().getMainWindow(); f.setTitle(newCaseName + " - " + getAppName()); } } /** - * Deletes a case directory. + * Get the single thread executor for the current case, creating it if + * necessary. * - * @param casePath A case directory path. - * - * @return True if the deletion succeeded, false otherwise. - */ - static boolean deleteCaseDirectory(File casePath) { - logger.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS - return FileUtil.deleteDir(casePath); - } - - /** - * Get the single thread executor for the current case, creating it if necessary. * @return The executor */ - private static ExecutorService getCurrentCaseExecutor(){ - if(currentCaseExecutor == null){ - currentCaseExecutor = Executors.newSingleThreadExecutor(); + private static ExecutorService getSingleThreadedExecutor() { // RJCTODO: Does this need synch? + if (null == singleThreadedExecutor) { + singleThreadedExecutor = Executors.newSingleThreadExecutor(); } - return currentCaseExecutor; + return singleThreadedExecutor; + } /** - * Gets the time zone(s) of the image data source(s) in this case. - * - * @return The set of time zones in use. - * - * @deprecated Use getTimeZones instead. + * Constructs an Autopsy case. */ - @Deprecated - public Set getTimeZone() { - return getTimeZones(); + private Case(CaseMetadata caseMetadata, SleuthkitCase db) { + this.caseMetadata = caseMetadata; + this.db = db; + this.services = new Services(db); } /** - * Determines whether or not a given path is for a physical drive. + * Gets the path to the specified subdirectory of the case directory, + * creating it if it does not already exist. * - * @param path The path to test. - * - * @return True or false. - * - * @deprecated Use - * org.sleuthkit.autopsy.coreutils.DriveUtils.isPhysicalDrive instead. + * @return The absolute path to the specified subdirectory. */ - @Deprecated - static boolean isPhysicalDrive(String path) { - return DriveUtils.isPhysicalDrive(path); + private String getOrCreateSubdirectory(String subDirectoryName) { + File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile(); + if (!subDirectory.exists()) { + subDirectory.mkdirs(); + } + return subDirectory.toString(); + } + + private final static class IllegalCaseNameException extends Exception { + + private static final long serialVersionUID = 1L; + + private IllegalCaseNameException(String message) { + super(message); + } + + private IllegalCaseNameException(String message, Throwable cause) { + super(message, cause); + } + } + + private final static class CancelButtonListener implements ActionListener { + + private Future caseActionFuture; + private CaseContext caseContext; + + private void setCaseActionFuture(Future caseActionFuture) { + this.caseActionFuture = caseActionFuture; + } + + private void setCaseContext(CaseContext caseContext) { + this.caseContext = caseContext; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (null != this.caseContext) { + this.caseContext.requestCancel(); + } + if (null != this.caseActionFuture) { + this.caseActionFuture.cancel(true); + } + } + } /** - * Determines whether or not a given path is for a local drive or partition. + * Creates a new, single-user Autopsy case. * - * @param path The path to test. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. + * @param caseDisplayName The display name of case, which may be changed + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. * - * @deprecated Use org.sleuthkit.autopsy.coreutils.DriveUtils.isPartition - * instead. + * @throws CaseActionException if there is a problem creating the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + * @deprecated */ @Deprecated - static boolean isPartition(String path) { - return DriveUtils.isPartition(path); + public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException { + createCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE); } /** - * Determines whether or not a drive exists by eading the first byte and - * checking if it is a -1. + * Creates a new Autopsy case and makes it the current case. * - * @param path The path to test. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. + * @param caseDisplayName The display name of case, which may be changed + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. + * @param caseType The type of case (single-user or multi-user). * - * @return True or false. - * - * @deprecated Use org.sleuthkit.autopsy.coreutils.DriveUtils.driveExists - * instead. + * @throws CaseActionException if there is a problem creating the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + * @deprecated */ @Deprecated - static boolean driveExists(String path) { - return DriveUtils.driveExists(path); + public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + createCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType); + } + + /** + * Opens an existing Autopsy case and makes it the current case. + * + * @param caseMetadataFilePath The path of the case metadata (.aut) file. + * + * @throws CaseActionException if there is a problem opening the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + * @deprecated + */ + @Deprecated + public static void open(String caseMetadataFilePath) throws CaseActionException { + openCurrentCase(caseMetadataFilePath); } /** @@ -1742,21 +2011,6 @@ public class Case implements SleuthkitCase.ErrorObserver { return Version.getVersion(); } - /** - * Creates an Autopsy case directory. - * - * @param caseDir Path to the case directory (typically base + case name) - * @param caseName the case name (used only for error messages) - * - * @throws CaseActionException - * @Deprecated Use createCaseDirectory(String caseDir, CaseType caseType) - * instead - */ - @Deprecated - static void createCaseDirectory(String caseDir, String caseName) throws CaseActionException { - createCaseDirectory(caseDir, CaseType.SINGLE_USER_CASE); - } - /** * Check if case is currently open. * @@ -1769,19 +2023,6 @@ public class Case implements SleuthkitCase.ErrorObserver { return currentCase != null; } - /** - * Get module output directory path where modules should save their - * permanent data. - * - * @return absolute path to the module output directory - * - * @deprecated Use getModuleDirectory() instead. - */ - @Deprecated - public String getModulesOutputDirAbsPath() { - return getModuleDirectory(); - } - /** * Get relative (with respect to case dir) module output directory path * where modules should save their permanent data. The directory is a @@ -1806,20 +2047,23 @@ public class Case implements SleuthkitCase.ErrorObserver { * @deprecated Do not use. */ @Deprecated - public static PropertyChangeSupport getPropertyChangeSupport() { - return new PropertyChangeSupport(Case.class); + public static PropertyChangeSupport + getPropertyChangeSupport() { + return new PropertyChangeSupport(Case.class + ); } /** - * Gets the full path to the case metadata file for this case. + * Get module output directory path where modules should save their + * permanent data. * - * @return configFilePath The case metadata file path. + * @return absolute path to the module output directory * - * @deprecated Use getCaseMetadata and CaseMetadata.getFilePath instead. + * @deprecated Use getModuleDirectory() instead. */ @Deprecated - String getConfigFilePath() { - return getCaseMetadata().getFilePath().toString(); + public String getModulesOutputDirAbsPath() { + return getModuleDirectory(); } /** @@ -1844,18 +2088,15 @@ public class Case implements SleuthkitCase.ErrorObserver { } /** - * Finishes adding new local data source to the case. Sends out event and - * reopens windows if needed. + * Gets the time zone(s) of the image data source(s) in this case. * - * @param newDataSource new data source added + * @return The set of time zones in use. * - * @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and - * {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and - * {@link #notifyFailedAddingDataSource(java.util.UUID)} + * @deprecated Use getTimeZones instead. */ @Deprecated - void addLocalDataSource(Content newDataSource) { - notifyDataSourceAdded(newDataSource, UUID.randomUUID()); + public Set getTimeZone() { + return getTimeZones(); } /** @@ -1873,6 +2114,20 @@ public class Case implements SleuthkitCase.ErrorObserver { deleteReports(reports); } + /** + * Closes this Autopsy case. + * + * @throws CaseActionException if there is a problem closing the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + * @deprecated Use Case.closeCurrentCase instead. instead. + */ + @Deprecated + public void closeCase() throws CaseActionException { + Case.closeCurrentCase(); + } + @Deprecated public static final String propStartup = "LBL_StartupDialog"; //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java new file mode 100644 index 0000000000..91242fe477 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java @@ -0,0 +1,78 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Cursor; +import java.util.logging.Level; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestJob; +import org.sleuthkit.autopsy.ingest.IngestManager; + +/** + * A utility class for common code for case actions. + */ +class CaseActionHelper { + + /** + * Tries to closes the current case, if any, with a checks to see if ingest + * is running; if it is, the user is given the opportunity to let the ingest + * continue and stop the case action. + * + * @param + * @return True if the current case, if any, is closed, false otherwise. + */ + // RJCTODO: Be sure to test this! + static boolean closeCaseAndContinueAction() { + if (IngestManager.getInstance().isIngestRunning()) { + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), // RJCTODO + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), // RJCTODO + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object choice = DialogDisplayer.getDefault().notify(descriptor); + if (null != choice && DialogDescriptor.YES_OPTION == choice) { + IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED); + // RJCTODO; refer to JIRA here for blocking wait on cancel... + return true; + } else { + return false; + } + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + // RJCTODO: Pop up here + Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.SEVERE, "Error closing case.", ex); //NON-NLS + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return true; + } + + /** + * Private contructor to prevent instantiation. + */ + private CaseActionHelper() { + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java index 86cc38c251..1da9ab0061 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,35 +23,31 @@ import java.awt.event.ActionEvent; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; -import javax.swing.SwingWorker; -import org.openide.util.HelpCtx; -import org.openide.util.NbBundle; -import org.openide.util.actions.CallableSystemAction; -import org.openide.util.actions.Presenter; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; -import java.util.logging.Level; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; -import org.openide.windows.WindowManager; -import java.awt.Cursor; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.openide.util.actions.Presenter; /** - * The action to close the current Case. This class should be disabled on - * creation and it will be enabled on new case creation or case opened. + * An action to close the current case and pop up the start up window that + * allows a user to open anothjer case. This action should only be enabled when + * there is a current case. + * + * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.CaseCloseAction") @ActionRegistration(displayName = "#CTL_CaseCloseAct", lazy = false) @ActionReferences(value = { - @ActionReference(path = "Toolbars/Case", position = 104)}) + @ActionReference(path = "Toolbars/Case", position = 104) +}) public final class CaseCloseAction extends CallableSystemAction implements Presenter.Toolbar { - JButton toolbarButton = new JButton(); + private static final long serialVersionUID = 1L; + private final JButton toolbarButton = new JButton(); /** * The constructor for this class @@ -59,10 +55,7 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese public CaseCloseAction() { putValue("iconBase", "org/sleuthkit/autopsy/images/close-icon.png"); // put the icon NON-NLS putValue(Action.NAME, NbBundle.getMessage(CaseCloseAction.class, "CTL_CaseCloseAct")); // put the action Name - - // set action of the toolbar button toolbarButton.addActionListener(CaseCloseAction.this::actionPerformed); - this.setEnabled(false); } @@ -73,51 +66,9 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese */ @Override public void actionPerformed(ActionEvent e) { - - // if ingest is ongoing, warn and get confirmaion before opening a different case - if (IngestManager.getInstance().isIngestRunning()) { - // show the confirmation first to close the current case and open the "New Case" wizard panel - String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, - NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - - Object res = DialogDisplayer.getDefault().notify(descriptor); - if (res != null && res == DialogDescriptor.YES_OPTION) { - try { - Case.getCurrentCase().closeCase(); // close the current case - } catch (Exception ex) { - Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS - } - } else { - return; - } + if (CaseActionHelper.closeCaseAndContinueAction()) { + StartupWindowProvider.getInstance().open(); } - - if (Case.isCaseOpen() == false) { - return; - } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - new SwingWorker() { - - @Override - protected Void doInBackground() throws Exception { - try { - Case result = Case.getCurrentCase(); - result.closeCase(); - } catch (CaseActionException | IllegalStateException unused) { - // Already logged. - } - return null; - } - - @Override - protected void done() { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - StartupWindowProvider.getInstance().open(); - } - }.execute(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index bd07a3deaf..c08894d4f7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,7 +54,8 @@ public final class CaseMetadata { private static final String FILE_EXTENSION = ".aut"; private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)"); private static final String SCHEMA_VERSION_ONE = "1.0"; - private final static String AUTOPSY_CREATED_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS + private static final String SCHEMA_VERSION_TWO = "2"; + private final static String AUTOPSY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS private static final String CURRENT_SCHEMA_VERSION = "2.0"; @@ -74,6 +75,7 @@ public final class CaseMetadata { private final Path metadataFilePath; private Case.CaseType caseType; private String caseName; + private String caseDisplayName; private String caseNumber; private String examiner; private String caseDatabase; @@ -96,7 +98,9 @@ public final class CaseMetadata { * * @param caseDirectory The case directory. * @param caseType The type of case. - * @param caseName The name of the case. + * @param caseName The immutable name of the case. + * @param caseDisplayName The display name of the case, can be changed by + * a user. * @param caseNumber The case number. * @param examiner The name of the case examiner. * @param caseDatabase For a single-user case, the full path to the @@ -107,10 +111,11 @@ public final class CaseMetadata { * @throws CaseMetadataException If the new case metadata file cannot be * created. */ - CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseNumber, String examiner, String caseDatabase, String caseTextIndexName) throws CaseMetadataException { + CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseDisplayName, String caseNumber, String examiner, String caseDatabase, String caseTextIndexName) throws CaseMetadataException { metadataFilePath = Paths.get(caseDirectory, caseName + FILE_EXTENSION); this.caseType = caseType; this.caseName = caseName; + this.caseDisplayName = caseDisplayName; this.caseNumber = caseNumber; this.examiner = examiner; this.caseDatabase = caseDatabase; @@ -162,10 +167,11 @@ public final class CaseMetadata { } /** - * Gets the case display name. + * Gets the immutable case name, set at case creation. * * @return The case display name. */ + // RJCTODO: Deal with the change public String getCaseName() { return caseName; } @@ -176,6 +182,7 @@ public final class CaseMetadata { * * @param caseName A case display name. */ + // RJCTODO: Deal with the change, remove this void setCaseName(String caseName) throws CaseMetadataException { String oldCaseName = caseName; this.caseName = caseName; @@ -187,6 +194,33 @@ public final class CaseMetadata { } } + /** + * Gets the case display name. + * + * @return The case display name. + */ + public String getCaseDisplayName() { + return this.caseDisplayName; + } + + /** + * Sets the case display name. This does not change the name of the case + * directory, the case database, or the text index name. + * + * @param caseName A case display name. + */ + // RJCTODO: Deal with the change + void setCaseDisplayName(String caseName) throws CaseMetadataException { + String oldCaseName = caseName; + this.caseDisplayName = caseName; + try { + writeToFile(); + } catch (CaseMetadataException ex) { + this.caseDisplayName = oldCaseName; + throw ex; + } + } + /** * Gets the case number. * @@ -301,6 +335,7 @@ public final class CaseMetadata { * @throws CaseMetadataException If there is an error writing to the case * metadata file. */ + // RJCTODO: Should we have a backup copy of the file in case of error? private void writeToFile() throws CaseMetadataException { try { /* @@ -402,7 +437,7 @@ public final class CaseMetadata { String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true); this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true); if (schemaVersion.equals(SCHEMA_VERSION_ONE)) { - this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_VERSION_ELEMENT_NAME, true); + this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_VERSION_ELEMENT_NAME, true); } else { this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java index 4e8243d3ba..c3bd3fa7ce 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,21 +28,16 @@ import org.openide.util.lookup.ServiceProvider; /** * The action to create a new case. This action class is always enabled. * - * @author jantonius + * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). */ @ServiceProvider(service = CaseNewActionInterface.class) public final class CaseNewAction extends CallableSystemAction implements CaseNewActionInterface { - private NewCaseWizardAction wizard = SystemAction.get(NewCaseWizardAction.class); + private static final long serialVersionUID = 1L; - /** - * Calls the "New Case" wizard panel action. - * - * @param e - */ @Override public void actionPerformed(ActionEvent e) { - wizard.performAction(); + SystemAction.get(NewCaseWizardAction.class).performAction(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 8a4f4b6c6f..dcf902dfc6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,32 +22,34 @@ import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; +import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; -import org.openide.util.NbBundle; -import org.openide.util.lookup.ServiceProvider; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; -import org.sleuthkit.autopsy.coreutils.Version; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; -import org.sleuthkit.autopsy.coreutils.Logger; -import java.util.logging.Level; import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; +import org.openide.util.lookup.ServiceProvider; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action that opens an existing case. + * + * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). */ @ServiceProvider(service = CaseOpenAction.class) public final class CaseOpenAction extends CallableSystemAction implements ActionListener { - private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName()); + private static final Logger LOGGER = Logger.getLogger(CaseOpenAction.class.getName()); private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS private static final long serialVersionUID = 1L; private final JFileChooser fileChooser = new JFileChooser(); @@ -75,73 +77,43 @@ public final class CaseOpenAction extends CallableSystemAction implements Action */ @Override public void actionPerformed(ActionEvent e) { - /* - * If ingest is running, do a dialog to warn the user and confirm the - * intent to close the current case and leave the ingest process - * incomplete. - */ - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"), - NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object res = DialogDisplayer.getDefault().notify(descriptor); - if (res != null && res == DialogDescriptor.YES_OPTION) { - Case currentCase = null; + if (CaseActionHelper.closeCaseAndContinueAction()) { + + /** + * Pop up a file chooser to allow the user to select a case metadata + * file (.aut file). + */ + int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); + if (retval == JFileChooser.APPROVE_OPTION) { + /* + * Close the startup window, if it is open. + */ + StartupWindowProvider.getInstance().close(); + + /* + * Try to open the case associated with the case metadata file + * the user selected. + */ + final String path = fileChooser.getSelectedFile().getPath(); + String dirPath = fileChooser.getSelectedFile().getParent(); + ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator))); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - currentCase = Case.getCurrentCase(); - currentCase.closeCase(); - } catch (IllegalStateException ignored) { - /* - * No current case. - */ + Case.openCurrentCase(path); } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null != currentCase ? currentCase.getCaseDirectory() : "?")), ex); //NON-NLS + LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + if (!Case.isCaseOpen()) { + StartupWindowProvider.getInstance().open(); + } } - } else { - return; } } - - /** - * Pop up a file chooser to allow the user to select a case meta data - * file (.aut file). - */ - int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); - if (retval == JFileChooser.APPROVE_OPTION) { - /* - * Close the startup window, if it is open. - */ - StartupWindowProvider.getInstance().close(); - - /* - * Try to open the case associated with the case metadata file the - * user selected. - */ - final String path = fileChooser.getSelectedFile().getPath(); - String dirPath = fileChooser.getSelectedFile().getParent(); - ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator))); - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - new Thread(() -> { - try { - Case.open(path); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - if (!Case.isCaseOpen()) { - StartupWindowProvider.getInstance().open(); - } - }); - } - }).start(); - } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java index c575217f06..4b99312fc9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,68 +23,54 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; /** - * A task progress indicator that writes to the Autopsy application log. + * A progress indicator that writes progress to the Autopsy application log. */ class LoggingProgressIndicator implements ProgressIndicator { - - private static Logger LOGGER = Logger.getLogger(LoggingProgressIndicator.class.getName()); - private String taskName; + + private final Logger LOGGER = Logger.getLogger(LoggingProgressIndicator.class.getName()); private int totalWorkUnits; - - LoggingProgressIndicator() { - LOGGER = Logger.getLogger(LoggingProgressIndicator.class.getName()); - } - - LoggingProgressIndicator(Logger logger) { - LOGGER = logger; - } - - @Override - public void setTaskName(String taskName) { - this.taskName = taskName; - } @Override - public void start(int totalWorkUnits) { + public void start(String message, int totalWorkUnits) { this.totalWorkUnits = totalWorkUnits; - LOGGER.log(Level.INFO, "{0} task started in determinate mode with {1} total work units", new Object[]{this.taskName, this.totalWorkUnits}); + LOGGER.log(Level.INFO, "{0} started, {1} total work units", new Object[]{message, this.totalWorkUnits}); } @Override - public void start() { - LOGGER.log(Level.INFO, "{0} task started in indeterminate mode", this.taskName); + public void start(String message) { + LOGGER.log(Level.INFO, "{0}", message); } @Override - public void switchToIndeterminate() { + public void switchToIndeterminate(String message) { this.totalWorkUnits = 0; - LOGGER.log(Level.INFO, "{0} task switched to indeterminate mode", this.taskName); + LOGGER.log(Level.INFO, "{0}", message); } @Override - public void switchToDeterminate(int workUnitsCompleted, int totalWorkUnits) { + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { this.totalWorkUnits = totalWorkUnits; - LOGGER.log(Level.INFO, "{0} task switched to determinate mode, reporting {1} of {2} total work units completed", new Object[]{this.taskName, workUnitsCompleted, this.totalWorkUnits}); + LOGGER.log(Level.INFO, "{0}, {1} of {2} total work units completed", new Object[]{message, workUnitsCompleted, this.totalWorkUnits}); } @Override public void progress(String message) { - LOGGER.log(Level.INFO, "{0} task reported message '{0}'", message); + LOGGER.log(Level.INFO, "{0}", message); } @Override public void progress(int workUnitsCompleted) { - LOGGER.log(Level.INFO, "{0} task reported {1} of {2} total work units completed", new Object[]{this.taskName, workUnitsCompleted, this.totalWorkUnits}); + LOGGER.log(Level.INFO, "{1} of {2} total work units completed", new Object[]{workUnitsCompleted, this.totalWorkUnits}); } @Override public void progress(String message, int workUnitsCompleted) { - LOGGER.log(Level.INFO, "{0} task reported message '{0}' with {1} of {2} total work units completed", new Object[]{this.taskName, message, workUnitsCompleted, this.totalWorkUnits}); + LOGGER.log(Level.INFO, "{0}, {1} of {2} total work units completed", new Object[]{message, workUnitsCompleted, this.totalWorkUnits}); } @Override - public void finish() { - LOGGER.log(Level.INFO, "{0} task reported finished", this.taskName); + public void finish(String message) { + LOGGER.log(Level.INFO, "{0} finished", message); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/casemodule/ModalDialogProgressIndicator.java new file mode 100644 index 0000000000..19ff954ddf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ModalDialogProgressIndicator.java @@ -0,0 +1,150 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Dialog; +import java.awt.event.ActionListener; +import javax.swing.SwingUtilities; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.util.HelpCtx; +import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; + +/** + * A progress indicator that displays progress using a modal dialog with a + * message label and a progress bar. + */ +public final class ModalDialogProgressIndicator implements ProgressIndicator { + + private final ProgressPanel progressPanel; + private final ActionListener listener; + private final Dialog dialog; + + public ModalDialogProgressIndicator(String title, Object[] options, Object initialValue, HelpCtx helpCtx, ActionListener listener) { + progressPanel = new ProgressPanel(); + this.listener = listener; + DialogDescriptor dialogDescriptor = new DialogDescriptor( + progressPanel, + title, + true, + options, + initialValue, + DialogDescriptor.BOTTOM_ALIGN, + helpCtx, + this.listener); + dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); + } + + public void setVisible(boolean isVisible) { + this.dialog.setVisible(isVisible); + } + + public ActionListener getListener() { + return listener; + } + + @Override + public void start(String message, int totalWorkUnits) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(false); + progressPanel.setMessage(message); + progressPanel.setMaximum(totalWorkUnits); + } + }); + } + + @Override + public void start(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(true); + progressPanel.setMessage(message); + } + }); + } + + @Override + public void switchToIndeterminate(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(true); + progressPanel.setMessage(message); + } + }); + } + + @Override + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(false); + progressPanel.setMessage(message); + progressPanel.setMaximum(totalWorkUnits); + progressPanel.setCurrent(workUnitsCompleted); + } + }); + } + + @Override + public void progress(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setMessage(message); + } + }); + } + + @Override + public void progress(int workUnitsCompleted) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setCurrent(workUnitsCompleted); + } + }); + } + + @Override + public void progress(String message, int workUnitsCompleted) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setMessage(message); + progressPanel.setCurrent(workUnitsCompleted); + } + }); + } + + @Override + public void finish(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setMessage(message); + } + }); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 39f831c5a1..66241a8453 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,28 +19,25 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Component; +import java.awt.Cursor; import java.awt.Dialog; import java.io.File; import java.text.MessageFormat; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JComponent; -import javax.swing.SwingWorker; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import org.openide.DialogDescriptor; +import javax.swing.SwingWorker; import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.WizardDescriptor; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.SystemAction; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.JOptionPane; -import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.openide.windows.WindowManager; -import java.awt.Cursor; -import java.util.concurrent.ExecutionException; -import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.casemodule.Case.CaseType; +import org.sleuthkit.autopsy.coreutils.Logger; /** * An action that creates and runs the new case wizard. @@ -53,36 +50,10 @@ final class NewCaseWizardAction extends CallableSystemAction { @Override public void performAction() { - /* - * If ingest is running, do a dialog to warn the user and confirm the - * intent to close the current case and leave the ingest process - * incomplete. - */ - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"), - NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object res = DialogDisplayer.getDefault().notify(descriptor); - if (res != null && res == DialogDescriptor.YES_OPTION) { - Case currentCase = null; - try { - currentCase = Case.getCurrentCase(); - currentCase.closeCase(); - } catch (IllegalStateException ignored) { - /* - * No current case. - */ - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null != currentCase ? currentCase.getCaseDirectory() : "?")), ex); //NON-NLS - } - } else { - return; - } + if (CaseActionHelper.closeCaseAndContinueAction()) { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // RJCTODO: Is this right? + runNewCaseWizard(); } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - runNewCaseWizard(); } private void runNewCaseWizard() { @@ -101,7 +72,7 @@ final class NewCaseWizardAction extends CallableSystemAction { final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS - Case.create(createdDirectory, caseName, caseNumber, examiner, caseType); + Case.createCurrentCase(createdDirectory, caseName, caseNumber, examiner, caseType); return null; } @@ -111,7 +82,7 @@ final class NewCaseWizardAction extends CallableSystemAction { get(); AddImageAction addImageAction = SystemAction.get(AddImageAction.class); addImageAction.actionPerformed(null); - } catch (Exception ex) { + } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, String.format("Error creating case %s", wizardDescriptor.getProperty("caseName")), ex); //NON-NLS SwingUtilities.invokeLater(() -> { JOptionPane.showMessageDialog( diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java index 24c1e2a17e..9cc94cd182 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java @@ -316,7 +316,6 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,18 +18,17 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Cursor; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.File; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.JTable; -import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.windows.WindowManager; -import java.awt.Cursor; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Panel used by the the open recent case option of the start window. @@ -103,7 +102,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel { } final String casePath = casePaths[imagesTable.getSelectedRow()]; final String caseName = caseNames[imagesTable.getSelectedRow()]; - if (!casePath.equals("")) { + if (!casePath.isEmpty()) { try { StartupWindowProvider.getInstance().close(); CueBannerPanel.closeOpenRecentCasesWindow(); @@ -114,7 +113,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel { /* * Open the case. */ - if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) { + if (caseName.isEmpty() || casePath.isEmpty() || (!new File(casePath).exists())) { JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName), NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), @@ -124,27 +123,21 @@ class OpenRecentCasePanel extends javax.swing.JPanel { StartupWindowProvider.getInstance().open(); } } else { - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - }); - new Thread(() -> { - try { - Case.open(casePath); - } catch (CaseActionException ex) { - SwingUtilities.invokeLater(() -> { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - if (!Case.isCaseOpen()) { - StartupWindowProvider.getInstance().open(); - } - }); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + Case.openCurrentCase(casePath); + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + if (!Case.isCaseOpen()) { + StartupWindowProvider.getInstance().open(); } - }).start(); + } } } } @@ -160,7 +153,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel { public int getRowCount() { int count = 0; for (String s : caseNames) { - if (!s.equals("")) { + if (!s.isEmpty()) { count++; } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.form new file mode 100644 index 0000000000..d3377cf55e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.form @@ -0,0 +1,52 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.java new file mode 100644 index 0000000000..613c2dd9c1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.java @@ -0,0 +1,97 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +/** + * RJCTODO + */ +class ProgressPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + /** + * RJCTODO + */ + ProgressPanel() { + initComponents(); + this.progressBar.setMinimum(0); + } + + /** + * RJCTODO + * @param message + */ + void setMessage(String message) { + this.progressMessage.setText(message); + } + + void setInderminate(boolean indeterminate) { + this.progressBar.setIndeterminate(indeterminate); + } + + void setMaximum(int max) { + this.progressBar.setMaximum(max); + } + + void setCurrent(int current) { + this.progressBar.setValue(current); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + progressMessage = new javax.swing.JLabel(); + progressBar = new javax.swing.JProgressBar(); + + org.openide.awt.Mnemonics.setLocalizedText(progressMessage, org.openide.util.NbBundle.getMessage(ProgressPanel.class, "ProgressPanel.progressMessage.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(22, 22, 22) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(progressMessage, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 355, Short.MAX_VALUE)) + .addContainerGap(23, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(24, 24, 24) + .addComponent(progressMessage) + .addGap(18, 18, 18) + .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(33, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JProgressBar progressBar; + private javax.swing.JLabel progressMessage; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index 2df4401ad9..7fabd797ec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,24 +18,25 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Cursor; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import org.openide.util.NbBundle; -import org.openide.windows.WindowManager; -import java.awt.Cursor; import java.util.logging.Level; +import javax.swing.JOptionPane; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action listener that opens a recent case. + * + * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). */ class RecentItems implements ActionListener { @@ -49,7 +50,7 @@ class RecentItems implements ActionListener { * @param caseName The name of the case. * @param caseMetaDataFilePath The path to the case metadata file. */ - public RecentItems(String caseName, String caseMetaDataFilePath) { + RecentItems(String caseName, String caseMetaDataFilePath) { this.caseName = caseName; this.caseMetaDataFilePath = caseMetaDataFilePath; } @@ -61,71 +62,35 @@ class RecentItems implements ActionListener { */ @Override public void actionPerformed(ActionEvent e) { - /* - * If ingest is running, do a dialog to warn the user and confirm the - * intent to close the current case and leave the ingest process - * incomplete. - */ - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"), - NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object res = DialogDisplayer.getDefault().notify(descriptor); - if (res != null && res == DialogDescriptor.YES_OPTION) { - Case currentCase = null; - try { - currentCase = Case.getCurrentCase(); - currentCase.closeCase(); - } catch (IllegalStateException ignored) { - /* - * No current case. - */ - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null!= currentCase ? currentCase.getCaseDirectory() : "?")),ex); //NON-NLS - } - } else { - return; - } - } - - /* - * Open the case. - */ - if (caseName.equals("") || caseMetaDataFilePath.equals("") || (!new File(caseMetaDataFilePath).exists())) { - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName), - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), - JOptionPane.ERROR_MESSAGE); - RecentCases.getInstance().removeRecentCase(caseName, caseMetaDataFilePath); - if (Case.isCaseOpen() == false) { - EventQueue.invokeLater(() -> { - StartupWindowProvider.getInstance().open(); - }); - } - } else { - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - }); - new Thread(() -> { - try { - Case.open(caseMetaDataFilePath); - } catch (CaseActionException ex) { - SwingUtilities.invokeLater(() -> { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - if (!Case.isCaseOpen()) { - StartupWindowProvider.getInstance().open(); - } + if (CaseActionHelper.closeCaseAndContinueAction()) { + if (caseName.isEmpty() || caseMetaDataFilePath.isEmpty() || (!new File(caseMetaDataFilePath).exists())) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName), + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), + JOptionPane.ERROR_MESSAGE); + RecentCases.getInstance().removeRecentCase(caseName, caseMetaDataFilePath); + if (Case.isCaseOpen() == false) { + EventQueue.invokeLater(() -> { + StartupWindowProvider.getInstance().open(); }); } - }).start(); + } else { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + Case.openCurrentCase(caseMetaDataFilePath); + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + if (!Case.isCaseOpen()) { + StartupWindowProvider.getInstance().open(); + } + } + } } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index e39f0d5ebb..61a87decc6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -195,6 +195,7 @@ public class SingleUserCaseConverter { CaseMetadata newCaseMetadata = new CaseMetadata(icd.getCaseOutputFolder().toString(), CaseType.MULTI_USER_CASE, icd.getNewCaseName(), + icd.getNewCaseName(), // RJCTODO oldCaseMetadata.getCaseNumber(), oldCaseMetadata.getExaminer(), dbName, solrName); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/TestAutopsyService.java b/Core/src/org/sleuthkit/autopsy/casemodule/TestAutopsyService.java new file mode 100644 index 0000000000..488c16e9a5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/TestAutopsyService.java @@ -0,0 +1,73 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; + +/** + * An implementation of the Autopsy service interface used for test purposes. + */ +@ServiceProvider(service = AutopsyService.class) +public class TestAutopsyService implements AutopsyService { + + @Override + public String getServiceName() { + return "TestService"; + } + + @Override + public void openCaseResources(CaseContext context) throws AutopsyServiceException { + ProgressIndicator progressIndicator = context.getProgressIndicator(); + try { + progressIndicator.start("Doing first task", 100); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + progressIndicator.finish("First task completed"); + progressIndicator.start("Doing second task"); + Thread.sleep(10000L); + } catch (InterruptedException ex) { + progressIndicator.finish("Cancelling..."); + try { + Thread.sleep(1000L); + } catch (InterruptedException ex1) { + } + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index adc89d4afa..e546e4d260 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -166,6 +166,9 @@ public final class CoordinationService { * in the namespace managed by this coordination service. Blocks until the * lock is obtained or the time out expires. * + * IMPORTANT: The lock needs to be released in the same thread in which it + * is acquired. + * * @param category The desired category in the namespace. * @param nodePath The node path to use as the basis for the lock. * @param timeOut Length of the time out. @@ -201,6 +204,9 @@ public final class CoordinationService { * in the namespace managed by this coordination service. Returns * immediately if the lock can not be acquired. * + * IMPORTANT: The lock needs to be released in the same thread in which it + * is acquired. + * * @param category The desired category in the namespace. * @param nodePath The node path to use as the basis for the lock. * @@ -227,6 +233,9 @@ public final class CoordinationService { * the namespace managed by this coordination service. Blocks until the lock * is obtained or the time out expires. * + * IMPORTANT: The lock needs to be released in the same thread in which it + * is acquired. + * * @param category The desired category in the namespace. * @param nodePath The node path to use as the basis for the lock. * @param timeOut Length of the time out. @@ -262,6 +271,9 @@ public final class CoordinationService { * the namespace managed by this coordination service. Returns immediately * if the lock can not be acquired. * + * IMPORTANT: The lock needs to be released in the same thread in which it + * is acquired. + * * @param category The desired category in the namespace. * @param nodePath The node path to use as the basis for the lock. * diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java index 3c240c749f..2550f954b4 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,49 +19,46 @@ package org.sleuthkit.autopsy.corecomponentinterfaces; /** - * An interface for task progress indicators. A progress indicator can run in + * An interface for progress indicators. A progress indicator can run in * determinate mode (the total number of work units to be completed is known) or * indeterminate mode. Switching back and forth between the two modes is - * supported. + * supported. Starting, finishing, and starting again is supported. */ public interface ProgressIndicator { - /** - * Sets the name of the task for which progress is being indicated. This - * should be set before the task is started and should not be changed. - * - * @param taskName The task name. - */ - void setTaskName(String taskName); - /** * Starts the progress indicator in determinate mode (the total number of * work units to be completed is known). * + * @param message The initial progress message. * @param totalWorkUnits The total number of work units. */ - void start(int totalWorkUnits); + void start(String message, int totalWorkUnits); /** * Starts the progress indicator in indeterminate mode (the total number of * work units to be completed is unknown). + * + * @param message The initial progress message. */ - void start(); + void start(String message); /** * Switches the progress indicator to indeterminate mode (the total number * of work units to be completed is unknown). + * @param message The initial progress message. */ - public void switchToIndeterminate(); + public void switchToIndeterminate(String message); /** * Switches the progress indicator to determinate mode (the total number of * work units to be completed is known). * + * @param message The initial progress message. * @param workUnitsCompleted The number of work units completed so far. * @param totalWorkUnits The total number of work units to be completed. */ - public void switchToDeterminate(int workUnitsCompleted, int totalWorkUnits); + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits); /** * Updates the progress indicator with a progress message. @@ -91,7 +88,9 @@ public interface ProgressIndicator { /** * Finishes the progress indicator when the task is completed. + * + * @param message The finished message. */ - void finish(); - + void finish(String message); + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java index 34a752a1e8..48c3127027 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,11 +46,11 @@ import org.sleuthkit.autopsy.coreutils.Logger; public class Installer extends ModuleInstall { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(Installer.class.getName()); + private static final Logger LOGGER = Logger.getLogger(Installer.class.getName()); private static Installer instance; public synchronized static Installer getDefault() { - if (instance == null) { + if (null == instance) { instance = new Installer(); } return instance; @@ -80,21 +80,19 @@ public class Installer extends ModuleInstall { if (processor instanceof OpenFromArguments) { OpenFromArguments argsProcessor = (OpenFromArguments) processor; final String caseFile = argsProcessor.getDefaultArg(); - if (caseFile != null && !caseFile.equals("") && caseFile.endsWith(CaseMetadata.getFileExtension()) && new File(caseFile).exists()) { //NON-NLS - new Thread(() -> { - try { - Case.open(caseFile); - } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseFile), ex); //NON-NLS - } - }).start(); + if (caseFile != null && !caseFile.isEmpty() && caseFile.endsWith(CaseMetadata.getFileExtension()) && new File(caseFile).exists()) { //NON-NLS + try { + Case.openCurrentCase(caseFile); + } catch (CaseActionException ex) { + // RJCTODO: SHould there be a popup here? + LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseFile), ex); //NON-NLS + } return; } } } StartupWindowProvider.getInstance().open(); }); - } @Override @@ -104,22 +102,11 @@ public class Installer extends ModuleInstall { @Override public void close() { - new Thread(() -> { - String caseDirName = null; - try { - if (Case.isCaseOpen()) { - Case currentCase = Case.getCurrentCase(); - caseDirName = currentCase.getCaseDirectory(); - currentCase.closeCase(); - } - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error closing case with case directory %s", (null != caseDirName ? caseDirName : "?")), ex); //NON-NLS - } catch (IllegalStateException ignored) { - /* - * No current case. Case.isCaseOpen is not reliable. - */ - } - }).start(); + try { + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + LOGGER.log(Level.SEVERE, "Error closing current case", ex); //NON-NLS + } } private void setLookAndFeel() { @@ -137,7 +124,7 @@ public class Installer extends ModuleInstall { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { - logger.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS + LOGGER.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS } // Store the keys that deal with menu items @@ -153,7 +140,7 @@ public class Installer extends ModuleInstall { try { UIManager.setLookAndFeel(info.getClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { - logger.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS + LOGGER.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS } break; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java new file mode 100644 index 0000000000..c47b53a618 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java @@ -0,0 +1,60 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.hashdatabase; + +import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; + +/** + * A "silent" or "null" progress indicator. + */ +public class SilentProgressIndicator implements ProgressIndicator { + + @Override + public void start(String message, int totalWorkUnits) { + } + + @Override + public void start(String message) { + } + + @Override + public void switchToIndeterminate(String message) { + } + + @Override + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { + } + + @Override + public void progress(String message) { + } + + @Override + public void progress(int workUnitsCompleted) { + } + + @Override + public void progress(String message, int workUnitsCompleted) { + } + + @Override + public void finish(String message) { + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java new file mode 100644 index 0000000000..e187cec054 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java @@ -0,0 +1,125 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.swing.SwingUtilities; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.actions.CallableSystemAction; +import org.sleuthkit.autopsy.casemodule.AddImageAction; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseNewAction; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; + +/** + * Handles locating and opening cases created by auto ingest. + */ +final class AutoIngestCaseManager { + + private static final Logger LOGGER = Logger.getLogger(AutoIngestCaseManager.class.getName()); + private static AutoIngestCaseManager instance; + + /** + * Gets the auto ingest case manager. + * + * @return The auto ingest case manager singleton. + */ + synchronized static AutoIngestCaseManager getInstance() { + if (null == instance) { + instance = new AutoIngestCaseManager(); + } + return instance; + } + + /** + * Constructs an object that handles locating and opening cases created by + * auto ingest. + */ + private AutoIngestCaseManager() { + /* + * Disable the new case action because review mode is only for looking + * at cases created by automated ingest. + */ + CallableSystemAction.get(CaseNewAction.class).setEnabled(false); + + /* + * Permanently delete the "Open Recent Cases" item in the "File" menu. + * This is quite drastic, as it also affects Autopsy standalone mode on + * this machine, but review mode is only for looking at cases created by + * automated ingest. + */ + // RJCTODO: Write a story about this. + FileObject root = FileUtil.getConfigRoot(); + FileObject openRecentCasesMenu = root.getFileObject("Menu/Case/OpenRecentCase"); + if (openRecentCasesMenu != null) { + try { + openRecentCasesMenu.delete(); + } catch (IOException ex) { + AutoIngestCaseManager.LOGGER.log(Level.WARNING, "Unable to remove Open Recent Cases file menu item", ex); + } + } + } + + /* + * Gets a list of the cases in the top level case folder used by auto + * ingest. + */ + List getCases() { + List cases = new ArrayList<>(); + List caseFolders = PathUtils.findCaseFolders(Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder())); + for (Path caseFolderPath : caseFolders) { + cases.add(new AutoIngestCase(caseFolderPath)); + } + return cases; + } + + /** + * Opens an auto ingest case case. + * + * @param caseMetadataFilePath Path to the case metadata file. + * + * @throws CaseActionException + */ + synchronized void openCase(Path caseMetadataFilePath) throws CaseActionException { + /* + * Open the case. + */ + Case.openCurrentCase(caseMetadataFilePath.toString()); + + /** + * Disable the add data source action in auto ingest examiner mode. This + * has to be done here because Case.open() calls Case.doCaseChange() and + * the latter method enables the action. Since Case.doCaseChange() + * enables the menus on EDT by calling SwingUtilities.invokeLater(), we + * have to do the same thing here to maintain the order of execution. + */ + // RJCTODO: Write a story about this. + SwingUtilities.invokeLater(() -> { + CallableSystemAction.get(AddImageAction.class).setEnabled(false); + }); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form similarity index 96% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.form rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form index 2756fa8da0..0c22854c9a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form @@ -73,7 +73,7 @@ - + @@ -109,7 +109,7 @@ - + @@ -160,7 +160,7 @@ - + @@ -173,7 +173,7 @@ - + @@ -186,7 +186,7 @@ - + @@ -199,7 +199,7 @@ - + @@ -213,7 +213,7 @@ - + @@ -222,10 +222,10 @@ - + - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java similarity index 88% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java index fc45308db7..22b2c83a38 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,38 +18,38 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.Cursor; import java.awt.Desktop; -import java.nio.file.Paths; -import java.util.List; -import javax.swing.JPanel; import java.awt.EventQueue; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Date; +import java.util.List; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.JOptionPane; import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; -import org.sleuthkit.autopsy.casemodule.StartupWindowProvider; -import java.awt.Cursor; -import java.io.IOException; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.experimental.autoingest.ReviewModeCaseManager.ReviewModeCaseManagerException; +import org.sleuthkit.autopsy.casemodule.StartupWindowProvider; +import org.sleuthkit.autopsy.coreutils.Logger; /** - * A panel that allows a user to open cases created by automated ingest. + * A panel that allows a user to open cases created by auto ingest. */ -public final class ReviewModeCasePanel extends JPanel { +public final class AutoIngestCasePanel extends JPanel { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(ReviewModeCasePanel.class.getName()); + private static final Logger logger = Logger.getLogger(AutoIngestCasePanel.class.getName()); private static final AutoIngestCase.LastAccessedDateDescendingComparator reverseDateModifiedComparator = new AutoIngestCase.LastAccessedDateDescendingComparator(); private static final int CASE_COL_MIN_WIDTH = 30; private static final int CASE_COL_MAX_WIDTH = 2000; @@ -60,8 +60,8 @@ public final class ReviewModeCasePanel extends JPanel { private static final int STATUS_COL_MIN_WIDTH = 55; private static final int STATUS_COL_MAX_WIDTH = 250; private static final int STATUS_COL_PREFERRED_WIDTH = 60; - private static final int MILLISECONDS_TO_WAIT_BEFORE_STARTING = 500; // RJCTODO: Shorten name - private static final int MILLISECONDS_TO_WAIT_BETWEEN_UPDATES = 30000; // RJCTODO: Shorten name + private static final int MILLIS_TO_WAIT_BEFORE_STARTING = 500; + private static final int MILLIS_TO_WAIT_BETWEEN_UPDATES = 30000; private ScheduledThreadPoolExecutor casesTableRefreshExecutor; /* @@ -71,11 +71,11 @@ public final class ReviewModeCasePanel extends JPanel { * TODO (RC): Consider unifying this stuff in an enum as in * AutoIngestDashboard to make it less error prone. */ - private static final String CASE_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.CaseHeaderText"); - private static final String CREATEDTIME_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.CreatedTimeHeaderText"); - private static final String COMPLETEDTIME_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.LastAccessedTimeHeaderText"); - private static final String STATUS_ICON_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.StatusIconHeaderText"); - private static final String OUTPUT_FOLDER_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.OutputFolderHeaderText"); + private static final String CASE_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.CaseHeaderText"); + private static final String CREATEDTIME_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.CreatedTimeHeaderText"); + private static final String COMPLETEDTIME_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.LastAccessedTimeHeaderText"); + private static final String STATUS_ICON_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.StatusIconHeaderText"); + private static final String OUTPUT_FOLDER_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.OutputFolderHeaderText"); enum COLUMN_HEADERS { @@ -83,7 +83,7 @@ public final class ReviewModeCasePanel extends JPanel { CREATEDTIME, COMPLETEDTIME, STATUS_ICON, - OUTPUTFOLDER // RJCTODO: Change name + OUTPUTFOLDER } private final String[] columnNames = {CASE_HEADER, CREATEDTIME_HEADER, COMPLETEDTIME_HEADER, STATUS_ICON_HEADER, OUTPUT_FOLDER_HEADER}; private DefaultTableModel caseTableModel; @@ -92,8 +92,10 @@ public final class ReviewModeCasePanel extends JPanel { /** * Constructs a panel that allows a user to open cases created by automated * ingest. + * + * @param parent The parent dialog for this panel. */ - public ReviewModeCasePanel(JDialog parent) { + public AutoIngestCasePanel(JDialog parent) { caseTableModel = new DefaultTableModel(columnNames, 0) { private static final long serialVersionUID = 1L; @@ -183,7 +185,7 @@ public final class ReviewModeCasePanel extends JPanel { casesTableRefreshExecutor = new ScheduledThreadPoolExecutor(1); this.casesTableRefreshExecutor.scheduleAtFixedRate(() -> { refreshCasesTable(); - }, MILLISECONDS_TO_WAIT_BEFORE_STARTING, MILLISECONDS_TO_WAIT_BETWEEN_UPDATES, TimeUnit.MILLISECONDS); + }, MILLIS_TO_WAIT_BEFORE_STARTING, MILLIS_TO_WAIT_BETWEEN_UPDATES, TimeUnit.MILLISECONDS); } } @@ -214,7 +216,7 @@ public final class ReviewModeCasePanel extends JPanel { private void refreshCasesTable() { try { currentlySelectedCase = getSelectedCase(); - List theModel = ReviewModeCaseManager.getInstance().getCases(); + List theModel = AutoIngestCaseManager.getInstance().getCases(); EventQueue.invokeLater(new CaseTableRefreshTask(theModel)); } catch (Exception ex) { logger.log(Level.SEVERE, "Unexpected exception in refreshCasesTable", ex); //NON-NLS @@ -279,10 +281,10 @@ public final class ReviewModeCasePanel extends JPanel { private void openCase(Path caseMetadataFilePath) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - ReviewModeCaseManager.getInstance().openCaseInEDT(caseMetadataFilePath); + AutoIngestCaseManager.getInstance().openCase(caseMetadataFilePath); stopCasesTableRefreshes(); StartupWindowProvider.getInstance().close(); - } catch (ReviewModeCaseManagerException ex) { + } catch (CaseActionException ex) { logger.log(Level.SEVERE, String.format("Error while opening case with case metadata file path %s", caseMetadataFilePath), ex); /* * ReviewModeCaseManagerExceptions have user-friendly error @@ -290,7 +292,7 @@ public final class ReviewModeCasePanel extends JPanel { */ JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getMessage(), - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.cannotOpenCase"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.cannotOpenCase"), JOptionPane.ERROR_MESSAGE); } finally { @@ -345,18 +347,12 @@ public final class ReviewModeCasePanel extends JPanel { long multiplier = 1; if (rbAllCases.isSelected()) { return true; - } else { - if (rbMonths.isSelected()) { - multiplier = 31; - } else { - if (rbWeeks.isSelected()) { - multiplier = 7; - } else { - if (rbDays.isSelected()) { - multiplier = 1; - } - } - } + } else if (rbMonths.isSelected()) { + multiplier = 31; + } else if (rbWeeks.isSelected()) { + multiplier = 7; + } else if (rbDays.isSelected()) { + multiplier = 1; } return ((currentTime - inputTime) / (1000 * 60 * 60 * 24)) < (numberOfUnits * multiplier); } @@ -387,7 +383,7 @@ public final class ReviewModeCasePanel extends JPanel { setName("Completed Cases"); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnOpen.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnOpen.text")); // NOI18N bnOpen.setEnabled(false); bnOpen.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -407,7 +403,7 @@ public final class ReviewModeCasePanel extends JPanel { }); scrollPaneTable.setViewportView(casesTable); - org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnRefresh.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnRefresh.text")); // NOI18N bnRefresh.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bnRefreshActionPerformed(evt); @@ -416,7 +412,7 @@ public final class ReviewModeCasePanel extends JPanel { rbGroupHistoryLength.add(rbAllCases); rbAllCases.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(rbAllCases, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbAllCases.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbAllCases, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbAllCases.text")); // NOI18N rbAllCases.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { rbAllCasesItemStateChanged(evt); @@ -424,7 +420,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupHistoryLength.add(rbMonths); - org.openide.awt.Mnemonics.setLocalizedText(rbMonths, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbMonths.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbMonths, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbMonths.text")); // NOI18N rbMonths.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { rbMonthsItemStateChanged(evt); @@ -432,7 +428,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupHistoryLength.add(rbWeeks); - org.openide.awt.Mnemonics.setLocalizedText(rbWeeks, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbWeeks.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbWeeks, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbWeeks.text")); // NOI18N rbWeeks.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { rbWeeksItemStateChanged(evt); @@ -440,7 +436,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupHistoryLength.add(rbDays); - org.openide.awt.Mnemonics.setLocalizedText(rbDays, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbDays.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbDays, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbDays.text")); // NOI18N rbDays.setName(""); // NOI18N rbDays.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { @@ -449,7 +445,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupLabel.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(rbGroupLabel, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbGroupLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbGroupLabel, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbGroupLabel.text")); // NOI18N javax.swing.GroupLayout panelFilterLayout = new javax.swing.GroupLayout(panelFilter); panelFilter.setLayout(panelFilterLayout); @@ -481,8 +477,8 @@ public final class ReviewModeCasePanel extends JPanel { .addContainerGap()) ); - org.openide.awt.Mnemonics.setLocalizedText(bnShowLog, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnShowLog.text")); // NOI18N - bnShowLog.setToolTipText(org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnShowLog.toolTipText")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(bnShowLog, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnShowLog.text")); // NOI18N + bnShowLog.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnShowLog.toolTipText")); // NOI18N bnShowLog.setEnabled(false); bnShowLog.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -586,14 +582,14 @@ public final class ReviewModeCasePanel extends JPanel { if (pathToLog.toFile().exists()) { Desktop.getDesktop().edit(pathToLog.toFile()); } else { - JOptionPane.showMessageDialog(this, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.cannotFindLog"), - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.cannotFindLog"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE); } } catch (IOException ex) { logger.log(Level.SEVERE, String.format("Error attempting to open case auto ingest log file %s", pathToLog), ex); JOptionPane.showMessageDialog(this, - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.cannotOpenLog"), - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.cannotOpenLog"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.PLAIN_MESSAGE); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index bb55281227..8e80680b0c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -1983,16 +1983,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang String caseName = manifest.getCaseName(); SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()}); currentJob.setStage(AutoIngestJob.Stage.OPENING_CASE); - try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, 30, TimeUnit.MINUTES)) { + try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, 12, TimeUnit.HOURS)) { // RJCTODO: New lock type! if (null != caseLock) { try { Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, caseName); if (null != caseDirectoryPath) { Path metadataFilePath = caseDirectoryPath.resolve(manifest.getCaseName() + CaseMetadata.getFileExtension()); - Case.open(metadataFilePath.toString()); + Case.openCurrentCase(metadataFilePath.toString()); } else { caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); - Case.create(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); + Case.createCurrentCase(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); /* * Sleep a bit before releasing the lock to ensure * that the new case folder is visible on the diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index d5fa88092b..cddf2ed860 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -89,13 +89,6 @@ OpenIDE-Module-Long-Description=\ We make no guarantee that the API of this module will not change, so developers should be careful when relying on it. OpenIDE-Module-Name=Experimental OpenIDE-Module-Short-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. -ReviewModeCasePanel.bnRefresh.text=&Refresh -ReviewModeCasePanel.bnOpen.text=&Open -ReviewModeCasePanel.rbGroupLabel.text=Show Last 10: -ReviewModeCasePanel.rbDays.text=Days -ReviewModeCasePanel.rbWeeks.text=Weeks -ReviewModeCasePanel.rbMonths.text=Months -ReviewModeCasePanel.rbAllCases.text=Everything ReviewModeCasePanel.cannotOpenCase=Cannot Open Case ReviewModeCasePanel.casePathNotFound=Case path not found ReviewModeCasePanel.caseIsLocked=Single-user case is locked. @@ -170,12 +163,10 @@ CopyFilesPanel.ConfirmCopyAdd=exists. Do you really want to copy more files to t CopyFilesPanel.ConfirmCopyYes=Copy CopyFilesPanel.ConfirmCopyNo=Do not copy ConfirmationDialog.ConfirmUnlockHeader=Confirm Case Unlock -ReviewModeCasePanel.bnShowLog.text=&Show Log AutoIngestDashboard.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestDashboard.bnPrioritizeCase.text=Prioriti&ze Case AutoIngestDashboard.bnShowCaseLog.toolTipText=Display case log file for selected case AutoIngestDashboard.bnShowCaseLog.text=Show Case &Log -ReviewModeCasePanel.bnShowLog.toolTipText=Display case log file for selected case CopyFilesPanel.bnCancelPendingJob.text=Ca&ncel CopyFilesPanel.tbDestinationCase.text= CopyFilesPanel.cbThrottleNetwork.text=&Throttle Network @@ -298,3 +289,12 @@ AutoIngestDashboard.bnPrioritizeJob.toolTipText=Move this folder to the top of t AutoIngestDashboard.bnReprocessJob.text=Reprocess Job AutoIngestDashboard.bnPrioritizeFolder.label= AutoIngestDashboard.bnPrioritizeJob.actionCommand= +AutoIngestCasePanel.rbDays.text=Days +AutoIngestCasePanel.rbWeeks.text=Weeks +AutoIngestCasePanel.rbMonths.text=Months +AutoIngestCasePanel.rbAllCases.text=Everything +AutoIngestCasePanel.bnRefresh.text=&Refresh +AutoIngestCasePanel.bnOpen.text=&Open +AutoIngestCasePanel.bnShowLog.toolTipText=Display case log file for selected case +AutoIngestCasePanel.bnShowLog.text=&Show Log +AutoIngestCasePanel.rbGroupLabel.text=Show Last 10: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java deleted file mode 100644 index 2cae50ef1a..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2015 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.experimental.autoingest; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javax.swing.SwingUtilities; -import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileUtil; -import org.openide.util.actions.CallableSystemAction; -import org.sleuthkit.autopsy.casemodule.AddImageAction; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.casemodule.CaseNewAction; -import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; - -/** - * Handles opening, locking, and unlocking cases in review mode. Instances of - * this class are tightly coupled to the Autopsy "current case" concept and the - * Autopsy UI, and cases must be opened by code executing in the event - * dispatch thread (EDT). Because of the tight coupling to the UI, exception - * messages are deliberately user-friendly. - */ -final class ReviewModeCaseManager { - - /* - * Provides uniform exceptions with user-friendly error messages. - */ - final class ReviewModeCaseManagerException extends Exception { - - private static final long serialVersionUID = 1L; - - private ReviewModeCaseManagerException(String message) { - super(message); - } - - private ReviewModeCaseManagerException(String message, Throwable cause) { - super(message, cause); - } - - } - - private static final Logger logger = Logger.getLogger(ReviewModeCaseManager.class.getName()); - private static ReviewModeCaseManager instance; - private CoordinationService.Lock currentCaseLock; - - /** - * Gets the review mode case manager. - * - * @return The review mode case manager singleton. - */ - synchronized static ReviewModeCaseManager getInstance() { - if (instance == null) { - instance = new ReviewModeCaseManager(); - } - return instance; - } - - /** - * Constructs a review mode case manager to handles opening, locking, and - * unlocking cases in review mode. Instances of this class are tightly - * coupled to the Autopsy "current case" concept and the Autopsy UI, - * and cases must be opened by code executing in the event dispatch thread - * (EDT). Because of the tight coupling to the UI, exception messages are - * deliberately user-friendly. - * - */ - private ReviewModeCaseManager() { - /* - * Disable the new case action because review mode is only for looking - * at cases created by automated ingest. - */ - CallableSystemAction.get(CaseNewAction.class).setEnabled(false); - - /* - * Permanently delete the "Open Recent Cases" item in the "File" menu. - * This is quite drastic, as it also affects Autopsy standalone mode on - * this machine, but review mode is only for looking at cases created by - * automated ingest. - */ - FileObject root = FileUtil.getConfigRoot(); - FileObject openRecentCasesMenu = root.getFileObject("Menu/Case/OpenRecentCase"); - if (openRecentCasesMenu != null) { - try { - openRecentCasesMenu.delete(); - } catch (IOException ex) { - ReviewModeCaseManager.logger.log(Level.WARNING, "Unable to remove Open Recent Cases file menu item", ex); - } - } - } - - /* - * Gets a list of the cases in the top level case folder used by automated - * ingest. - */ - List getCases() { - List cases = new ArrayList<>(); - List caseFolders = PathUtils.findCaseFolders(Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder())); - for (Path caseFolderPath : caseFolders) { - cases.add(new AutoIngestCase(caseFolderPath)); - } - return cases; - } - - /** - * Attempts to open a case as the current case. Assumes it is called by code - * executing in the event dispatch thread (EDT). - * - * @param caseMetadataFilePath Path to the case metadata file. - * - * @throws ReviewModeCaseManagerException - */ - /* - * TODO (RC): With a little work, the lock acquisition/release could be done - * by a thread in a single thread executor, removing the "do it in the EDT" - * requirement - */ - synchronized void openCaseInEDT(Path caseMetadataFilePath) throws ReviewModeCaseManagerException { - try { - /* - * Open the case. - */ - Case.open(caseMetadataFilePath.toString()); - - /** - * Disable the add data source action in review mode. This has to be - * done here because Case.open() calls Case.doCaseChange() and the - * latter method enables the action. Since Case.doCaseChange() - * enables the menus on EDT by calling SwingUtilities.invokeLater(), - * we have to do the same thing here to maintain the order of - * execution. - */ - SwingUtilities.invokeLater(() -> { - CallableSystemAction.get(AddImageAction.class).setEnabled(false); - }); - - } catch (CaseActionException ex) { - throw new ReviewModeCaseManagerException(String.format("Could not open the case (%s), contract administrator", ex.getMessage()), ex); - } - } -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java index fbffcf7ece..ce76b1eea1 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java @@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.casemodule.CueBannerPanel; import org.sleuthkit.autopsy.casemodule.StartupWindowInterface; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboard; -import org.sleuthkit.autopsy.experimental.autoingest.ReviewModeCasePanel; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestCasePanel; /** * The default implementation of the Autopsy startup window @@ -47,7 +47,7 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa private static Dimension DIMENSIONS = new Dimension(750, 400); private static CueBannerPanel welcomeWindow; private static final long serialVersionUID = 1L; - private ReviewModeCasePanel caseManagementPanel = null; + private AutoIngestCasePanel caseManagementPanel = null; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); public StartupWindow() { @@ -120,7 +120,7 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa break; case REVIEW: this.setTitle(NbBundle.getMessage(StartupWindow.class, "StartupWindow.ReviewMode") + " (" + LOCAL_HOST_NAME + ")"); - caseManagementPanel = new ReviewModeCasePanel(this); + caseManagementPanel = new AutoIngestCasePanel(this); setIconImage(ImageUtilities.loadImage("org/sleuthkit/autopsy/experimental/images/frame.gif", false)); //NON-NLS add(caseManagementPanel); break; From 9e955cd4e4b63a3069cb5051dbaf8f529dc923a3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 19 Jan 2017 16:50:18 -0500 Subject: [PATCH 02/20] Interim checkin of case services infrastructure --- .../sleuthkit/autopsy/casemodule/Case.java | 24 ++++---- .../autopsy/core/RuntimeProperties.java | 59 +++++++++++-------- .../DirectoryTreeTopComponent.java | 2 +- .../autopsy/ingest/IngestManager.java | 26 +++----- .../autopsy/ingest/IngestMessagesToolbar.java | 2 +- .../modules/hashdatabase/HashDbManager.java | 4 +- .../hashdatabase/HashLookupSettings.java | 4 +- .../autopsy/report/ReportWizardAction.java | 2 +- .../CustomArtifactsCreatorIngestModule.java | 6 +- ...omArtifactsCreatorIngestModuleFactory.java | 6 +- .../TestAutopsyService.java | 27 ++++++--- .../autoingest/AutoIngestManager.java | 2 +- .../autoingest/CaseImportPanel.java | 4 +- .../imagegallery/ImageGalleryController.java | 4 +- .../keywordsearch/DropdownToolbar.java | 2 +- .../autopsy/keywordsearch/KeywordSearch.java | 4 +- .../keywordsearch/SolrSearchService.java | 2 +- 17 files changed, 93 insertions(+), 87 deletions(-) rename Core/src/org/sleuthkit/autopsy/{report/testfixtures => test}/CustomArtifactsCreatorIngestModule.java (98%) rename Core/src/org/sleuthkit/autopsy/{report/testfixtures => test}/CustomArtifactsCreatorIngestModuleFactory.java (90%) rename Core/src/org/sleuthkit/autopsy/{casemodule => test}/TestAutopsyService.java (70%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index bbc2da5dee..017cc32298 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -89,7 +89,6 @@ import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.modules.hashdatabase.SilentProgressIndicator; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; @@ -426,7 +425,10 @@ public class Case implements SleuthkitCase.ErrorObserver { */ @Messages({ "Case.creationException.illegalCaseName=Could not create case: case name contains illegal characters.", - "# {0} - exception message", "Case.creationException.couldNotCreateCase=Could not create case: {0}" + "# {0} - exception message", "Case.creationException.couldNotCreateCase=Could not create case: {0}", + "Case.creationMessage.acquiringLocks=Acquiring locks", + "Case.progressIndicatorTitle.creatingCase=Creating Case", + "Case.progressIndicatorCancelButton.cancelLabel=Cancel" }) public static void createCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { @@ -437,7 +439,7 @@ public class Case implements SleuthkitCase.ErrorObserver { try { caseName = sanitizeCaseName(caseDisplayName); } catch (IllegalCaseNameException ex) { - throw new CaseActionException(Bundle.Case_creationException_iilegalCaseName(), ex); + throw new CaseActionException(Bundle.Case_creationException_illegalCaseName(), ex); } LOGGER.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS @@ -447,12 +449,12 @@ public class Case implements SleuthkitCase.ErrorObserver { */ CancelButtonListener listener = new CancelButtonListener(); ProgressIndicator progressIndicator; - if (RuntimeProperties.coreComponentsAreActive()) { - progressIndicator = new ModalDialogProgressIndicator("Creating Case", new String[]{"Cancel"}, "Cancel", null, listener); // RJCTODO: bundle message + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_creatingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_cancelLabel()}, Bundle.Case_progressIndicatorCancelButton_cancelLabel(), null, listener); } else { progressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start("Acquiring locks"); // RJCTODO: Bundle message + progressIndicator.start(Bundle.Case_creationMessage_acquiringLocks()); /* * Creating a case is always done in the same non-UI thread that will be @@ -486,7 +488,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } return null; }); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { listener.setCaseActionFuture(future); ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); } @@ -509,7 +511,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } } finally { progressIndicator.finish(""); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); } } @@ -1443,7 +1445,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * Check for the presence of the UI and do things that can only be done * with user interaction. */ - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { /* * If the case database was upgraded for a new schema, notify the * user. @@ -1710,7 +1712,7 @@ public class Case implements SleuthkitCase.ErrorObserver { // clear the temp folder when the case is created / opened Case.clearTempFolder(); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { // enable these menus SwingUtilities.invokeLater(() -> { CallableSystemAction.get(AddImageAction.class @@ -1746,7 +1748,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } else { // case is closed SwingUtilities.invokeLater(() -> { - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { // close all top components first CoreComponentControl.closeCoreWindows(); diff --git a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java index 1ad74738dc..605fee4135 100644 --- a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java +++ b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java @@ -24,46 +24,55 @@ package org.sleuthkit.autopsy.core; */ public class RuntimeProperties { - private static boolean coreComponentsActive = true; - private static boolean coreComponentsActiveSet = false; + private static boolean runningWithGUI = true; + private static boolean runningWithGUIFlagHasBeenSet = false; /** - * Sets or unsets a flag indicating whether or not the core Autopsy UI - * components and user interactions with those components via menus, message - * boxes, NetBeans progress handles, etc., are enabled. - *

- * This flag exists as a mechanism to allow use of Autopsy as a platform - * with the core Autopsy user interface disabled, until such time as the - * user interface is made separable and optional. + * Sets or unsets a flag indicating whether or not the application is + * running with a GUI. The flag can only be set once per application + * innvocation. * - * @param coreComponentsActive True or false. + * @param runningWithGUI True or false. + * + * @throws RuntimePropertiesException if the flag has already been set. */ - public synchronized static void setCoreComponentsActive(boolean coreComponentsActive) { - if (!coreComponentsActiveSet) { - RuntimeProperties.coreComponentsActive = coreComponentsActive; - coreComponentsActiveSet = true; + public synchronized static void setRunningWithGUI(boolean runningWithGUI) throws RuntimePropertiesException { + if (!runningWithGUIFlagHasBeenSet) { + RuntimeProperties.runningWithGUI = runningWithGUI; + runningWithGUIFlagHasBeenSet = true; + } else { + throw new RuntimePropertiesException("The runningWithGUI flag has already been set and cannot be changed"); } } /** - * Gets a flag indicating whether or not the core Autopsy UI components and - * user interactions with those components via menus, message boxes, - * NetBeans progress handles, etc., are enabled. - *

- * This flag exists as a mechanism to allow use of Autopsy as a platform - * with the core Autopsy user interface disabled, until such time as the - * user interface is made separable and optional. + * Gets a flag indicating whether or not the application is running with a + * GUI. * * @return True or false. */ - public synchronized static boolean coreComponentsAreActive() { - return coreComponentsActive; + public synchronized static boolean runningWithGUI() { + return runningWithGUI; } - + /** * Private constructor to prevent creation of instances of this class. */ private RuntimeProperties() { - + } + + private final static class RuntimePropertiesException extends Exception { + + private static final long serialVersionUID = 1L; + + private RuntimePropertiesException(String message) { + super(message); + } + + private RuntimePropertiesException(String message, Throwable cause) { + super(message, cause); + } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 20ce2db7e8..56b5f417d5 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -536,7 +536,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat */ @Override public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { String changed = evt.getPropertyName(); if (changed.equals(Case.Events.CURRENT_CASE.toString())) { // changed current case // When a case is closed, the old value of this property is the diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index e1b2ae9163..60c664ec7c 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -343,7 +343,7 @@ public class IngestManager { logger.log(Level.SEVERE, "Service {0} is down! Cancelling all running ingest jobs", serviceDisplayName); //NON-NLS // display notification if running interactively - if (isIngestRunning() && RuntimeProperties.coreComponentsAreActive()) { + if (isIngestRunning() && RuntimeProperties.runningWithGUI()) { EventQueue.invokeLater(new Runnable() { @Override public void run() { @@ -398,18 +398,6 @@ public class IngestManager { clearIngestMessageBox(); } - /** - * Deprecated, use RuntimeProperties.setCoreComponentsActive instead. - * - * @param runInteractively True or false - * - * @deprecated - */ - @Deprecated - public synchronized void setRunInteractively(boolean runInteractively) { - RuntimeProperties.setCoreComponentsActive(runInteractively); - } - /** * Called by the custom installer for this package once the window system is * initialized, allowing the ingest manager to get the top component used to @@ -428,7 +416,7 @@ public class IngestManager { */ void postIngestMessage(IngestMessage message) { synchronized (this.ingestMessageBoxLock) { - if (ingestMessageBox != null && RuntimeProperties.coreComponentsAreActive()) { + if (ingestMessageBox != null && RuntimeProperties.runningWithGUI()) { if (message.getMessageType() != IngestMessage.MessageType.ERROR && message.getMessageType() != IngestMessage.MessageType.WARNING) { ingestMessageBox.displayMessage(message); } else { @@ -475,7 +463,7 @@ public class IngestManager { */ public void queueIngestJob(Collection dataSources, IngestJobSettings settings) { if (jobCreationIsEnabled) { - IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.coreComponentsAreActive()); + IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.runningWithGUI()); if (job.hasIngestPipeline()) { long taskId = nextThreadId.incrementAndGet(); Future task = startIngestJobsThreadPool.submit(new StartIngestJobTask(taskId, job)); @@ -497,7 +485,7 @@ public class IngestManager { */ public synchronized IngestJobStartResult beginIngestJob(Collection dataSources, IngestJobSettings settings) { if (this.jobCreationIsEnabled) { - IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.coreComponentsAreActive()); + IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.runningWithGUI()); if (job.hasIngestPipeline()) { return this.startIngestJob(job); // Start job } @@ -543,7 +531,7 @@ public class IngestManager { try { if (!servicesMonitor.getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).equals(ServicesMonitor.ServiceStatus.UP.toString())) { // display notification if running interactively - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { EventQueue.invokeLater(new Runnable() { @Override public void run() { @@ -582,7 +570,7 @@ public class IngestManager { logger.log(Level.SEVERE, String.format("Error starting %s ingest module for job %d", error.getModuleDisplayName(), job.getId()), error.getThrowable()); //NON-NLS } IngestManager.logger.log(Level.SEVERE, "Ingest job {0} could not be started", job.getId()); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { final StringBuilder message = new StringBuilder(); message.append(Bundle.IngestManager_startupErr_dlgMsg()).append("\n"); message.append(Bundle.IngestManager_startupErr_dlgSolution()).append("\n\n"); @@ -966,7 +954,7 @@ public class IngestManager { return null; } - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { final String displayName = NbBundle.getMessage(this.getClass(), "IngestManager.StartIngestJobsTask.run.displayName"); this.progress = ProgressHandle.createHandle(displayName, new Cancellable() { @Override diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java index fac3307653..20b791c64d 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java @@ -130,7 +130,7 @@ class IngestMessagesToolbar extends javax.swing.JPanel { Case.addPropertyChangeListener((PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { - setEnabled(evt.getNewValue() != null && RuntimeProperties.coreComponentsAreActive()); + setEnabled(evt.getNewValue() != null && RuntimeProperties.runningWithGUI()); } }); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index aea5ae1ce2..01af5addc1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -492,7 +492,7 @@ public class HashDbManager implements PropertyChangeListener { the database from HashLookupSettings and the user may not know about this because the dialogs are not being displayed. The next time user starts Autopsy, HashDB will load without errors and the user may think that the problem was solved.*/ - if (!allDatabasesLoadedCorrectly && RuntimeProperties.coreComponentsAreActive()) { + if (!allDatabasesLoadedCorrectly && RuntimeProperties.runningWithGUI()) { try { HashLookupSettings.writeSettings(new HashLookupSettings(this.knownHashSets, this.knownBadHashSets)); allDatabasesLoadedCorrectly = true; @@ -512,7 +512,7 @@ public class HashDbManager implements PropertyChangeListener { // Give the user an opportunity to find the desired file. String newPath = null; - if (RuntimeProperties.coreComponentsAreActive() && + if (RuntimeProperties.runningWithGUI() && JOptionPane.showConfirmDialog(null, NbBundle.getMessage(this.getClass(), "HashDbManager.dlgMsg.dbNotFoundAtLoc", hashSetName, configuredPath), diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java index ae52f86219..9adc3a4df0 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java @@ -203,7 +203,7 @@ final class HashLookupSettings implements Serializable { newHashSetName = hashSetName + suffix; } while (hashSetNames.contains(newHashSetName)); logger.log(Level.INFO, "Duplicate hash set name " + hashSetName + " found. Replacing with " + newHashSetName + "."); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.replacingDuplicateHashsetNameMsg", @@ -269,7 +269,7 @@ final class HashLookupSettings implements Serializable { try { FileUtils.copyFile(new File(configFilePath), new File(backupFilePath)); logger.log(Level.INFO, "Updated the schema, backup saved at: " + backupFilePath); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.savedBackupOfOldConfigMsg", diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 5e32ce5016..75c8f7c205 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -84,7 +84,7 @@ public final class ReportWizardAction extends CallableSystemAction implements Pr Case.addPropertyChangeListener((PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { Case newCase = (Case) evt.getNewValue(); - setEnabled(newCase != null && RuntimeProperties.coreComponentsAreActive()); + setEnabled(newCase != null && RuntimeProperties.runningWithGUI()); } }); diff --git a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModule.java b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModule.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModule.java rename to Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModule.java index b3dafaa1d6..975bc1e11c 100644 --- a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.report.testfixtures; +package org.sleuthkit.autopsy.test; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import javax.xml.bind.DatatypeConverter; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; @@ -31,7 +32,6 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; -import org.openide.util.NbBundle; /** * A file ingest module that associates custom artifacts and attributes with diff --git a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModuleFactory.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModuleFactory.java rename to Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModuleFactory.java index 766a3260dd..0b0eb7d4d2 100644 --- a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModuleFactory.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.report.testfixtures; +package org.sleuthkit.autopsy.test; -import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.ingest.FileIngestModule; -import org.sleuthkit.autopsy.ingest.IngestModuleFactory; import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/TestAutopsyService.java b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java similarity index 70% rename from Core/src/org/sleuthkit/autopsy/casemodule/TestAutopsyService.java rename to Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java index 488c16e9a5..d0493384e6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/TestAutopsyService.java +++ b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java @@ -16,11 +16,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.test; +import java.util.logging.Level; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; +import org.sleuthkit.autopsy.coreutils.Logger; /** * An implementation of the Autopsy service interface used for test purposes. @@ -28,16 +30,18 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; @ServiceProvider(service = AutopsyService.class) public class TestAutopsyService implements AutopsyService { + private static final Logger LOGGER = Logger.getLogger(TestAutopsyService.class.getName()); + @Override public String getServiceName() { - return "TestService"; + return "Test Autopsy Service"; } @Override public void openCaseResources(CaseContext context) throws AutopsyServiceException { ProgressIndicator progressIndicator = context.getProgressIndicator(); try { - progressIndicator.start("Doing first task", 100); + progressIndicator.start("Doing first task...", 100); Thread.sleep(1000L); progressIndicator.progress(10); Thread.sleep(1000L); @@ -58,14 +62,19 @@ public class TestAutopsyService implements AutopsyService { progressIndicator.progress(10); Thread.sleep(1000L); progressIndicator.progress(10); - progressIndicator.finish("First task completed"); - progressIndicator.start("Doing second task"); + progressIndicator.finish("First task completed."); + progressIndicator.start("Doing second task..."); Thread.sleep(10000L); + progressIndicator.finish("Second task completed."); } catch (InterruptedException ex) { - progressIndicator.finish("Cancelling..."); - try { - Thread.sleep(1000L); - } catch (InterruptedException ex1) { + LOGGER.log(Level.INFO, "Autopsy Test Service caught interrupt while working"); + if (context.cancelRequested()) { + progressIndicator.finish("Cancelling..."); + try { + Thread.sleep(1000L); + } catch (InterruptedException ex1) { + LOGGER.log(Level.INFO, "Autopsy Test Service caught interrupt while working"); + } } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 8e80680b0c..300f787a53 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -249,7 +249,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang jobProcessingTaskFuture = jobProcessingExecutor.submit(jobProcessingTask); jobStatusPublishingExecutor.scheduleAtFixedRate(new PeriodicJobStatusEventTask(), JOB_STATUS_EVENT_INTERVAL_SECONDS, JOB_STATUS_EVENT_INTERVAL_SECONDS, TimeUnit.SECONDS); eventPublisher.addSubscriber(EVENT_LIST, instance); - RuntimeProperties.setCoreComponentsActive(false); + RuntimeProperties.setRunningWithGUI(false); state = State.RUNNING; errorState = ErrorState.NONE; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java index d1ee4c3865..34c361b3c6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java @@ -108,7 +108,7 @@ public class CaseImportPanel extends javax.swing.JPanel implements ImportDoneCal if (!UserPreferences.getIsMultiUserModeEnabled()) { tbOops.setText(MULTI_USER_SETTINGS_MUST_BE_ENABLED); return; - } else if (RuntimeProperties.coreComponentsAreActive()) { + } else if (RuntimeProperties.runningWithGUI()) { tbOops.setText(AIM_MUST_BE_ENABLED); return; } else { @@ -674,7 +674,7 @@ public class CaseImportPanel extends javax.swing.JPanel implements ImportDoneCal private void enableStartButton() { if (UserPreferences.getIsMultiUserModeEnabled() && AutoIngestUserPreferences.getJoinAutoModeCluster() - && (! RuntimeProperties.coreComponentsAreActive()) + && (! RuntimeProperties.runningWithGUI()) && !tbCaseSource.getText().isEmpty() && !tbCaseDestination.getText().isEmpty() && canTalkToDb == true diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 55fb260bbd..f85b5a4050 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -917,7 +917,7 @@ public final class ImageGalleryController implements Executor { @Override public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.coreComponentsAreActive() == false) { + if (RuntimeProperties.runningWithGUI() == false) { /* * Running in "headless" mode, no need to process any events. * This cannot be done earlier because the switch to core @@ -978,7 +978,7 @@ public final class ImageGalleryController implements Executor { @Override public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.coreComponentsAreActive() == false) { + if (RuntimeProperties.runningWithGUI() == false) { /* * Running in "headless" mode, no need to process any events. * This cannot be done earlier because the switch to core diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java index 68c482edd4..90835907ff 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java @@ -255,7 +255,7 @@ class DropdownToolbar extends javax.swing.JPanel { String changed = evt.getPropertyName(); if (changed.equals(Case.Events.CURRENT_CASE.toString())) { dropPanel.clearSearchBox(); - setFields(null != evt.getNewValue() && RuntimeProperties.coreComponentsAreActive()); + setFields(null != evt.getNewValue() && RuntimeProperties.runningWithGUI()); } else if (changed.equals(Server.CORE_EVT)) { final Server.CORE_EVT_STATES state = (Server.CORE_EVT_STATES) evt.getNewValue(); switch (state) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java index f491d1696e..c01a29032b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java @@ -136,7 +136,7 @@ public class KeywordSearch { server.closeCore(); } catch (Exception ex) { logger.log(Level.SEVERE, String.format("Failed to close core for %s", closedCase.getCaseDirectory()), ex); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { MessageNotifyUtil.Notify.error(NbBundle.getMessage(KeywordSearch.class, "KeywordSearch.closeCore.notification.msg"), ex.getMessage()); } } @@ -151,7 +151,7 @@ public class KeywordSearch { server.openCoreForCase(openedCase); } catch (Exception ex) { logger.log(Level.SEVERE, String.format("Failed to open or create core for %s", openedCase.getCaseDirectory()), ex); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { MessageNotifyUtil.Notify.error(NbBundle.getMessage(KeywordSearch.class, "KeywordSearch.openCore.notification.msg"), ex.getMessage()); } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index d2f4df78d0..56072eb7d3 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -167,7 +167,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService // ELTODO not sure what to do when there are multiple old indexes. grab the first one? String oldIndexDir = indexDirs.get(0); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { //pop up a message box to indicate the restrictions on adding additional //text and performing regex searches and give the user the option to decline the upgrade if (!KeywordSearchUtil.displayConfirmDialog(NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexUpgradeDialog.title"), From b289e8c75b30b976a3ca90c91f1054c124e6d5ca Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 20 Jan 2017 10:02:45 -0500 Subject: [PATCH 03/20] Case class refinements --- .../sleuthkit/autopsy/casemodule/Case.java | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 8e32aa52df..5c4cd244c8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -426,10 +426,10 @@ public class Case implements SleuthkitCase.ErrorObserver { @Messages({ "Case.creationException.illegalCaseName=Could not create case: case name contains illegal characters.", "# {0} - exception message", "Case.creationException.couldNotCreateCase=Could not create case: {0}", - "Case.creationMessage.acquiringLocks=Acquiring locks", "Case.progressIndicatorTitle.creatingCase=Creating Case", - "Case.progressIndicatorCancelButton.cancelLabel=Cancel" - }) + "Case.progressIndicatorCancelButton.cancelLabel=Cancel", + "Case.progressMessage.preparingToCreateCase=Preparing to create case...", + "Case.progressMessage.acquiringLocks=Acquiring locks...",}) public static void createCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { /* @@ -454,7 +454,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } else { progressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start(Bundle.Case_creationMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_preparingToCreateCase()); /* * Creating a case is always done in the same non-UI thread that will be @@ -476,7 +476,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * exclusive case resources lock to allow only one node at a * time to create/open/upgrade case resources. */ - progressIndicator.start("Acquiring locks"); // RJCTODO: Bundle message + progressIndicator.start(Bundle.Case_creationMessage_acquiringLocks()); try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) { assert (null != nameLock); acquireSharedCaseDirLock(caseDir); @@ -505,7 +505,9 @@ public class Case implements SleuthkitCase.ErrorObserver { * CaseActionException with a user-friendly error message * suitable for substitution in the error message below. * - * RJCTODO: Add TODO comment referencing JIRA + * TODO (JIRA-2206): Update Case API to throw more specific + * exceptions so that clients can display error messages based + * on exception type rather than having localized log messages. */ throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(ex.getCause().getMessage()), ex); } @@ -528,7 +530,9 @@ public class Case implements SleuthkitCase.ErrorObserver { * exception. */ @Messages({ - "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}" + "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}", + "Case.progressIndicatorTitle.openingCase=Opening Case", + "Case.progressMessage.preparingToOpenCase=Preparing to open case...", }) public static void openCurrentCase(String caseMetadataFilePath) throws CaseActionException { LOGGER.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS @@ -538,6 +542,19 @@ public class Case implements SleuthkitCase.ErrorObserver { } CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); + /* + * Set up either a visual progress indicator or a logging progress + * indicator, depending on whether a GUI is present. + */ + CancelButtonListener listener = new CancelButtonListener(); + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_openingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_cancelLabel()}, Bundle.Case_progressIndicatorCancelButton_cancelLabel(), null, listener); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + progressIndicator.start(Bundle.Case_progressMessage_preparingToOpenCase()); + /* * Creating a case is always done in the same non-UI thread that * will be used later to close the case. If the case is a multi-user From af0d333261dfb7cf36906a5da1755ec4adc8443d Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 20 Jan 2017 17:48:22 -0500 Subject: [PATCH 04/20] Minor case improvements --- .../sleuthkit/autopsy/casemodule/Case.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 5c4cd244c8..015a275553 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -459,8 +459,9 @@ public class Case implements SleuthkitCase.ErrorObserver { /* * Creating a case is always done in the same non-UI thread that will be * used later to close the case. If the case is a multi-user case, this - * ensures that case directory lock is released in the same thread in - * which it was acquired, as is required by the coordination service. + * ensures that case directory lock that is held as long as the case is + * open is released in the same thread in which it was acquired, as is + * required by the coordination service. */ try { Future future = getSingleThreadedExecutor().submit(() -> { @@ -476,7 +477,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * exclusive case resources lock to allow only one node at a * time to create/open/upgrade case resources. */ - progressIndicator.start(Bundle.Case_creationMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) { assert (null != nameLock); acquireSharedCaseDirLock(caseDir); @@ -531,9 +532,8 @@ public class Case implements SleuthkitCase.ErrorObserver { */ @Messages({ "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}", - "Case.progressIndicatorTitle.openingCase=Opening Case", - "Case.progressMessage.preparingToOpenCase=Preparing to open case...", - }) + "Case.progressIndicatorTitle.openingCase=Opening Case", + "Case.progressMessage.preparingToOpenCase=Preparing to open case...",}) public static void openCurrentCase(String caseMetadataFilePath) throws CaseActionException { LOGGER.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS try { @@ -556,11 +556,11 @@ public class Case implements SleuthkitCase.ErrorObserver { progressIndicator.start(Bundle.Case_progressMessage_preparingToOpenCase()); /* - * Creating a case is always done in the same non-UI thread that - * will be used later to close the case. If the case is a multi-user - * case, this ensures that case directory lock is released in the - * same thread in which it was acquired, as is required by the - * coordination service. + * Opening a case is always done in the same non-UI thread that will + * be used later to close the case. If the case is a multi-user + * case, this ensures that case directory lock that is held as long + * as the case is open is released in the same thread in which it + * was acquired, as is required by the coordination service. */ CaseType caseType = metadata.getCaseType(); String caseName = metadata.getCaseName(); @@ -577,6 +577,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * to allow only one node at a time to * create/open/upgrade case resources. */ + progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); acquireSharedCaseDirLock(metadata.getCaseDirectory()); try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseName())) { assert (null != resourcesLock); @@ -585,6 +586,10 @@ public class Case implements SleuthkitCase.ErrorObserver { } return null; }); + if (RuntimeProperties.runningWithGUI()) { + listener.setCaseActionFuture(future); + ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); + } future.get(); } catch (InterruptedException | ExecutionException ex) { if (CaseType.SINGLE_USER_CASE == caseType) { @@ -602,23 +607,17 @@ public class Case implements SleuthkitCase.ErrorObserver { } else { throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); } + } finally { + progressIndicator.finish(""); + if (RuntimeProperties.runningWithGUI()) { + ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); + } } - } catch (CaseMetadataException ex) { throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Failed to access case metadata"), ex); } } - /** - * Checks if case is currently open. - * - * @return True or false. - */ - // RJCTODO: Deprecate this, it cannot work - public static boolean isCaseOpen() { - return currentCase != null; - } - /** * Gets the current case, if there is one. * @@ -1973,6 +1972,17 @@ public class Case implements SleuthkitCase.ErrorObserver { openCurrentCase(caseMetadataFilePath); } + /** + * Checks if case is currently open. + * + * @return True or false. + * @deprecated Do not use, this method is not relaible. + */ + @Deprecated + public static boolean isCaseOpen() { + return currentCase != null; + } + /** * Invokes the startup dialog window. * From b3c1a2488048db875a18dec443a9cc7ad875ed66 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Sat, 21 Jan 2017 20:18:56 -0500 Subject: [PATCH 05/20] Refactoring and bug fixing for Case infrastructure --- .../autopsy/actions/OpenLogFolderAction.java | 55 +- .../actions/OpenOutputFolderAction.java | 59 +- .../ShowIngestProgressSnapshotAction.java | 7 +- .../sleuthkit/autopsy/casemodule/Case.java | 1744 +++++++++-------- .../autopsy/casemodule/CaseDeleteAction.java | 156 -- .../autopsy/casemodule/CaseOpenAction.java | 9 +- .../casemodule/CasePropertiesForm.java | 2 +- .../casemodule/DeleteCurrentCaseAction.java | 103 + .../casemodule/NewCaseWizardAction.java | 2 - .../casemodule/OpenRecentCasePanel.java | 9 +- .../autopsy/casemodule/RecentItems.java | 16 +- .../casemodule/SingleUserCaseConverter.java | 11 +- ...tData.java => SleuthkitErrorReporter.java} | 47 +- .../DataContentTopComponent.java | 66 +- .../corecomponents/DataResultPanel.java | 21 +- .../DataResultTopComponent.java | 13 +- .../autopsy/corecomponents/FXVideoPanel.java | 4 +- .../corecomponents/MediaViewImagePanel.java | 24 +- .../diagnostics/PerformancePanelAction.java | 9 +- .../DirectoryTreeTopComponent.java | 4 +- .../autopsy/filesearch/FileSearchAction.java | 20 +- .../autopsy/ingest/IngestManager.java | 4 +- .../autopsy/ingest/RunIngestAction.java | 7 +- .../hashdatabase/HashDbPanelSearchAction.java | 11 +- .../keywordsearch/SolrSearchService.java | 40 +- 25 files changed, 1313 insertions(+), 1130 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java rename Core/src/org/sleuthkit/autopsy/casemodule/{IntervalErrorReportData.java => SleuthkitErrorReporter.java} (65%) diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java index bbadf4291d..75721867ab 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2014-2015 Basis Technology Corp. + * + * Copyright 2014-2017 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,9 +18,9 @@ */ package org.sleuthkit.autopsy.actions; +import java.awt.Desktop; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.Desktop; import java.io.File; import java.io.IOException; import java.util.logging.Level; @@ -35,37 +35,46 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; /** - * Action in menu to open the folder containing the log files + * Action to open the log subdirectory for the currently open case, or the log + * subdirectory of the user directory if there is no current case. */ -@ActionRegistration( - displayName = "#CTL_OpenLogFolder", iconInMenu = true) +@ActionRegistration(displayName = "#CTL_OpenLogFolder", iconInMenu = true) @ActionReference(path = "Menu/Help", position = 1750) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenLogFolderAction", category = "Help") public final class OpenLogFolderAction implements ActionListener { - private static final Logger logger = Logger.getLogger(OpenLogFolderAction.class.getName()); + private static final Logger LOGGER = Logger.getLogger(OpenLogFolderAction.class.getName()); @Override public void actionPerformed(ActionEvent e) { + File logDir; + try { + Case currentCase = Case.getCurrentCase(); + logDir = new File(currentCase.getLogDirectoryPath()); + } catch (IllegalStateException ex) { + /* + * No open case. + */ + logDir = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "var" + File.separator + "log"); + } + try { - File logDir; - if (Case.isCaseOpen()) { - logDir = new File(Case.getCurrentCase().getLogDirectoryPath()); - } else { - logDir = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "var" + File.separator + "log"); - } if (logDir.exists() == false) { - NotifyDescriptor d - = new NotifyDescriptor.Message( - NbBundle.getMessage(this.getClass(), "OpenLogFolder.error1", logDir.getAbsolutePath()), - NotifyDescriptor.ERROR_MESSAGE); - DialogDisplayer.getDefault().notify(d); + LOGGER.log(Level.SEVERE, String.format("The log subdirectory %s does not exist", logDir)); + NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message( + NbBundle.getMessage(this.getClass(), "OpenLogFolder.error1", logDir.getAbsolutePath()), + NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDescriptor); } else { Desktop.getDesktop().open(logDir); } } catch (IOException ex) { - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "OpenLogFolder.CouldNotOpenLogFolder"), ex); //NON-NLS - + LOGGER.log(Level.SEVERE, String.format("Could not open log directory %s", logDir), ex); + NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message( + NbBundle.getMessage(this.getClass(), "OpenLogFolder.CouldNotOpenLogFolder", logDir.getAbsolutePath()), + NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDescriptor); } } + } diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index d05baf425d..b57e1b607f 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2015 Basis Technology Corp. + * + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -35,43 +35,51 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; /** - * Action in menu to open the folder containing the output files + * Action to open the subdirectory of the current case that contains the output + * files. */ -@ActionRegistration( - displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy=true) +@ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = true) @ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help") public final class OpenOutputFolderAction extends CallableSystemAction { - private static final Logger logger = Logger.getLogger(OpenOutputFolderAction.class.getName()); + private static final Logger LOGGER = Logger.getLogger(OpenOutputFolderAction.class.getName()); + private static final long serialVersionUID = 1L; @Override public void performAction() { - + File outputDir; try { - File outputDir; - if (Case.isCaseOpen()) { - outputDir = new File(Case.getCurrentCase().getOutputDirectory()); - if (outputDir.exists() == false) { - NotifyDescriptor d - = new NotifyDescriptor.Message(NbBundle.getMessage(this.getClass(), - "OpenOutputFolder.error1", outputDir.getAbsolutePath()), - NotifyDescriptor.ERROR_MESSAGE); - DialogDisplayer.getDefault().notify(d); - } else { + Case currentCase = Case.getCurrentCase(); + outputDir = new File(currentCase.getOutputDirectory()); + if (outputDir.exists()) { + try { Desktop.getDesktop().open(outputDir); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to open output folder %s", outputDir), ex); //NON-NLS + NotifyDescriptor descriptor = new NotifyDescriptor.Message( + NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder", outputDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(descriptor); } } else { - JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.noCaseOpen")); + NotifyDescriptor descriptor = new NotifyDescriptor.Message( + NbBundle.getMessage(this.getClass(), "OpenOutputFolder.error1", outputDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(descriptor); } - } catch (IOException ex) { - logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder"), ex); //NON-NLS + } catch (IllegalStateException ex) { + LOGGER.log(Level.SEVERE, "OpenOutputFolderAction enabled with no current case", ex); //NON-NLS + JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.noCaseOpen")); } } @Override public boolean isEnabled() { - return Case.isCaseOpen(); + try { + Case.getCurrentCase(); + return true; + } catch (IllegalStateException ex) { + return false; + } } @Override @@ -81,8 +89,9 @@ public final class OpenOutputFolderAction extends CallableSystemAction { @Override public boolean asynchronous() { - return false; // run on edt + return false; } + @Override public String getName() { return NbBundle.getMessage(OpenOutputFolderAction.class, "CTL_OpenOutputFolder"); diff --git a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java index 1fa497dc2c..842bcabdbf 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java @@ -60,7 +60,12 @@ public final class ShowIngestProgressSnapshotAction extends CallableSystemAction @Override public boolean isEnabled() { - return Case.isCaseOpen(); + try { + Case.getCurrentCase(); + return true; + } catch (IllegalStateException ex) { + return false; + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 1ea36980e9..37e81f94eb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.Cursor; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -26,9 +25,13 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; @@ -48,8 +51,10 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.concurrent.GuardedBy; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -81,7 +86,6 @@ import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.events.AutopsyEvent; @@ -91,6 +95,7 @@ import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.Image; @@ -101,10 +106,13 @@ import org.sleuthkit.datamodel.TskCoreException; /** * An Autopsy case. Currently, only one case at a time may be open. */ -public class Case implements SleuthkitCase.ErrorObserver { +public class Case { + /* + * Constants. + */ private static final int NAME_LOCK_TIMOUT_HOURS = 12; - private static final int DIR_LOCK_TIMOUT_HOURS = 12; + private static final int SHARED_DIR_LOCK_TIMOUT_HOURS = 12; private static final int RESOURCE_LOCK_TIMOUT_HOURS = 12; private static final int MAX_SANITIZED_CASE_NAME_LEN = 47; private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS @@ -114,19 +122,63 @@ public class Case implements SleuthkitCase.ErrorObserver { private static final String REPORTS_FOLDER = "Reports"; //NON-NLS private static final String TEMP_FOLDER = "Temp"; //NON-NLS private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; - static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS // RJCTODO + private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS // RJCTODO private static final Logger LOGGER = Logger.getLogger(Case.class.getName()); - private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); + + /* + * The following group of fields is state associated with the "current case" + * concept. This state is managed by a set of static methods that could be + * refactored into a case manager class, which would be a step towards + * supporting multiple open cases. RJCTODO: Write a story about this + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties item set + * by Installers. + */ + @GuardedBy("Case.class") private static String appName; + /* + * The one and only open case. + */ + @GuardedBy("Case.class") private static Case currentCase; - private static CoordinationService.Lock currentCaseDirLock; + /* + * A coordination service lock on the case directory of the one and only + * open case. Used to prevent deletion of a multi-user case by this node if + * it is open in another node. + */ + @GuardedBy("Case.class") + private static CoordinationService.Lock currentCaseDirLock; // RJCTODO: Move into case + /* + * A single-threaded executor used to guarantee that the case directory lock + * for the current case is acquired and released in the saem thread, as + * required by the coordination service. + */ + @GuardedBy("Case.class") private static ExecutorService singleThreadedExecutor; - private final CaseMetadata caseMetadata; - private final SleuthkitCase db; - private final Services services; - private CollaborationMonitor collaborationMonitor; + /* + * The collaboration monitor for the current case. It is specific to the + * current case because it uses an event channel with a name derived from + * the name of the current case. + */ + @GuardedBy("Case.class") + private static CollaborationMonitor collaborationMonitor; // RJCTODO: Move into case + /* + * The publisher for case events, both locally and, if the case is a + * multi-user case, to other nodes. This is part of the state for the + * current case because it opens and closes an event channel with a name + * derived from the name of the current case. + */ + @GuardedBy("Case.class") + private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); + + /* + * Case instance data. + */ + private CaseMetadata caseMetadata; + private SleuthkitCase caseDb; + private SleuthkitErrorReporter sleuthkitErrorReporter; + private Services services; private boolean hasDataSources; - private volatile IntervalErrorReportData tskErrorReporter; /** * An enumeration of case types. @@ -373,39 +425,25 @@ public class Case implements SleuthkitCase.ErrorObserver { } /** - * Gets the application name. + * Checks if a case display name is valid, i.e., does not include any + * characters that cannot be used in file names. * - * @return The application name. - */ - // RJCTODO: Comment on funky way this works, deprecate and make it return Autopsy, make a private method that does funkiness - public static String getAppName() { - if ((appName == null) || appName.isEmpty()) { - appName = WindowManager.getDefault().getMainWindow().getTitle(); - } - return appName; - } - - /** - * Checks if a case name is valid, i.e., does not include any characters - * that cannot be used in file names. + * + * RJCTODO: Not really needed any more with display name concept; what + * happens in auto ingest? Make sure these things are in sanitize...etc. * * @param caseName The case name. * * @return True or false. */ public static boolean isValidName(String caseName) { - /* - * TODO(JIRA-2221): This should incorporate the validity checks of - * sanitizeCaseName. RJCTODO: This is no longer necessary, kill off this - * story - */ return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":") || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"") || caseName.contains("<") || caseName.contains(">") || caseName.contains("|")); } /** - * Creates a new Autopsy case and makes it the current case. + * Creates a new case and makes it the current case. * * @param caseDir The full path of the case directory. The directory * will be created if it doesn't already exist; if it @@ -430,10 +468,29 @@ public class Case implements SleuthkitCase.ErrorObserver { "Case.progressIndicatorCancelButton.cancelLabel=Cancel", "Case.progressMessage.preparingToCreateCase=Preparing to create case...", "Case.progressMessage.acquiringLocks=Acquiring locks...",}) - public static void createCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + public synchronized static void createCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + /* + * If running with the desktop GUI, this needs to be done before any + * cases are created or opened so that the application name can be + * captured before a case name is added to the title. + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties item + * set by an Installer. + */ + if (RuntimeProperties.runningWithGUI()) { + getAppNameFromMainWindow(); + } /* - * Clean up the display name for the case to make a suitable case name. + * If another case is open, close it. + */ + if (null != currentCase) { + closeCurrentCase(); + } + + /* + * Clean up the display name for the case to make a suitable immutable + * case name. */ String caseName; try { @@ -444,8 +501,8 @@ public class Case implements SleuthkitCase.ErrorObserver { LOGGER.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS /* - * Set up either a visual progress indicator or a logging progress - * indicator, depending on whether a GUI is present. + * Set up either a GUI progress indicator or a logging progress + * indicator. */ CancelButtonListener listener = new CancelButtonListener(); ProgressIndicator progressIndicator; @@ -464,36 +521,47 @@ public class Case implements SleuthkitCase.ErrorObserver { * required by the coordination service. */ try { - Future future = getSingleThreadedExecutor().submit(() -> { + Future future = getSingleThreadedExecutor().submit(() -> { + Case newCase = new Case(); if (CaseType.SINGLE_USER_CASE == caseType) { - createCase(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType); + newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator); } else { /* * First, acquire an exclusive case name lock to prevent two - * nodes from creating the same case at the same time. Next, - * acquire a shared case directory lock that will be held as - * long as this node has this case open, in order to prevent - * deletion of the case by another node. Finally, acquire an - * exclusive case resources lock to allow only one node at a - * time to create/open/upgrade case resources. + * nodes from creating the same case at the same time. */ progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) { assert (null != nameLock); + /* + * Next, acquire a shared case directory lock that will + * be held as long as this node has this case open. This + * will prevent deletion of the case by another node. + */ acquireSharedCaseDirLock(caseDir); + /* + * Finally, acquire an exclusive case resources lock to + * ensure only one node at a time can + * create/open/upgrade the case resources. + */ try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseName)) { assert (null != resourcesLock); - createCase(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType); + newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator); } } } - return null; + return newCase; }); if (RuntimeProperties.runningWithGUI()) { listener.setCaseActionFuture(future); ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); } - future.get(); + currentCase = future.get(); + LOGGER.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseOpened(); + } + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); } catch (InterruptedException | ExecutionException ex) { if (CaseType.SINGLE_USER_CASE == caseType) { releaseSharedCaseDirLock(caseName); @@ -501,19 +569,13 @@ public class Case implements SleuthkitCase.ErrorObserver { if (ex instanceof InterruptedException) { throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase("Interrupted during locks acquisition"), ex); //RJCTODO } else { - /* - * The methods called within the task MUST throw a - * CaseActionException with a user-friendly error message - * suitable for substitution in the error message below. - * - * TODO (JIRA-2206): Update Case API to throw more specific - * exceptions so that clients can display error messages based - * on exception type rather than having localized log messages. - */ throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(ex.getCause().getMessage()), ex); } + } catch (Exception ex) { + // RJCTODO: Remove, why not catching NPE's? + ex.getMessage(); } finally { - progressIndicator.finish(""); + progressIndicator.finish(""); // RJCTODO: Is this right message? if (RuntimeProperties.runningWithGUI()) { ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); } @@ -533,18 +595,36 @@ public class Case implements SleuthkitCase.ErrorObserver { @Messages({ "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}", "Case.progressIndicatorTitle.openingCase=Opening Case", - "Case.progressMessage.preparingToOpenCase=Preparing to open case...",}) - public static void openCurrentCase(String caseMetadataFilePath) throws CaseActionException { + "Case.progressMessage.preparingToOpenCase=Preparing to open case...", + "Case.creationException.couldNotCreateMetadataFile=Could not create case: failed to create case metadata file." + }) + public synchronized static void openCurrentCase(String caseMetadataFilePath) throws CaseActionException { + /* + * If running with the desktop GUI, this needs to be done before any + * cases are created or opened so that the application name can be + * captured before a case name is added to the title. + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties item + * set by an Installer. + */ + if (RuntimeProperties.runningWithGUI()) { + getAppNameFromMainWindow(); + } + + /* + * If another case is open, close it. + */ + if (null != currentCase) { + closeCurrentCase(); + } + LOGGER.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS try { - if (!caseMetadataFilePath.endsWith(CaseMetadata.getFileExtension())) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CaseMetadata.getFileExtension())); - } CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); /* - * Set up either a visual progress indicator or a logging progress - * indicator, depending on whether a GUI is present. + * Set up either a GUI progress indicator or a logging progress + * indicator. */ CancelButtonListener listener = new CancelButtonListener(); ProgressIndicator progressIndicator; @@ -565,50 +645,53 @@ public class Case implements SleuthkitCase.ErrorObserver { CaseType caseType = metadata.getCaseType(); String caseName = metadata.getCaseName(); try { - Future future = getSingleThreadedExecutor().submit(() -> { + Future future = getSingleThreadedExecutor().submit(() -> { + Case openedCase = new Case(); if (CaseType.SINGLE_USER_CASE == caseType) { - openCase(metadata); + openedCase.open(metadata, progressIndicator); } else { /* * First, acquire a shared case directory lock that will * be held as long as this node has this case open, in * order to prevent deletion of the case by another - * node. Next, acquire an exclusive case resources lock - * to allow only one node at a time to - * create/open/upgrade case resources. + * node. */ progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); acquireSharedCaseDirLock(metadata.getCaseDirectory()); + /* + * Next, acquire an exclusive case resources lock to + * ensure only one node at a time can + * create/open/upgrade case resources. + */ try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseName())) { assert (null != resourcesLock); - openCase(metadata); + openedCase.open(metadata, progressIndicator); } } - return null; + return openedCase; }); if (RuntimeProperties.runningWithGUI()) { listener.setCaseActionFuture(future); ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); } future.get(); + currentCase = future.get(); + LOGGER.log(Level.INFO, "Opened case {0} in directory = {1}", new Object[]{metadata.getCaseName(), metadata.getCaseDirectory()}); //NON-NLS + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseOpened(); + } + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); } catch (InterruptedException | ExecutionException ex) { if (CaseType.SINGLE_USER_CASE == caseType) { releaseSharedCaseDirLock(caseName); } if (ex instanceof ExecutionException) { - /* - * The methods called within the task MUST throw a - * CaseActionException with a user-friendly error message - * suitable for substitution in the error message below. - * - * RJCTODO: Add TODO comment referencing JIRA - */ throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); } else { throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); } } finally { - progressIndicator.finish(""); + progressIndicator.finish(""); // RJCTODO: Is this right message? if (RuntimeProperties.runningWithGUI()) { ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); } @@ -625,10 +708,11 @@ public class Case implements SleuthkitCase.ErrorObserver { * * @throws IllegalStateException if there is no current case. */ - public static Case getCurrentCase() { + public synchronized static Case getCurrentCase() { if (currentCase != null) { return currentCase; } else { + // This is for backawards compatibility. RJCTODO: Reference story throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen")); } } @@ -636,68 +720,627 @@ public class Case implements SleuthkitCase.ErrorObserver { /** * Closes the current case if there is a current case. * - * @throws CaseActionException + * @throws CaseActionException if there is a problem closing the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ - "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}" - }) - public static void closeCurrentCase() throws CaseActionException { - if (null != currentCase) { - try { - /* - * Closing a case is always done in the same non-UI thread that - * opened/created the case. If the case is a multi-user case, - * this ensures that case directory lock is released in the same - * thread in which it was acquired, as is required by the - * coordination service. - */ - Future future = getSingleThreadedExecutor().submit(() -> { - if (CaseType.SINGLE_USER_CASE == currentCase.getCaseType()) { - closeTheCase(); - } else { - String caseName = currentCase.getCaseMetadata().getCaseName(); - /* - * Only one node at a time is allowed to close a case, - * so acquire an exclusive case resources lock. - */ - try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseName)) { - assert (null != resourcesLock); - closeTheCase(); - } finally { - /* - * Always release the case directory lock that was - * acquired when the case was opened. - */ - releaseSharedCaseDirLock(caseName); - } - } - return null; - }); - future.get(); - } catch (InterruptedException | ExecutionException ex) { - if (ex instanceof ExecutionException) { - /* - * The methods called within the task MUST throw a - * CaseActionException with a user-friendly error message - * suitable for substitution in the error message below. - * - * RJCTODO: Add TODO comment referencing JIRA - */ - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); + "Case.caseActionException.noCurrentCase=There is no open case", + "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}", + "Case.progressIndicatorTitle.closingCase=Closing Case", + "Case.progressMessage.preparingToCloseCase=Preparing to close case...",}) + public synchronized static void closeCurrentCase() throws CaseActionException { + if (null == currentCase) { + return; + } + + /* + * Set up either a GUI progress indicator or a logging progress + * indicator. + */ + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_closingCase(), null, null, null, null); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + progressIndicator.start(Bundle.Case_progressMessage_preparingToCloseCase()); + + try { + /* + * Closing a case is always done in the same non-UI thread that + * opened/created the case. If the case is a multi-user case, this + * ensures that case directory lock that is held as long as the case + * is open is released in the same thread in which it was acquired, + * as is required by the coordination service. + */ + Future future = getSingleThreadedExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == currentCase.getCaseType()) { + currentCase.close(progressIndicator); } else { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); + String caseName = currentCase.getCaseMetadata().getCaseName(); + try { + currentCase.close(progressIndicator); + } finally { + /* + * Always release the case directory lock that was + * acquired when the case was opened. + */ + releaseSharedCaseDirLock(caseName); + } } + currentCase = null; + return null; + }); + if (RuntimeProperties.runningWithGUI()) { + ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); + } + future.get(); + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof ExecutionException) { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); + } else { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); + } + } finally { + currentCase = null; + progressIndicator.finish(""); // RJCTODO: Is this right message? + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseClosed(); + ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); } } } + /** + * Deletes the current case. + * + * @throws CaseActionException if there is a problem deleting the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + */ + public synchronized static void deleteCurrentCase() throws CaseActionException { + if (null == currentCase) { + throw new CaseActionException(Bundle.Case_caseActionException_noCurrentCase()); + } + CaseMetadata metadata = currentCase.getCaseMetadata(); + closeCurrentCase(); + deleteCase(metadata); + } + + /** + * Deletes a case. This method cannot be used to delete the current case; + * deleting the current case must be done by calling Case.deleteCurrentCase. + * + * @param metadata The metadata for the case to delete. + * + * @throws CaseActionException if there is a problem deleting the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + */ + public static synchronized void deleteCase(CaseMetadata metadata) throws CaseActionException { + if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(currentCase.getCaseDirectory())) { + // RJCTODO: Throw + } + + try { + /* + * Set up either a GUI progress indicator or a logging progress + * indicator. + */ + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_closingCase(), null, null, null, null); // RJCTODO + } else { + progressIndicator = new LoggingProgressIndicator(); + } + progressIndicator.start(Bundle.Case_progressMessage_preparingToCloseCase()); + Future future = getSingleThreadedExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { + cleanupDeletedCase(metadata); + } else { + String caseName = currentCase.getCaseMetadata().getCaseName(); + /* + * First, acquire an exclusive case directory lock. The case + * cannot be deleted if another node has it open. + */ + try (CoordinationService.Lock dirLock = acquireExclusiveLock(metadata.getCaseDirectory())) { + assert (null != dirLock); + /* + * Try to unload/delete the Solr core from the Solr + * server. Do this before deleting the case directory + * because the index files are in the case directory and + * the deletion will fail if the core is not unloaded + * first. + */ + // RJCTODO: Need a method for this in keyword search service, code is in AIM at the moment + + /* + * Delete the case database from the database server. + */ + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS + statement.execute(deleteCommand); + } + + cleanupDeletedCase(metadata); + } + } + return null; + }); + future.get(); + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof ExecutionException) { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); // RJCTODO + } else { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); // RJCTODO + } + } + } + + /** + * Sanitizes the case name for use as a PostgreSQL database name and in + * ActiveMQ event channle (topic) names. + * + * PostgreSQL: + * http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html 63 + * chars max, must start with a-z or _ following chars can be letters _ or + * digits + * + * ActiveMQ: + * http://activemq.2283324.n4.nabble.com/What-are-limitations-restrictions-on-destination-name-td4664141.html + * may not be ? + * + * @param caseName A candidate case name. + * + * @return The sanitized case name. + * + * @throws org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException + */ + static String sanitizeCaseName(String caseName) throws IllegalCaseNameException { + + String result; + + // Remove all non-ASCII characters + result = caseName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS + + // Remove all control characters + result = result.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS + + // Remove / \ : ? space ' " + result = result.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS + + // Make it all lowercase + result = result.toLowerCase(); + + // Must start with letter or underscore for PostgreSQL. If not, prepend an underscore. + if (result.length() > 0 && !(Character.isLetter(result.codePointAt(0))) && !(result.codePointAt(0) == '_')) { + result = "_" + result; + } + + // Chop to 63-16=47 left (63 max for PostgreSQL, taking 16 for the date _20151225_123456) + if (result.length() > MAX_SANITIZED_CASE_NAME_LEN) { + result = result.substring(0, MAX_SANITIZED_CASE_NAME_LEN); + } + + if (result.isEmpty()) { + throw new IllegalCaseNameException(String.format("Failed to sanitize case name '%s'", caseName)); + } + + return result; + } + + /** + * Creates a case directory and its subdirectories. + * + * @param caseDir Path to the case directory (typically base + case name). + * @param caseType The type of case, single-user or multi-user. + * + * @throws CaseActionException throw if could not create the case dir + */ + static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException { + + File caseDirF = new File(caseDir); + + if (caseDirF.exists()) { + if (caseDirF.isFile()) { + throw new CaseActionException( + NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir)); + + } else if (!caseDirF.canRead() || !caseDirF.canWrite()) { + throw new CaseActionException( + NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir)); + } + } + + try { + boolean result = (caseDirF).mkdirs(); // create root case Directory + + if (result == false) { + throw new CaseActionException( + NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir)); + } + + // create the folders inside the case directory + String hostClause = ""; + + if (caseType == CaseType.MULTI_USER_CASE) { + hostClause = File.separator + NetworkUtils.getLocalHostName(); + } + result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs() + && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs() + && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs() + && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs(); + + if (result == false) { + throw new CaseActionException( + NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir)); + } + + final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER; + result = new File(modulesOutDir).mkdir(); + + if (result == false) { + throw new CaseActionException( + NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", + modulesOutDir)); + } + + final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER; + result = new File(reportsOutDir).mkdir(); + + if (result == false) { + throw new CaseActionException( + NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", + modulesOutDir)); + + } + + } catch (MissingResourceException | CaseActionException e) { + throw new CaseActionException( + NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e); + } + } + + /** + * Deletes a case directory. + * + * @param casePath A case directory path. + * + * @return True if the deletion succeeded, false otherwise. + */ + // RJCTODO: Get rid of this foolishness + static boolean deleteCaseDirectory(File casePath) { + LOGGER.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS + return FileUtil.deleteDir(casePath); + } + + /** + * Gets the paths of data sources that are images. + * + * @param db A case database. + * + * @return A mapping of object ids to image paths. + */ + static Map getImagePaths(SleuthkitCase db) { + Map imgPaths = new HashMap<>(); + try { + Map> imgPathsList = db.getImagePaths(); + for (Map.Entry> entry : imgPathsList.entrySet()) { + if (entry.getValue().size() > 0) { + imgPaths.put(entry.getKey(), entry.getValue().get(0)); + } + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS + } + return imgPaths; + } + + /** + * Use the main window of the desktop application to initialize the + * application name. Should be called BEFORE any case is opened or created. + */ + private synchronized static void getAppNameFromMainWindow() { + /* + * This is tricky and fragile. What looks like lazy initialization of + * the appName field is actually getting the application name from the + * main window title BEFORE a case has been opened and a case name has + * been included in the title. It is also very specific to the desktop + * GUI. + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties item + * set by Installers. + */ + if (RuntimeProperties.runningWithGUI() && (null == appName || appName.isEmpty())) { + try { + SwingUtilities.invokeAndWait(() -> { + appName = WindowManager.getDefault().getMainWindow().getTitle(); + }); + } catch (InterruptedException | InvocationTargetException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception getting main window title", ex); + } + } + } + + // RJCTODO + private static void cleanupDeletedCase(CaseMetadata metadata) { + /* + * Delete the case directory. + */ + if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { + LOGGER.log(Level.SEVERE, "Failed to fully delete case directory {0}", metadata.getCaseDirectory()); + } + + /* + * If running in a GUI, remove the case from the Recent Cases menu + */ + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> { + RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString()); + }); + } + } + + /** + * Acquires an exclusive case name lock. + * + * @param caseName The case name (not the case display name, which can be + * changed by a user). + * + * @return The lock. + * + * @throws CaseActionException with a user-friendly message if the lock + * cannot be acquired. + */ + @Messages({"Case.creationException.couldNotAcquireNameLock=Failed to get lock on case name."}) + private static CoordinationService.Lock acquireExclusiveCaseNameLock(String caseName) throws CaseActionException { + try { + Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, NAME_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); + if (null == lock) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock()); + } + return lock; + + } catch (InterruptedException | CoordinationServiceException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); + } + } + + /** + * Acquires a shared case directory lock for the current case. + * + * @param caseDir The full path of the case directory. + * + * @throws CaseActionException with a user-friendly message if the lock + * cannot be acquired. + */ + @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."}) + private static void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { + try { + currentCaseDirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir, SHARED_DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); + if (null == currentCaseDirLock) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); + } + } catch (InterruptedException | CoordinationServiceException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); + } + } + + /** + * Releases a shared case directory lock for the current case. + * + * @param caseDir The full path of the case directory. + */ + private static void releaseSharedCaseDirLock(String caseDir) { + // RJCTODO: Guard currentCaseLock? Yep! + if (currentCaseDirLock != null) { + try { + currentCaseDirLock.release(); + currentCaseDirLock = null; + } catch (CoordinationService.CoordinationServiceException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex); + } + } + } + + private static CoordinationService.Lock acquireExclusiveLock(String nodePath) throws CaseActionException { + try { + CoordinationService.Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, nodePath); + if (null == lock) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); // RJCTODO + } + return lock; + } catch (CoordinationServiceException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); // RJCTODO + } + } + + /** + * Acquires an exclusive case resources lock. + * + * @param caseName The case name (not the case display name, which can be + * changed by a user). + * + * @return The lock. + * + * @throws CaseActionException with a user-friendly message if the lock + * cannot be acquired. + */ + @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources."}) + private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseName) throws CaseActionException { + try { + String resourcesNodeName = caseName + "_resources"; + Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, resourcesNodeName, RESOURCE_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); + if (null == lock) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock()); + } + return lock; + + } catch (InterruptedException | CoordinationServiceException ex) { + throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex); + } + } + + /** + * Update the GUI to to reflect the current case. + */ + private static void updateGUIForCaseOpened() { + if (RuntimeProperties.runningWithGUI() && null != currentCase) { + + SleuthkitCase caseDb = currentCase.getSleuthkitCase(); + + // RJCTODO: Test both of these conditions + /* + * If the case database was upgraded for a new schema and a backup + * database was created, notify the user. + */ + final String backupDbPath = caseDb.getBackupDatabasePath(); + if (null != backupDbPath) { + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), + JOptionPane.INFORMATION_MESSAGE); + }); + } + + /* + * Look for the files for the data sources listed in the case + * database and give the user the opportunity to locate any that are + * missing. + */ + Map imgPaths = getImagePaths(caseDb); + for (Map.Entry entry : imgPaths.entrySet()) { + long obj_id = entry.getKey(); + String path = entry.getValue(); + boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); + if (!fileExists) { + int ret = JOptionPane.showConfirmDialog( + WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", appName, path), // RJCTODO: Don't use app name here + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), + JOptionPane.YES_NO_OPTION); + if (ret == JOptionPane.YES_OPTION) { + MissingImageDialog.makeDialog(obj_id, caseDb); + } else { + LOGGER.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS // RJCTODO + } + } + } + + /* + * Enable the case-specific actions. + */ + SwingUtilities.invokeLater(() -> { + CallableSystemAction.get(AddImageAction.class).setEnabled(true); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); + CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); + CallableSystemAction.get(DeleteCurrentCaseAction.class).setEnabled(true); + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); + + /* + * Add the case to the recent cases tracker that supplies a list + * of recent cases to the recent cases menu item and the + * open/create case dialog. + */ + RecentCases.getInstance().addRecentCase(currentCase.getName(), currentCase.getCaseMetadata().getFilePath().toString()); // update the recent cases + + /* + * Open the top components (windows within the main application + * window). + */ + if (currentCase.hasData()) { + CoreComponentControl.openCoreWindows(); + } + + /* + * Reset the main window title to be [application name] - [case + * name], instead of just the application name. + */ + addCaseNameToMainWindowTitle(currentCase.getName()); + }); + } + } + + /* + * Update the GUI to to reflect the lack of a current case. + */ + private static void updateGUIForCaseClosed() { + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> { + + /* + * Close the top components (windows within the main application + * window). + */ + CoreComponentControl.closeCoreWindows(); + + /* + * Disable the case-specific menu items. + */ + CallableSystemAction.get(AddImageAction.class).setEnabled(false); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); + CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); + CallableSystemAction.get(DeleteCurrentCaseAction.class).setEnabled(false); + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); + + /* + * Clear the notifications in the notfier component in the lower + * right hand corner of the main application window. + */ + MessageNotifyUtil.Notify.clear(); + + /* + * Reset the main window title to be just the application name, + * instead of [application name] - [case name]. + */ + Frame mainWindow = WindowManager.getDefault().getMainWindow(); + mainWindow.setTitle(appName); + }); + } + } + + /** + * Changes the title of the main window to include the case name. + * + * @param caseName The name of the case. + */ + private static void addCaseNameToMainWindowTitle(String caseName) { + if (!caseName.isEmpty()) { + Frame frame = WindowManager.getDefault().getMainWindow(); + frame.setTitle(caseName + " - " + appName); + } + } + + /** + * Get the single thread executor for the current case, creating it if + * necessary. + * + * @return The executor + */ + private synchronized static ExecutorService getSingleThreadedExecutor() { + if (null == singleThreadedExecutor) { + singleThreadedExecutor = Executors.newSingleThreadExecutor(); + } + return singleThreadedExecutor; + + } + /** * Gets the case database. * * @return The case database. */ public SleuthkitCase getSleuthkitCase() { - return this.db; + return this.caseDb; } /** @@ -728,14 +1371,24 @@ public class Case implements SleuthkitCase.ErrorObserver { } /** - * Gets the case name. + * Gets the immutable case name. * * @return The case name. */ + // RJCTODO: Check all uses of this! public String getName() { return getCaseMetadata().getCaseName(); } + /** + * Gets the case name that can be changed by the user. + * + * @return The case display name. + */ + public String getDisplayName() { + return getCaseMetadata().getCaseDisplayName(); + } + /** * Gets the case number. * @@ -871,7 +1524,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * database. */ public List getDataSources() throws TskCoreException { - List list = db.getRootObjects(); + List list = caseDb.getRootObjects(); hasDataSources = (list.size() > 0); return list; } @@ -1029,7 +1682,7 @@ public class Case implements SleuthkitCase.ErrorObserver { String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS throw new TskCoreException(errorMsg, ex); } - Report report = this.db.addReport(normalizedLocalPath, srcModuleName, reportName); + Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName); eventPublisher.publish(new ReportAddedEvent(report)); } @@ -1042,7 +1695,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * database. */ public List getAllReports() throws TskCoreException { - return this.db.getAllReports(); + return this.caseDb.getAllReports(); } /** @@ -1055,30 +1708,11 @@ public class Case implements SleuthkitCase.ErrorObserver { */ public void deleteReports(Collection reports) throws TskCoreException { for (Report report : reports) { - this.db.deleteReport(report); + this.caseDb.deleteReport(report); eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null)); } } - /** - * Allows the case database to report internal error conditions in - * situations where throwing an exception is not appropriate. - * - * @param context The context of the error condition. - * @param errorMessage An error message. - */ - @Override - public void receiveError(String context, String errorMessage) { - /* - * NOTE: We are accessing tskErrorReporter from two different threads. - * This is ok as long as we only read the value of tskErrorReporter - * because tskErrorReporter is declared as volatile. - */ - if (null != tskErrorReporter) { - tskErrorReporter.addProblems(context, errorMessage); - } - } - /** * Gets the case metadata. * @@ -1116,206 +1750,20 @@ public class Case implements SleuthkitCase.ErrorObserver { } /** - * Deletes the case folder for this Autopsy case and sets the current case - * to null. It does not not delete the case database for a multi-user case. - * - * @param caseDir The case directory to delete. - * - * @throws CaseActionException exception throw if case could not be deleted + * Constructs an Autopsy case. */ - void deleteCase(File caseDir) throws CaseActionException { - LOGGER.log(Level.INFO, "Deleting case.\ncaseDir: {0}", caseDir); //NON-NLS - - try { - boolean result = deleteCaseDirectory(caseDir); - - RecentCases.getInstance().removeRecentCase(this.caseMetadata.getCaseName(), this.caseMetadata.getFilePath().toString()); // remove it from the recent case - Case.changeCurrentCase(null); - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg", caseDir)); - } - - } catch (MissingResourceException | CaseActionException ex) { - LOGGER.log(Level.SEVERE, "Error deleting the current case dir: " + caseDir, ex); //NON-NLS - throw new CaseActionException( - NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg2", caseDir), ex); - } + private Case() { } /** - * Sanitizes the case name for PostgreSQL database, Solr cores, and ActiveMQ - * topics. Makes it plain-vanilla enough that each item should be able to - * use it. + * Creates and opens a new case. * - * Solr: - * http://stackoverflow.com/questions/29977519/what-makes-an-invalid-core-name - * may not be / \ : - * - * ActiveMQ: - * http://activemq.2283324.n4.nabble.com/What-are-limitations-restrictions-on-destination-name-td4664141.html - * may not be ? - * - * PostgreSQL: - * http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html 63 - * chars max, must start with a-z or _ following chars can be letters _ or - * digits - * - * SQLite: Uses autopsy.db for the database name and follows the Windows - * naming convention - * - * @param caseName A candidate case name. - * - * @return The sanitized case name. - * - * @throws org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException - */ - // RJCTODO: Get Eugene to Update and also reference story - static String sanitizeCaseName(String caseName) throws IllegalCaseNameException { - - String result; - - // Remove all non-ASCII characters - result = caseName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS - - // Remove all control characters - result = result.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS - - // Remove / \ : ? space ' " - result = result.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS - - // Make it all lowercase - result = result.toLowerCase(); - - // Must start with letter or underscore for PostgreSQL. If not, prepend an underscore. - if (result.length() > 0 && !(Character.isLetter(result.codePointAt(0))) && !(result.codePointAt(0) == '_')) { - result = "_" + result; - } - - // Chop to 63-16=47 left (63 max for PostgreSQL, taking 16 for the date _20151225_123456) - if (result.length() > MAX_SANITIZED_CASE_NAME_LEN) { - result = result.substring(0, MAX_SANITIZED_CASE_NAME_LEN); - } - - if (result.isEmpty()) { - throw new IllegalCaseNameException(String.format("Failed to sanitize case name '%s'", caseName)); - } - - return result; - } - - /** - * Creates a case directory and its subdirectories. - * - * @param caseDir Path to the case directory (typically base + case name). - * @param caseType The type of case, single-user or multi-user. - * - * @throws CaseActionException throw if could not create the case dir - */ - static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException { - - File caseDirF = new File(caseDir); - - if (caseDirF.exists()) { - if (caseDirF.isFile()) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir)); - - } else if (!caseDirF.canRead() || !caseDirF.canWrite()) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir)); - } - } - - try { - boolean result = (caseDirF).mkdirs(); // create root case Directory - - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir)); - } - - // create the folders inside the case directory - String hostClause = ""; - - if (caseType == CaseType.MULTI_USER_CASE) { - hostClause = File.separator + NetworkUtils.getLocalHostName(); - } - result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs() - && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs() - && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs() - && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs(); - - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir)); - } - - final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER; - result = new File(modulesOutDir).mkdir(); - - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", - modulesOutDir)); - } - - final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER; - result = new File(reportsOutDir).mkdir(); - - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", - modulesOutDir)); - - } - - } catch (MissingResourceException | CaseActionException e) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e); - } - } - - /** - * Deletes a case directory. - * - * @param casePath A case directory path. - * - * @return True if the deletion succeeded, false otherwise. - */ - static boolean deleteCaseDirectory(File casePath) { - LOGGER.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS - return FileUtil.deleteDir(casePath); - } - - /** - * Gets the paths of data sources that are images. - * - * @param db A case database. - * - * @return A mapping of object ids to image paths. - */ - static Map getImagePaths(SleuthkitCase db) { - Map imgPaths = new HashMap<>(); - try { - Map> imgPathsList = db.getImagePaths(); - for (Map.Entry> entry : imgPathsList.entrySet()) { - if (entry.getValue().size() > 0) { - imgPaths.put(entry.getKey(), entry.getValue().get(0)); - } - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS - } - return imgPaths; - } - - /** - * Creates a new Autopsy case. - * - * @param caseDir The full path of the case directory. - * @param caseName The name of case. - * @param caseDisplayName The name of case. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. + * @param caseDisplayName The display name of case, which may be changed + * later by the user. * @param caseNumber The case number, can be the empty string. * @param examiner The examiner to associate with the case, can be * the empty string. @@ -1326,14 +1774,18 @@ public class Case implements SleuthkitCase.ErrorObserver { * and may be a wrapper for a lower-level * exception. */ - @Messages({"Case.creationException.couldNotCreateMetadataFile=Could not create case: failed to create case metadata file."}) - private static void createCase(String caseDir, String caseName, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + @Messages({ + "Case.progressMessage.creatingCaseDirectory=Creating case directory...", + "Case.progressMessage.creatingCaseDatabase=Creating case database...", + "Case.progressMessage.creatingCaseMetadataFile=Creating case metadata file..." + }) + private void open(String caseDir, String caseName, String caseDisplayName, String caseNumber, String examiner, CaseType caseType, ProgressIndicator progressIndicator) throws CaseActionException { /* * Create the case directory, if it does not already exist. * - * CAUTION: The reason for the check for the existence of the case - * directory is not at all obvious. It reflects the assumption that if - * the case directory already exists, it is because the case is being + * TODO (JIRA-2180): The reason for this check for the existence of the + * case directory is not at all obvious. It reflects the assumption that + * if the case directory already exists, it is because the case is being * created using the the "New Case" wizard, which separates the creation * of the case directory from the creation of the case, with the idea * that if the case directory cannot be created, the user can be asked @@ -1341,15 +1793,14 @@ public class Case implements SleuthkitCase.ErrorObserver { * subtle and undetectable coupling between this code and the wizard * code. The desired effect could be accomplished more simply and safely * by having this method throw a specific exception to indicate that the - * case directory could not be created. A FEW specific exception types - * would in turn allow us to put localized, user-friendly messages in - * the GUI instead of putting user-friendly, localized messages in the - * exceptions, which cause them to appear in the logs, where it would be - * better to have English for readability by the broadest group of - * developers. - * - * TODO (JIRA-2180): Fix the problem described above. + * case directory could not be created. In fact, a FEW specific + * exception types would in turn allow us to put localized, + * user-friendly messages in the GUI instead of putting user-friendly, + * localized messages in the exceptions, which causes them to appear in + * the application log, where it would be better to use English for + * readability by the broadest group of developers. */ + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); if (new File(caseDir).exists() == false) { Case.createCaseDirectory(caseDir, caseType); } @@ -1357,447 +1808,221 @@ public class Case implements SleuthkitCase.ErrorObserver { /* * Create the case database. */ - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); - Date date = new Date(); + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase()); String dbName = null; - if (caseType == CaseType.SINGLE_USER_CASE) { - dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS - } else if (caseType == CaseType.MULTI_USER_CASE) { - dbName = caseName + "_" + dateFormat.format(date); - } - SleuthkitCase db = null; try { if (caseType == CaseType.SINGLE_USER_CASE) { - db = SleuthkitCase.newCase(dbName); + dbName = Paths.get(caseDir, "autopsy.db").toString(); // RJCTODO: Move to a const, fix non-relative path, need opening db message here + this.caseDb = SleuthkitCase.newCase(dbName); } else if (caseType == CaseType.MULTI_USER_CASE) { - db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); + Date date = new Date(); + dbName = caseName + "_" + dateFormat.format(date); + this.caseDb = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); } } catch (TskCoreException ex) { - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - /* - * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly - * messages, so propagate the exception message. - */ - throw new CaseActionException(String.format("Error creating the case database %s for %s ", dbName, caseName), ex); //NON-NLS RJCTODO - + throw new CaseActionException(String.format("Error creating the case database %s: %s", dbName, ex.getMessage()), ex); //NON-NLS RJCTOD } catch (UserPreferencesException ex) { - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); // RJCTODO } /* * Create the case metadata (.aut) file. */ - CaseMetadata metadata; + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseMetadataFile()); try { - metadata = new CaseMetadata(caseDir, caseType, caseName, caseDisplayName, caseNumber, examiner, dbName); + this.caseMetadata = new CaseMetadata(caseDir, caseType, caseName, caseDisplayName, caseNumber, examiner, dbName); } catch (CaseMetadataException ex) { throw new CaseActionException(Bundle.Case_creationException_couldNotCreateMetadataFile(), ex); } - - /* - * Create the case and make it the current case. - */ - Case newCase = new Case(metadata, db); - changeCurrentCase(newCase); - - LOGGER.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + open(progressIndicator); } /** - * Opens an existing Autopsy case. + * Opens an exsiting case. RJCTODO * - * @param caseMetadataFilePath The path of the case metadata (.aut) file. + * @param metadata + * @param progressIndicator * - * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @throws CaseActionException */ - private static void openCase(CaseMetadata metadata) throws CaseActionException { + private void open(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + this.caseMetadata = metadata; + /* * Open the case database. */ - SleuthkitCase db = null; try { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - db = SleuthkitCase.openCase(metadata.getCaseDatabasePath()); + this.caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath()); } else if (UserPreferences.getIsMultiUserModeEnabled()) { try { - db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); + this.caseDb = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); } catch (UserPreferencesException ex) { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); // RJCTODO: What does this say? - } } else { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled")); - } } catch (TskCoreException ex) { // RJCTODO: Need proper exception message throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); } + open(progressIndicator); + } + + /** + * Completes the case opening tasks common to both new cases and existing + * cases. + * + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.switchingLogDirectory=Switching log directory...", + "Case.progressMessage.settingUpTskErrorReporting=Setting up SleuthKit error reporting...", + "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", + "Case.progressMessage.openingCaseLevelServices=Opening case-level services...", + "Case.progressMessage.openingApplicationServiceResources=Opening case-specific application service resources...",}) + private void open(ProgressIndicator progressIndicator) { + /* + * Switch to writing to the application logs in the logs subdirectory. + */ + progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); + Logger.setLogDirectory(getLogDirectoryPath()); /* - * Check for the presence of the UI and do things that can only be done - * with user interaction. + * Hook up a SleuthKit layer error reporter. */ - if (RuntimeProperties.runningWithGUI()) { - /* - * If the case database was upgraded for a new schema, notify the - * user. - */ - final String backupDbPath = db.getBackupDatabasePath(); + progressIndicator.progress(Bundle.Case_progressMessage_settingUpTskErrorReporting()); + this.sleuthkitErrorReporter = new SleuthkitErrorReporter(MIN_SECS_BETWEEN_TSK_ERROR_REPORTS, NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText")); + this.caseDb.addErrorObserver(this.sleuthkitErrorReporter); - if (null != backupDbPath) { - SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), - JOptionPane.INFORMATION_MESSAGE); - }); - } + /* + * Clear the temp subdirectory. + */ + progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory()); + Case.clearTempSubDir(this.getTempDirectory()); - /* - * Look for the files for the data sources listed in the case - * database and give the user the opportunity to locate any that are - * missing. - */ - Map imgPaths = getImagePaths(db); - for (Map.Entry entry : imgPaths.entrySet()) { - long obj_id = entry.getKey(); - String path = entry.getValue(); - boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); + /* + * Open the case-level services. + */ + progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices()); + this.services = new Services(this.caseDb); - if (!fileExists) { - int ret = JOptionPane.showConfirmDialog( - WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", getAppName(), path), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), - JOptionPane.YES_NO_OPTION); - if (ret == JOptionPane.YES_OPTION) { - MissingImageDialog.makeDialog(obj_id, db); - } else { - LOGGER.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS - } - } - } - } - Case openedCase = new Case(metadata, db); - changeCurrentCase(openedCase); - LOGGER.log(Level.INFO, "Opened case {0} in directory = {1}", new Object[]{metadata.getCaseName(), metadata.getCaseDirectory()}); //NON-NLS RJCTODO - } - - /** - * Closes the current case. - * - * @throws CaseActionException - */ - private static void closeTheCase() throws CaseActionException { - if (null != currentCase) { - Case caseToClose = currentCase; - changeCurrentCase(null); // RJCTODO: Refactor + /* + * Allow any registered application services to open any resources + * specific to this case. + */ + progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); + AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator); + String serviceName = ""; + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { try { - caseToClose.getServices().close(); - caseToClose.db.close(); - } catch (IOException ex) { - throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(ex.getMessage()), ex); + serviceName = service.getServiceName(); + service.openCaseResources(context); + } catch (AutopsyService.AutopsyServiceException ex) { + // RJCTODO: Pop up error here? + Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to open case resources", serviceName), ex); } } - } - /** - * Acquires an exclusive case name lock. - * - * @param caseName The case name (not the case display name, which can be - * changed by a user). - * - * @return The lock. - * - * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. - */ - @Messages({"Case.creationException.couldNotAcquireNameLock=Failed to get lock on case name."}) - private static CoordinationService.Lock acquireExclusiveCaseNameLock(String caseName) throws CaseActionException { - try { - Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, NAME_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); - if (null == lock) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock()); - } - return lock; - - } catch (InterruptedException | CoordinationServiceException ex) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); - } - } - - /** - * Acquires a shared case directory lock for the current case. - * - * @param caseDir The full path of the case directory. - * - * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. - */ - @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."}) - private static void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { - try { - currentCaseDirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); - if (null == currentCaseDirLock) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); - } - } catch (InterruptedException | CoordinationServiceException ex) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); - } - } - - /** - * Releases a shared case directory lock for the current case. - * - * @param caseDir The full path of the case directory. - */ - private static void releaseSharedCaseDirLock(String caseDir) { - // RJCTODO: Guard currentCaseLock? - if (currentCaseDirLock != null) { + /* + * If this case is a multi-user case, set up for communication with + * other nodes. + */ + if (CaseType.MULTI_USER_CASE == this.caseMetadata.getCaseType()) { try { - currentCaseDirLock.release(); - currentCaseDirLock = null; - } catch (CoordinationService.CoordinationServiceException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex); + eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, this.getCaseMetadata().getCaseName())); + collaborationMonitor = new CollaborationMonitor(); + } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) { + // RJCTODO: Explain why this is not a fatal error + LOGGER.log(Level.SEVERE, "Failed to setup for collaboration", ex); //NON-NLS + MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")); } } + } - /** - * Acquires an exclusive case resources lock. - * - * @param caseName The case name (not the case display name, which can be - * changed by a user). - * - * @return The lock. - * - * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. + /* + * RJCTODO */ - @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources."}) - private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseName) throws CaseActionException { + private void close(ProgressIndicator progressIndicator) { + /* + * Cancel all ingest jobs. + * + * TODO (JIRA-2227): Case closing should wait for ingest to stop to + * avoid changing the case database while ingest is still using it. + */ + IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); + + /* + * Notify all local case event subscribers that the case is closed and + * all interactions with the current case are no longer permitted. + */ + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), currentCase, null)); + + /* + * Stop sending/receiving case events to and from other nodes if this is + * a multi-user case. + */ + if (CaseType.MULTI_USER_CASE == currentCase.getCaseType()) { + if (null != collaborationMonitor) { + collaborationMonitor.shutdown(); + } + eventPublisher.closeRemoteEventChannel(); + } + + /* + * Allow all registered applications ervices providers to close + * resources related to the case. + */ + AutopsyService.CaseContext context = new AutopsyService.CaseContext(currentCase, progressIndicator); + String serviceName = ""; + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { + try { + serviceName = service.getServiceName(); + if (!serviceName.equals("Solr Keyword Search Service")) { + service.closeCaseResources(context); + } + } catch (AutopsyService.AutopsyServiceException ex) { + Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to close case resources", serviceName), ex); + } + } + + /* + * Close the case-level services. + */ try { - String resourcesNodeName = caseName + "_resources"; - Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, resourcesNodeName, RESOURCE_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); - if (null == lock) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock()); - } - return lock; - - } catch (InterruptedException | CoordinationServiceException ex) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex); - } - } - - /** - * Updates the current case to the given case, firing property change events - * and updating the UI. - * - * @param newCase The new current case or null if there is no new current - * case. - */ - private static void changeCurrentCase(Case newCase) { - // close the existing case - Case oldCase = Case.currentCase; - Case.currentCase = null; - if (oldCase != null) { - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - }); - IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); - completeCaseChange(null); //closes windows, etc - if (null != oldCase.tskErrorReporter) { - oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case - oldCase.tskErrorReporter = null; - } - eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), oldCase, null)); - - AutopsyService.CaseContext context = new AutopsyService.CaseContext(oldCase, new LoggingProgressIndicator()); - String serviceName = ""; - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { - try { - serviceName = service.getServiceName(); - if (!serviceName.equals("Solr Keyword Search Service")) { - service.closeCaseResources(context); - } - } catch (AutopsyService.AutopsyServiceException ex) { - Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to close case resources", serviceName), ex); - } - } - - if (CaseType.MULTI_USER_CASE == oldCase.getCaseType()) { - if (null != oldCase.collaborationMonitor) { - oldCase.collaborationMonitor.shutdown(); - } - eventPublisher.closeRemoteEventChannel(); - } + currentCase.getServices().close(); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, String.format("Error closing internal case services for %s", getName()), ex); } - if (newCase != null) { - currentCase = newCase; - Logger.setLogDirectory(currentCase.getLogDirectoryPath()); - // sanity check - if (null != currentCase.tskErrorReporter) { - currentCase.tskErrorReporter.shutdown(); + /* + * Close the case database + */ + this.caseDb.close(); - } - // start listening for TSK errors for the new case - currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECS_BETWEEN_TSK_ERROR_REPORTS, - NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText")); - completeCaseChange(currentCase); - SwingUtilities.invokeLater(() -> { - RecentCases.getInstance().addRecentCase(currentCase.getName(), currentCase.getCaseMetadata().getFilePath().toString()); // update the recent cases - }); - if (CaseType.MULTI_USER_CASE == newCase.getCaseType()) { - try { - /** - * Use the text index name as the remote event channel name - * prefix since it is unique, the same as the case database - * name for a multiuser case, and is readily available - * through the Case.getTextIndexName() API. - */ - eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, newCase.getTextIndexName())); - currentCase.collaborationMonitor = new CollaborationMonitor(); - } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) { - LOGGER.log(Level.SEVERE, "Failed to setup for collaboration", ex); //NON-NLS - MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")); - } - } - - // RJCTODO: Remove, mention to Eugene. - AutopsyService.CaseContext context = new AutopsyService.CaseContext(Case.currentCase, new LoggingProgressIndicator()); - String serviceName = ""; - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class - )) { - try { - serviceName = service.getServiceName(); - service.openCaseResources(context); - } catch (AutopsyService.AutopsyServiceException ex) { - Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to open case resources", serviceName), ex); - } - } - - eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); - - } else { - Logger.setLogDirectory(PlatformUtil.getLogDirectory()); - } - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - } - - /** - * Updates the UI and does miscellaneous other things to complete changing - * the current case. - * - * @param newCase The new current case or null if there is no new current - * case. - */ - private static void completeCaseChange(Case newCase) { - LOGGER.log(Level.INFO, "Changing Case to: {0}", newCase); //NON-NLS - if (newCase != null) { // new case is open - - // clear the temp folder when the case is created / opened - Case.clearTempFolder(); - - if (RuntimeProperties.runningWithGUI()) { - // enable these menus - SwingUtilities.invokeLater(() -> { - CallableSystemAction.get(AddImageAction.class - ).setEnabled(true); - CallableSystemAction - .get(CaseCloseAction.class - ).setEnabled(true); - CallableSystemAction - .get(CasePropertiesAction.class - ).setEnabled(true); - CallableSystemAction - .get(CaseDeleteAction.class - ).setEnabled(true); // Delete Case menu - CallableSystemAction - .get(OpenTimelineAction.class - ).setEnabled(true); - - if (newCase.hasData()) { - // open all top components - CoreComponentControl.openCoreWindows(); - } else { - // close all top components - CoreComponentControl.closeCoreWindows(); - } - addCaseNameToMainWindowTitle(currentCase.getName()); - }); - } else { - SwingUtilities.invokeLater(() -> { - Frame f = WindowManager.getDefault().getMainWindow(); - f.setTitle(getAppName()); // set the window name to just application name - }); - } - - } else { // case is closed - SwingUtilities.invokeLater(() -> { - if (RuntimeProperties.runningWithGUI()) { - - // close all top components first - CoreComponentControl.closeCoreWindows(); - - // disable these menus - CallableSystemAction - .get(AddImageAction.class - ).setEnabled(false); // Add Image menu - CallableSystemAction - .get(CaseCloseAction.class - ).setEnabled(false); // Case Close menu - CallableSystemAction - .get(CasePropertiesAction.class - ).setEnabled(false); // Case Properties menu - CallableSystemAction - .get(CaseDeleteAction.class - ).setEnabled(false); // Delete Case menu - CallableSystemAction - .get(OpenTimelineAction.class - ).setEnabled(false); - } - - //clear pending notifications - MessageNotifyUtil.Notify.clear(); - Frame f = WindowManager.getDefault().getMainWindow(); - f.setTitle(getAppName()); // set the window name to just application name - }); - - //try to force gc to happen - System.gc(); - System.gc(); - } - - //log memory usage after case changed - LOGGER.log(Level.INFO, PlatformUtil.getAllMemUsageInfo()); + /* + * Disconnect the SleuthKit layer error reporter. + */ + this.caseDb.removeErrorObserver(this.sleuthkitErrorReporter); + // RJCTODO: Reset the log directory? } /** * Empties the temp subdirectory for the current case. */ - private static void clearTempFolder() { - File tempFolder = new File(currentCase.getTempDirectory()); + private static void clearTempSubDir(String tempSubDirPath) { + File tempFolder = new File(tempSubDirPath); if (tempFolder.isDirectory()) { File[] files = tempFolder.listFiles(); if (files.length > 0) { for (File file : files) { if (file.isDirectory()) { - deleteCaseDirectory(file); + FileUtil.deleteDir(file); } else { file.delete(); } @@ -1806,41 +2031,6 @@ public class Case implements SleuthkitCase.ErrorObserver { } } - /** - * Changes the title of the main window to include the case name. - * - * @param newCaseName The name of the case. - */ - private static void addCaseNameToMainWindowTitle(String newCaseName) { - if (!newCaseName.isEmpty()) { - Frame f = WindowManager.getDefault().getMainWindow(); - f.setTitle(newCaseName + " - " + getAppName()); - } - } - - /** - * Get the single thread executor for the current case, creating it if - * necessary. - * - * @return The executor - */ - private static ExecutorService getSingleThreadedExecutor() { // RJCTODO: Does this need synch? - if (null == singleThreadedExecutor) { - singleThreadedExecutor = Executors.newSingleThreadExecutor(); - } - return singleThreadedExecutor; - - } - - /** - * Constructs an Autopsy case. - */ - private Case(CaseMetadata caseMetadata, SleuthkitCase db) { - this.caseMetadata = caseMetadata; - this.db = db; - this.services = new Services(db); - } - /** * Gets the path to the specified subdirectory of the case directory, * creating it if it does not already exist. @@ -1853,6 +2043,7 @@ public class Case implements SleuthkitCase.ErrorObserver { subDirectory.mkdirs(); } return subDirectory.toString(); + } private final static class IllegalCaseNameException extends Exception { @@ -1870,14 +2061,14 @@ public class Case implements SleuthkitCase.ErrorObserver { private final static class CancelButtonListener implements ActionListener { - private Future caseActionFuture; + private Future caseActionFuture; private CaseContext caseContext; - private void setCaseActionFuture(Future caseActionFuture) { + private void setCaseActionFuture(Future caseActionFuture) { this.caseActionFuture = caseActionFuture; } - private void setCaseContext(CaseContext caseContext) { + private void setCaseContext(CaseContext caseContext) { // RJCTODO: USe this this.caseContext = caseContext; } @@ -1962,13 +2153,14 @@ public class Case implements SleuthkitCase.ErrorObserver { * Checks if case is currently open. * * @return True or false. + * * @deprecated Do not use, this method is not relaible. */ @Deprecated public static boolean isCaseOpen() { return currentCase != null; } - + /** * Invokes the startup dialog window. * @@ -2092,7 +2284,7 @@ public class Case implements SleuthkitCase.ErrorObserver { @Deprecated public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException { try { - Image newDataSource = db.getImageById(imgId); + Image newDataSource = caseDb.getImageById(imgId); notifyDataSourceAdded(newDataSource, UUID.randomUUID()); return newDataSource; } catch (Exception ex) { @@ -2127,20 +2319,6 @@ public class Case implements SleuthkitCase.ErrorObserver { deleteReports(reports); } - /** - * Closes this Autopsy case. - * - * @throws CaseActionException if there is a problem closing the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. - * @deprecated Use Case.closeCurrentCase instead. instead. - */ - @Deprecated - public void closeCase() throws CaseActionException { - Case.closeCurrentCase(); - } - @Deprecated public static final String propStartup = "LBL_StartupDialog"; //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java deleted file mode 100644 index 6ec7c79222..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2014 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.awt.event.ActionEvent; -import java.io.File; -import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.Action; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; -import org.openide.util.HelpCtx; -import org.openide.util.NbBundle; -import org.openide.util.actions.CallableSystemAction; -; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.Action; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; -import org.openide.util.HelpCtx; -import org.openide.util.NbBundle; -import org.openide.util.actions.CallableSystemAction; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.Action; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; -import org.openide.util.HelpCtx; -import org.openide.util.NbBundle; -import org.openide.util.actions.CallableSystemAction; - -/** - * The action to delete the current Case. This class should be disabled on - * creation and it will be enabled on new case creation or case opened. - */ - - -final class CaseDeleteAction extends CallableSystemAction { - - private JPanel caller; // for error handling - - private static final Logger logger = Logger.getLogger(CaseDeleteAction.class.getName()); - - /** - * The constructor for this class - */ - public CaseDeleteAction() { - putValue(Action.NAME, NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction")); // put the action Name - this.setEnabled(false); - } - - /** - * Deletes the current opened case. - * - * @param e - */ - @Override - public void actionPerformed(ActionEvent e) { - Case currentCase = Case.getCurrentCase(); - File caseFolder = new File(currentCase.getCaseDirectory()); - String caseName = currentCase.getName(); - if (!caseFolder.exists()) { - // throw an error - - logger.log(Level.WARNING, "Couldn't delete case.", new Exception("The case directory doesn't exist.")); //NON-NLS - } else { - // show the confirmation first to close the current case and open the "New Case" wizard panel - String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CaseDeleteAction.closeConfMsg.text", caseName, caseFolder.getPath()); - NotifyDescriptor d = new NotifyDescriptor.Confirmation(closeCurrentCase, - NbBundle.getMessage(this.getClass(), - "CaseDeleteAction.closeConfMsg.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - d.setValue(NotifyDescriptor.NO_OPTION); - - Object res = DialogDisplayer.getDefault().notify(d); - if (res != null && res == DialogDescriptor.YES_OPTION) { - boolean success = false; - - try { - Case.getCurrentCase().deleteCase(caseFolder); // delete the current case - success = true; - } catch (CaseActionException ex) { - logger.log(Level.WARNING, "Could not delete the case folder: " + caseFolder); //NON-NLS - } - - // show notification whether the case has been deleted or it failed to delete... - if (!success) { - JOptionPane.showMessageDialog(caller, - NbBundle.getMessage(this.getClass(), - "CaseDeleteAction.msgDlg.fileInUse.msg"), - NbBundle.getMessage(this.getClass(), - "CaseDeleteAction.msgDlg.fileInUse.title"), - JOptionPane.ERROR_MESSAGE); // throw an error - } else { - CasePropertiesAction.closeCasePropertiesWindow(); // because the "Delete Case" button is in the "CaseProperties" window, we have to close that window when we delete the case. - JOptionPane.showMessageDialog(caller, NbBundle.getMessage(this.getClass(), - "CaseDeleteAction.msgDlg.caseDelete.msg", - caseName)); - } - } - } - } - - /** - * This method does nothing. Use the "actionPerformed(ActionEvent e)" - * instead of this method. - */ - @Override - public void performAction() { - // Note: I use the actionPerformed above instead of this method - } - - /** - * Gets the name of this action. This may be presented as an item in a menu. - * - * @return actionName - */ - @Override - public String getName() { - return NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction"); - } - - /** - * Gets the HelpCtx associated with implementing object - * - * @return HelpCtx or HelpCtx.DEFAULT_HELP - */ - @Override - public HelpCtx getHelpCtx() { - return HelpCtx.DEFAULT_HELP; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index dcf902dfc6..62604a2d39 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -27,9 +27,6 @@ import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; @@ -38,8 +35,6 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Version; -import org.sleuthkit.autopsy.ingest.IngestJob; -import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action that opens an existing case. @@ -108,9 +103,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action ex.getMessage(), // Should be user-friendly NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS JOptionPane.ERROR_MESSAGE); - if (!Case.isCaseOpen()) { - StartupWindowProvider.getInstance().open(); - } + StartupWindowProvider.getInstance().open(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java index f4cc9cb24e..e0c4e75cab 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java @@ -353,7 +353,7 @@ class CasePropertiesForm extends javax.swing.JPanel { }//GEN-LAST:event_updateCaseNameButtonActionPerformed private void deleteCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteCaseButtonActionPerformed - CallableSystemAction.get(CaseDeleteAction.class).actionPerformed(evt); + CallableSystemAction.get(DeleteCurrentCaseAction.class).actionPerformed(evt); }//GEN-LAST:event_deleteCaseButtonActionPerformed private void caseNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_caseNameTextFieldActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java new file mode 100644 index 0000000000..ff60df9070 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java @@ -0,0 +1,103 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.event.ActionEvent; +import java.util.logging.Level; +import javax.swing.Action; +import javax.swing.JOptionPane; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.actions.CallableSystemAction; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * An action to delete the current case. + */ +final class DeleteCurrentCaseAction extends CallableSystemAction { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(DeleteCurrentCaseAction.class.getName()); + + DeleteCurrentCaseAction() { // Should pass in caller + putValue(Action.NAME, NbBundle.getMessage(DeleteCurrentCaseAction.class, "CTL_CaseDeleteAction")); + this.setEnabled(false); + } + + @Override + @Messages({ + "Case.deleteCaseConfirmationDialog.title=Delete Current Case?", + "Case.deleteCaseConfirmationDialog.message=Are you sure you want to close and delete the current case?", + "Case.deleteCaseFailureMessageBox.title=Failed to Delete Case", + "# {0} - exception message", "Case.deleteCaseFailureMessageBox.message=Error deleting case: {0}",}) + public void actionPerformed(ActionEvent e) { + try { + Case currentCase = Case.getCurrentCase(); + String caseName = currentCase.getName(); + + /* + * Do a confirmation dialog and close the current case if the user + * confirms he/she wants to proceed. + */ + Object response = DialogDisplayer.getDefault().notify(new NotifyDescriptor( + Bundle.Case_deleteCaseConfirmationDialog_message(), + Bundle.Case_deleteCaseConfirmationDialog_title(), + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE, + null, + NotifyDescriptor.NO_OPTION)); + if (null != response && DialogDescriptor.YES_OPTION == response) { + try { + Case.deleteCurrentCase(); // RJCTODO: Test this! + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Failed to delete case %s", caseName), ex); + JOptionPane.showMessageDialog( + null, + Bundle.Case_deleteCaseFailureMessageBox_message(ex.getMessage()), + Bundle.Case_deleteCaseFailureMessageBox_title(), + JOptionPane.ERROR_MESSAGE); + } + // RJCTODO: Fix this + // because the "Delete Case" button is in the "CaseProperties" window, we have to close that window when we delete the case. + CasePropertiesAction.closeCasePropertiesWindow(); + } + } catch (IllegalStateException ex) { + logger.log(Level.SEVERE, "Case delete action called with no current case", ex); + } + } + + @Override + public void performAction() { + } + + @Override + public String getName() { + return NbBundle.getMessage(DeleteCurrentCaseAction.class, "CTL_CaseDeleteAction"); + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 66241a8453..b85215a2be 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -91,9 +91,7 @@ final class NewCaseWizardAction extends CallableSystemAction { NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS JOptionPane.ERROR_MESSAGE); StartupWindowProvider.getInstance().close(); // RC: Why close and open? - if (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); - } }); doFailedCaseCleanup(wizardDescriptor); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java index bf23cbda16..91f007c81d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java @@ -119,11 +119,8 @@ class OpenRecentCasePanel extends javax.swing.JPanel { NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore - if (Case.isCaseOpen() == false) { - StartupWindowProvider.getInstance().open(); - } + StartupWindowProvider.getInstance().open(); } else { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { Case.openCurrentCase(casePath); } catch (CaseActionException ex) { @@ -134,9 +131,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel { ex.getMessage(), // Should be user-friendly NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS JOptionPane.ERROR_MESSAGE); - if (!Case.isCaseOpen()) { - StartupWindowProvider.getInstance().open(); - } + StartupWindowProvider.getInstance().open(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index 7fabd797ec..693b294c1c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -25,13 +25,9 @@ import java.awt.event.ActionListener; import java.io.File; import java.util.logging.Level; import javax.swing.JOptionPane; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action listener that opens a recent case. @@ -69,11 +65,9 @@ class RecentItems implements ActionListener { NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); RecentCases.getInstance().removeRecentCase(caseName, caseMetaDataFilePath); - if (Case.isCaseOpen() == false) { - EventQueue.invokeLater(() -> { - StartupWindowProvider.getInstance().open(); - }); - } + EventQueue.invokeLater(() -> { + StartupWindowProvider.getInstance().open(); + }); } else { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { @@ -86,9 +80,7 @@ class RecentItems implements ActionListener { ex.getMessage(), // Should be user-friendly NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS JOptionPane.ERROR_MESSAGE); - if (!Case.isCaseOpen()) { - StartupWindowProvider.getInstance().open(); - } + StartupWindowProvider.getInstance().open(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index ad025cad43..e61cdf4bb0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,19 +34,21 @@ import java.util.Date; import org.apache.commons.io.FileUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case.CaseType; -import static org.sleuthkit.autopsy.casemodule.Case.MODULE_FOLDER; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.datamodel.TskData; /** * Import a case from single-user to multi-user. + * + * DO NOT USE, NEEDS TO BE UPDATED */ public class SingleUserCaseConverter { + static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS // RJCTODO private static final String AUTOPSY_DB_FILE = "autopsy.db"; //NON-NLS private static final String DOTAUT = CaseMetadata.getFileExtension(); //NON-NLS private static final String TIMELINE_FOLDER = "Timeline"; //NON-NLS @@ -1171,4 +1173,7 @@ public class SingleUserCaseConverter { return DriverManager.getConnection("jdbc:sqlite:" + icd.getCaseInputFolder().resolve(AUTOPSY_DB_FILE).toString(), "", ""); //NON-NLS } + private SingleUserCaseConverter() { + } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java b/Core/src/org/sleuthkit/autopsy/casemodule/SleuthkitErrorReporter.java similarity index 65% rename from Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java rename to Core/src/org/sleuthkit/autopsy/casemodule/SleuthkitErrorReporter.java index 52ea056333..15deff10d6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SleuthkitErrorReporter.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,51 +18,43 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.util.logging.Level; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.datamodel.SleuthkitCase; /** - * This class enables capturing errors and batching them for reporting on a - * no-more-than-x number of seconds basis. When created, you specify the minimum - * time between user notifications. When the time between notifications has - * expired, the next error encountered will cause a report to be shown to the - * user. + * Acts as a bridge between the Sleuthkit Java bindings classes and Autopsy by + * implementing the SleuthkitCase$ErrorObserver interface. All errors are + * written to the Autopsy logs. If a GUI is running, errors are also batched up + * and reported periodically to the user via the notification area in the lower + * right hand corner of the main application window. */ -class IntervalErrorReportData { +class SleuthkitErrorReporter implements SleuthkitCase.ErrorObserver { - private final Case currentCase; + private static final Logger LOGGER = Logger.getLogger(SleuthkitErrorReporter.class.getName()); + private final int milliSecondsBetweenReports; + private final String message; private long newProblems; private long totalProblems; private long lastReportedDate; - private final int milliSecondsBetweenReports; - private final String message; /** * Create a new IntervalErrorReprotData instance and subscribe for TSK error * notifications for the current case. * - * @param currentCase Case for which TSK errors should be tracked - * and displayed. * @param secondsBetweenReports Minimum number of seconds between reports. * It will not warn more frequently than this. * @param message The message that will be shown when warning - * the user + * the user. */ - IntervalErrorReportData(Case currentCase, int secondsBetweenReports, String message) { + SleuthkitErrorReporter(int secondsBetweenReports, String message) { this.newProblems = 0; this.totalProblems = 0; this.lastReportedDate = 0; // arm the first warning by choosing zero this.milliSecondsBetweenReports = secondsBetweenReports * 1000; // convert to milliseconds this.message = message; - this.currentCase = currentCase; - this.currentCase.getSleuthkitCase().addErrorObserver(this.currentCase); - } - - /** - * Un-subscribe from TSK error notifications for current case. - */ - void shutdown() { - this.currentCase.getSleuthkitCase().removeErrorObserver(this.currentCase); } /** @@ -73,18 +65,19 @@ class IntervalErrorReportData { * @param context The context in which the error occurred. * @param errorMessage A description of the error that occurred. */ - void addProblems(String context, String errorMessage) { + @Override + public void receiveError(String context, String errorMessage) { + LOGGER.log(Level.SEVERE, String.format("%s error in the SleuthKit layer: %s", context, errorMessage)); this.newProblems += 1; this.totalProblems += newProblems; - long currentTimeStamp = System.currentTimeMillis(); if ((currentTimeStamp - lastReportedDate) > milliSecondsBetweenReports) { this.lastReportedDate = currentTimeStamp; MessageNotifyUtil.Notify.error(message, context + ", " + errorMessage + " " + this.newProblems + " " - + NbBundle.getMessage(IntervalErrorReportData.class, "IntervalErrorReport.NewIssues") + + NbBundle.getMessage(SleuthkitErrorReporter.class, "IntervalErrorReport.NewIssues") + " " + this.totalProblems + " " - + NbBundle.getMessage(IntervalErrorReportData.class, "IntervalErrorReport.TotalIssues") + + NbBundle.getMessage(SleuthkitErrorReporter.class, "IntervalErrorReport.TotalIssues") + "."); this.newProblems = 0; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java index 83532c70ed..accb7d3f86 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JTabbedPane; import org.openide.nodes.Node; import org.openide.util.NbBundle; @@ -30,6 +29,7 @@ import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Top component that organizes all of the data content viewers. Doing a lookup @@ -42,17 +42,18 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; //@TopComponent.OpenActionRegistration(displayName = "#CTL_DataContentAction", preferredID = "DataContentTopComponent") public final class DataContentTopComponent extends TopComponent implements DataContent { - private static Logger logger = Logger.getLogger(DataContentTopComponent.class.getName()); + private static final Logger logger = Logger.getLogger(DataContentTopComponent.class.getName()); // reference to the "default" TC that always stays open private static DataContentTopComponent defaultInstance; + private static final long serialVersionUID = 1L; // set to true if this is the TC that always stays open and is the default place to display content - private boolean isDefault; + private final boolean isDefault; // the content panel holding tabs with content viewers private final DataContentPanel dataContentPanel; // contains a list of the undocked TCs - private static ArrayList newWindowList = new ArrayList(); + private static final ArrayList newWindowList = new ArrayList<>(); private static final String PREFERRED_ID = "DataContentTopComponent"; //NON-NLS private static final String DEFAULT_NAME = NbBundle.getMessage(DataContentTopComponent.class, "CTL_DataContentTopComponent"); private static final String TOOLTIP_TEXT = NbBundle.getMessage(DataContentTopComponent.class, "HINT_DataContentTopComponent"); @@ -67,8 +68,8 @@ public final class DataContentTopComponent extends TopComponent implements DataC dataContentPanel = new DataContentPanel(isDefault); add(dataContentPanel); - putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.valueOf(isDefault)); // prevent option to close compoment in GUI - logger.log(Level.INFO, "Created DataContentTopComponent instance: " + this); //NON-NLS + putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isDefault); // prevent option to close compoment in GUI + logger.log(Level.INFO, "Created DataContentTopComponent instance: {0}", this); //NON-NLS } /** @@ -91,24 +92,13 @@ public final class DataContentTopComponent extends TopComponent implements DataC return dctc; } - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - // //GEN-BEGIN:initComponents - private void initComponents() { - - setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - // End of variables declaration//GEN-END:variables - /** * Gets default instance. Do not use directly: reserved for *.settings files * only, i.e. deserialization routines; otherwise you could get a * non-deserialized defaultInstance. To obtain the singleton instance, use - * {@link #findInstance}. + * findInstance. + * + * @return */ public static synchronized DataContentTopComponent getDefault() { if (defaultInstance == null) { @@ -118,8 +108,10 @@ public final class DataContentTopComponent extends TopComponent implements DataC } /** - * Obtain the default DataContentTopComponent defaultInstance. Never call - * {@link #getDefault} directly! + * Obtain the default DataContentTopComponent default instance. Never call + * getDefault directly! + * + * @return The default DataContentTopComponent. */ public static synchronized DataContentTopComponent findInstance() { TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID); @@ -171,7 +163,17 @@ public final class DataContentTopComponent extends TopComponent implements DataC @Override public boolean canClose() { - return (!this.isDefault) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case + /* + * Only allow this window to be closed when there's no case opened or no + * data sources in the open case. + */ + // + try { + return !this.isDefault || Case.getCurrentCase().hasData() == false; + } catch (IllegalStateException ex) { + // Thrown if there is no current case. + return true; + } } @Override @@ -194,5 +196,19 @@ public final class DataContentTopComponent extends TopComponent implements DataC */ public static List getNewWindowList() { return newWindowList; - } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 4206dddc1d..76ee694aa6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -55,7 +55,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C private ExplorerManager explorerManager; private ExplorerManagerNodeSelectionListener emNodeSelectionListener; - + private Node rootNode; // Different DataResultsViewers @@ -276,7 +276,9 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C @Override public void propertyChange(PropertyChangeEvent evt) { - if (!Case.isCaseOpen()) { + try { + Case.getCurrentCase(); + } catch (IllegalStateException ex) { // Handle the in-between condition when case is being closed // and legacy selection events are pumped. return; @@ -334,7 +336,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C explorerManager.removePropertyChangeListener(emNodeSelectionListener); explorerManager = null; } - + // clear all set nodes for (UpdateWrapper drv : this.viewers) { drv.setNode(null); @@ -452,7 +454,16 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } public boolean canClose() { - return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case + /* + * Only allow this window to be closed when there's no case opened or no + * data sources in the open case. + */ + try { + return !this.isMain || Case.getCurrentCase().hasData() == false; + } catch (IllegalStateException ex) { + // Thrown if there is no current case. + return true; + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index 7761afe04d..c8fb127335 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -314,7 +314,16 @@ public class DataResultTopComponent extends TopComponent implements DataResult, @Override public boolean canClose() { - return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case + /* + * Only allow this window to be closed when there's no case opened or no + * data sources in the open case. + */ + try { + return !this.isMain || Case.getCurrentCase().hasData() == false; + } catch (IllegalStateException ex) { + // Thrown if there is no current case. + return true; + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java index f12839bd20..60823fc6cc 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -110,7 +110,9 @@ public class FXVideoPanel extends MediaViewVideoPanel { if (file.equals(currentFile)) { return; } - if (!Case.isCaseOpen()) { + try { + Case.getCurrentCase(); + } catch (IllegalStateException ex) { //handle in-between condition when case is being closed //and an image was previously selected return; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index 3d7dabb7a0..b8826cbad9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -140,12 +140,12 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi private void showErrorNode(String errorMessage, AbstractFile file) { final Button externalViewerButton = new Button(Bundle.MediaViewImagePanel_externalViewerButton_text(), new ImageView(EXTERNAL)); - externalViewerButton.setOnAction(actionEvent -> //fx ActionEvent + externalViewerButton.setOnAction(actionEvent + -> //fx ActionEvent /* * TODO: why is the name passed into the action constructor? it * means we duplicate this string all over the place -jm - */ - new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file)) + */ new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file)) .actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")) //Swing ActionEvent ); @@ -172,10 +172,13 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi readImageTask = ImageUtils.newReadImageTask(file); readImageTask.setOnSucceeded(succeeded -> { //Note that all error conditions are allready logged in readImageTask.succeeded() - if (!Case.isCaseOpen()) { + try { + Case.getCurrentCase(); + } catch (IllegalStateException ex) { /* - * handle in-between condition when case is being closed and - * an image was previously selected + * Thrown if the current case is closed, so handle + * in-between condition when case is being closed and an + * image was previously selected * * NOTE: I think this is unnecessary -jm */ @@ -198,10 +201,13 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi borderpane.setCursor(Cursor.DEFAULT); }); readImageTask.setOnFailed(failed -> { - if (!Case.isCaseOpen()) { + try { + Case.getCurrentCase(); + } catch (IllegalStateException ex) { /* - * handle in-between condition when case is being closed and - * an image was previously selected + * Thrown if no current case. Handle in-between condition + * when case is being closed and an image was previously + * selected * * NOTE: I think this is unnecessary -jm */ diff --git a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java index e1af3697a6..7b7c8b260b 100755 --- a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java +++ b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java @@ -38,6 +38,8 @@ import org.sleuthkit.autopsy.casemodule.Case; @ActionReference(path = "Menu/Help", position = 1437) public final class PerformancePanelAction extends CallableSystemAction { + private static final long serialVersionUID = 1L; + @Override public void performAction() { JDialog dialog = new PerformancePanel(); @@ -46,7 +48,12 @@ public final class PerformancePanelAction extends CallableSystemAction { @Override public boolean isEnabled() { - return Case.isCaseOpen(); + try { + Case.getCurrentCase(); + return true; + } catch (IllegalStateException ex) { + return false; + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 56b5f417d5..5ca8245bcc 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -349,7 +349,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - if (Case.isCaseOpen()) { Case currentCase = Case.getCurrentCase(); // close the top component if there's no image in this case @@ -440,7 +439,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } } - } + } catch (IllegalStateException ex) { + // Thrown if there is no current case } finally { this.setCursor(null); } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java index 70c8baeaa6..653faafc3a 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011 Basis Technology Corp. + * + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,14 +29,18 @@ import org.sleuthkit.autopsy.directorytree.FileSearchProvider; final class FileSearchAction extends CallableSystemAction implements FileSearchProvider { + private static final long serialVersionUID = 1L; private static FileSearchAction instance = null; FileSearchAction() { super(); - setEnabled(Case.isCaseOpen()); //no guarantee listener executed, so check here - + try { + Case.getCurrentCase(); + setEnabled(true); + } catch (IllegalStateException ex) { + setEnabled(false); + } Case.addPropertyChangeListener(new PropertyChangeListener() { - @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 60c664ec7c..41745ce498 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -330,11 +330,11 @@ public class IngestManager { // check whether a multi-user case is currently being processed try { - if (!Case.isCaseOpen() || Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { + if (Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { return; } } catch (IllegalStateException ignore) { - // thorown by Case.getCurrentCase() when no case is open + // Thrown by Case.getCurrentCase() when no case is open return; } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java index ebb1f8db90..b7e26e2222 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java @@ -80,6 +80,11 @@ public final class RunIngestAction extends CallableSystemAction implements Prese @Override public boolean isEnabled() { - return Case.isCaseOpen();// && Case.getCurrentCase().hasData(); + try { + Case.getCurrentCase(); + return true; + } catch (IllegalStateException ex) { + return false; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java index b490099b57..16ee878210 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,13 +36,18 @@ import org.sleuthkit.autopsy.corecomponents.AdvancedConfigurationCleanDialog; */ class HashDbPanelSearchAction extends CallableSystemAction { + private static final long serialVersionUID = 1L; static final String ACTION_NAME = NbBundle.getMessage(HashDbPanelSearchAction.class, "HashDbPanelSearchAction.actionName"); private static HashDbPanelSearchAction instance = null; HashDbPanelSearchAction() { super(); - setEnabled(Case.isCaseOpen()); //no guarantee listener executed, so check here - + try { + Case.getCurrentCase(); + setEnabled(true); + } catch (IllegalStateException ex) { + setEnabled(false); + } Case.addPropertyChangeListener(new PropertyChangeListener() { @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 806d1c7c78..c1d5771c99 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -42,11 +42,11 @@ import org.sleuthkit.datamodel.TskCoreException; * An implementation of the KeywordSearchService interface that uses Solr for * text indexing and search. */ -@ServiceProviders(value={ - @ServiceProvider(service=KeywordSearchService.class), - @ServiceProvider(service=AutopsyService.class)} +@ServiceProviders(value = { + @ServiceProvider(service = KeywordSearchService.class), + @ServiceProvider(service = AutopsyService.class)} ) -public class SolrSearchService implements KeywordSearchService, AutopsyService { +public class SolrSearchService implements KeywordSearchService, AutopsyService { private static final Logger logger = Logger.getLogger(IndexFinder.class.getName()); private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS @@ -141,8 +141,8 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService @Override public void close() throws IOException { } - - /** + + /** * * @param context * @@ -154,11 +154,11 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService /* * Autopsy service providers may not have case-level resources. */ - + // do a case subdirectory search to check for the existence and upgrade status of KWS indexes IndexFinder indexFinder = new IndexFinder(); List indexes = indexFinder.findAllIndexDirs(context.getCase()); - + // check if index needs upgrade Index currentVersionIndex; if (indexes.isEmpty()) { @@ -181,8 +181,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService if (indexSolrVersion > currentSolrVersion) { // oops! throw new AutopsyServiceException("Unable to find index to use for Case open"); - } - else if (indexSolrVersion == currentSolrVersion) { + } else if (indexSolrVersion == currentSolrVersion) { // latest Solr version but not latest schema. index should be used in read-only mode and not be upgraded. if (RuntimeProperties.runningWithGUI()) { // pop up a message box to indicate the read-only restrictions. @@ -195,8 +194,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService } // proceed with case open currentVersionIndex = indexToUpgrade; - } - else { + } else { // index needs to be upgraded to latest supported version of Solr if (RuntimeProperties.runningWithGUI()) { //pop up a message box to indicate the restrictions on adding additional @@ -210,7 +208,6 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService } // ELTODO Check for cancellation at whatever points are feasible - // Copy the existing index and config set into ModuleOutput/keywordsearch/data/solrX_schema_Y/ String newIndexDir = indexFinder.copyIndexAndConfigSet(context.getCase(), indexToUpgrade); @@ -224,15 +221,12 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService } } } - + // open core try { KeywordSearch.getServer().openCoreForCase(context.getCase(), currentVersionIndex); } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Failed to open or create core for %s", context.getCase().getCaseDirectory()), ex); //NON-NLS - if (RuntimeProperties.runningWithGUI()) { - MessageNotifyUtil.Notify.error(NbBundle.getMessage(KeywordSearch.class, "KeywordSearch.openCore.notification.msg"), ex.getMessage()); - } + throw new AutopsyServiceException(String.format("Failed to open or create core for %s", context.getCase().getCaseDirectory()), ex); } } @@ -251,11 +245,11 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService try { KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters(); /* - * TODO (AUT-2084): The following code - * KeywordSearch.CaseChangeListener gambles that any - * BlackboardResultWriters (SwingWorkers) will complete - * in less than roughly two seconds - */ + * TODO (AUT-2084): The following code + * KeywordSearch.CaseChangeListener gambles that any + * BlackboardResultWriters (SwingWorkers) will complete in less than + * roughly two seconds + */ Thread.sleep(2000); KeywordSearch.getServer().closeCore(); } catch (Exception ex) { From ac303858238d3707cbac1983fe1dca6ec16f8a71 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Sat, 21 Jan 2017 22:59:42 -0500 Subject: [PATCH 06/20] Refactoring and bug fixing for Case infrastructure --- .../sleuthkit/autopsy/actions/ExitAction.java | 22 +++--- .../sleuthkit/autopsy/casemodule/Case.java | 79 +++++++++++-------- .../autopsy/casemodule/CaseActionHelper.java | 1 + .../casemodule/OpenRecentCasePanel.java | 29 ++++--- .../casemodule/SingleUserCaseConverter.java | 3 - .../DirectoryTreeTopComponent.java | 14 +++- .../autopsy/filesearch/DateSearchFilter.java | 6 +- .../menuactions/DataContentDynamicMenu.java | 9 ++- .../menuactions/DataExplorerDynamicMenu.java | 7 +- .../autopsy/timeline/OpenTimelineAction.java | 7 +- .../autopsy/timeline/TimeLineController.java | 19 +++-- .../autoingest/AutoIngestManager.java | 2 +- 12 files changed, 117 insertions(+), 81 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java index 5db65c0ca6..62cc26b532 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-201y Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,7 @@ import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.coreutils.Logger; /** - * RJCTODO + * An action wired in to the Case/Exit menu item. */ @ActionRegistration(displayName = "Exit", iconInMenu = true) @ActionReference(path = "Menu/Case", position = 1000, separatorBefore = 999) @@ -39,13 +39,15 @@ final public class ExitAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - try { - Case.closeCurrentCase(); - } catch (CaseActionException ex) { - // RJCTODO: Pop up here - Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case", ex); //NON-NLS - } finally { - LifecycleManager.getDefault().exit(); - } + new Thread(() -> { + try { + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + // RJCTODO: Pop up here + Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case", ex); //NON-NLS + } finally { + LifecycleManager.getDefault().exit(); + } + }).start(); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 37e81f94eb..a7615403ec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -54,7 +54,6 @@ import java.util.stream.Stream; import javax.annotation.concurrent.GuardedBy; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -124,6 +123,14 @@ public class Case { private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS // RJCTODO private static final Logger LOGGER = Logger.getLogger(Case.class.getName()); + private static final ExecutorService caseActionExecutor = Executors.newSingleThreadExecutor(); + + /* + * A single-threaded executor used to guarantee that the case directory lock + * for the current case is acquired and released in the same thread, as + * required by the coordination service. + */ + private static ExecutorService caseLockingExecutor; /* * The following group of fields is state associated with the "current case" @@ -148,13 +155,6 @@ public class Case { */ @GuardedBy("Case.class") private static CoordinationService.Lock currentCaseDirLock; // RJCTODO: Move into case - /* - * A single-threaded executor used to guarantee that the case directory lock - * for the current case is acquired and released in the saem thread, as - * required by the coordination service. - */ - @GuardedBy("Case.class") - private static ExecutorService singleThreadedExecutor; /* * The collaboration monitor for the current case. It is specific to the * current case because it uses an event channel with a name derived from @@ -521,7 +521,7 @@ public class Case { * required by the coordination service. */ try { - Future future = getSingleThreadedExecutor().submit(() -> { + Future innerFuture = getCaseLockingExecutor().submit(() -> { Case newCase = new Case(); if (CaseType.SINGLE_USER_CASE == caseType) { newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator); @@ -553,17 +553,19 @@ public class Case { return newCase; }); if (RuntimeProperties.runningWithGUI()) { - listener.setCaseActionFuture(future); - ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); + listener.setCaseActionFuture(innerFuture); + SwingUtilities.invokeLater(() + -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); } - currentCase = future.get(); + currentCase = innerFuture.get(); LOGGER.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS if (RuntimeProperties.runningWithGUI()) { updateGUIForCaseOpened(); } eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); + } catch (InterruptedException | ExecutionException ex) { - if (CaseType.SINGLE_USER_CASE == caseType) { + if (CaseType.MULTI_USER_CASE == caseType) { releaseSharedCaseDirLock(caseName); } if (ex instanceof InterruptedException) { @@ -571,13 +573,11 @@ public class Case { } else { throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(ex.getCause().getMessage()), ex); } - } catch (Exception ex) { - // RJCTODO: Remove, why not catching NPE's? - ex.getMessage(); } finally { - progressIndicator.finish(""); // RJCTODO: Is this right message? + progressIndicator.finish(""); // RJCTODO: Is this right message? Nope if (RuntimeProperties.runningWithGUI()) { - ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); + SwingUtilities.invokeLater(() + -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); } } } @@ -645,7 +645,7 @@ public class Case { CaseType caseType = metadata.getCaseType(); String caseName = metadata.getCaseName(); try { - Future future = getSingleThreadedExecutor().submit(() -> { + Future future = getCaseLockingExecutor().submit(() -> { Case openedCase = new Case(); if (CaseType.SINGLE_USER_CASE == caseType) { openedCase.open(metadata, progressIndicator); @@ -672,7 +672,8 @@ public class Case { }); if (RuntimeProperties.runningWithGUI()) { listener.setCaseActionFuture(future); - ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); + SwingUtilities.invokeLater(() + -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); } future.get(); currentCase = future.get(); @@ -693,7 +694,8 @@ public class Case { } finally { progressIndicator.finish(""); // RJCTODO: Is this right message? if (RuntimeProperties.runningWithGUI()) { - ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); + SwingUtilities.invokeLater(() + -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); } } } catch (CaseMetadataException ex) { @@ -755,7 +757,7 @@ public class Case { * is open is released in the same thread in which it was acquired, * as is required by the coordination service. */ - Future future = getSingleThreadedExecutor().submit(() -> { + Future future = getCaseLockingExecutor().submit(() -> { if (CaseType.SINGLE_USER_CASE == currentCase.getCaseType()) { currentCase.close(progressIndicator); } else { @@ -774,7 +776,8 @@ public class Case { return null; }); if (RuntimeProperties.runningWithGUI()) { - ((ModalDialogProgressIndicator) progressIndicator).setVisible(true); + SwingUtilities.invokeLater(() + -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); } future.get(); } catch (InterruptedException | ExecutionException ex) { @@ -785,10 +788,10 @@ public class Case { } } finally { currentCase = null; - progressIndicator.finish(""); // RJCTODO: Is this right message? + progressIndicator.finish(""); // RJCTODO: Is this right message? Nope if (RuntimeProperties.runningWithGUI()) { - updateGUIForCaseClosed(); - ((ModalDialogProgressIndicator) progressIndicator).setVisible(false); + SwingUtilities.invokeLater(() + -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); } } } @@ -838,7 +841,7 @@ public class Case { progressIndicator = new LoggingProgressIndicator(); } progressIndicator.start(Bundle.Case_progressMessage_preparingToCloseCase()); - Future future = getSingleThreadedExecutor().submit(() -> { + Future future = getCaseLockingExecutor().submit(() -> { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { cleanupDeletedCase(metadata); } else { @@ -1047,7 +1050,7 @@ public class Case { * Use the main window of the desktop application to initialize the * application name. Should be called BEFORE any case is opened or created. */ - private synchronized static void getAppNameFromMainWindow() { + private static void getAppNameFromMainWindow() { /* * This is tricky and fragile. What looks like lazy initialization of * the appName field is actually getting the application name from the @@ -1326,12 +1329,11 @@ public class Case { * * @return The executor */ - private synchronized static ExecutorService getSingleThreadedExecutor() { - if (null == singleThreadedExecutor) { - singleThreadedExecutor = Executors.newSingleThreadExecutor(); + private static ExecutorService getCaseLockingExecutor() { + if (null == caseLockingExecutor) { + caseLockingExecutor = Executors.newSingleThreadExecutor(); } - return singleThreadedExecutor; - + return caseLockingExecutor; } /** @@ -1550,6 +1552,17 @@ public class Case { return timezones; } + /** + * Sets the name of the keyword search index for the case. + * + * @param textIndexName The text index name. + * + * @throws CaseMetadataException + */ + public void setTextIndexName(String textIndexName) throws CaseMetadataException { + getCaseMetadata().setTextIndexName(textIndexName); + } + /** * Gets the name of the keyword search index for the case. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java index 91242fe477..46eb180ba4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java @@ -60,6 +60,7 @@ class CaseActionHelper { } } WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new T try { Case.closeCurrentCase(); } catch (CaseActionException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java index 91f007c81d..0c70fbbd5b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java @@ -25,6 +25,7 @@ import java.io.File; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.JTable; +import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; @@ -121,18 +122,22 @@ class OpenRecentCasePanel extends javax.swing.JPanel { RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore StartupWindowProvider.getInstance().open(); } else { - try { - Case.openCurrentCase(casePath); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - StartupWindowProvider.getInstance().open(); - } + new Thread(() -> { + try { + Case.openCurrentCase(casePath); + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().open(); + }); + } + }).start(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index e61cdf4bb0..87c010e25b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -1173,7 +1173,4 @@ public class SingleUserCaseConverter { return DriverManager.getConnection("jdbc:sqlite:" + icd.getCaseInputFolder().resolve(AUTOPSY_DB_FILE).toString(), "", ""); //NON-NLS } - private SingleUserCaseConverter() { - } - } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 5ca8245bcc..e8ba24d2e9 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -490,7 +490,11 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat @Override public boolean canClose() { - return !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case + try { + return Case.getCurrentCase().hasData() == false; + } catch (IllegalStateException ex) { + return true; + } } /** @@ -614,12 +618,14 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * @param newNodes */ private void respondSelection(final Node[] oldNodes, final Node[] newNodes) { - if (!Case.isCaseOpen()) { + try { + Case.getCurrentCase(); + } catch (IllegalStateException ex) { //handle in-between condition when case is being closed //and legacy selection events are pumped - return; + return; } - + // Some lock that prevents certain Node operations is set during the // ExplorerManager selection-change, so we must handle changes after the // selection-change event is processed. diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java index 843934d3a8..39b66fb244 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java @@ -166,7 +166,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { List timeZones = new ArrayList<>(); - if (Case.isCaseOpen()) { + try { // get the latest case Case currentCase = Case.getCurrentCase(); // get the most updated case @@ -195,6 +195,8 @@ class DateSearchFilter extends AbstractFileSearchFilter { String item = String.format("(GMT%+d:%02d) %s", hour, minutes, id); //NON-NLS timeZones.add(item); } + } catch (IllegalStateException ex) { + // No current case. } return timeZones; @@ -207,7 +209,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { @Override public boolean isValid() { - return this.getComponent().isValidSearch(); + return this.getComponent().isValidSearch(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java b/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java index 8e97875930..40ae79064c 100644 --- a/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java +++ b/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java @@ -50,10 +50,11 @@ class DataContentDynamicMenu extends JMenuItem implements DynamicMenuContent { defaultItem.addActionListener(new OpenTopComponentAction(contentWin)); - if (!Case.isCaseOpen() || Case.getCurrentCase().hasData() == false) { - defaultItem.setEnabled(false); // disable the menu items when no case is opened - } else { - defaultItem.setEnabled(true); // enable the menu items when there's a case opened / created + try { + Case currentCase = Case.getCurrentCase(); + defaultItem.setEnabled(currentCase.hasData()); + } catch (IllegalStateException ex) { + defaultItem.setEnabled(false); // disable the menu when no case is opened } comps[counter++] = defaultItem; diff --git a/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java b/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java index 11afe7cf03..16776ecdf4 100644 --- a/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java +++ b/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java @@ -53,10 +53,11 @@ class DataExplorerDynamicMenu extends JMenuItem implements DynamicMenuContent { JMenuItem item = new JMenuItem(explorerWin.getName()); item.addActionListener(new OpenTopComponentAction(explorerWin)); - if (!Case.isCaseOpen() || Case.getCurrentCase().hasData() == false) { + try { + Case currentCase = Case.getCurrentCase(); + item.setEnabled(currentCase.hasData()); + } catch (IllegalStateException ex) { item.setEnabled(false); // disable the menu when no case is opened - } else { - item.setEnabled(true); // enable the menu if the case is opened or created } comps[i++] = item; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 04fd16c71d..d7a35e0103 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -81,7 +81,12 @@ public final class OpenTimelineAction extends CallableSystemAction implements Pr * disabled that check because if it is executed while a data source is * being added, it blocks the edt */ - return Case.isCaseOpen() && FX_INITED; + try { + Case.getCurrentCase(); + return FX_INITED; + } catch (IllegalStateException ex) { + return false; + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 9ad1760ef7..fc102541a2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -453,8 +453,8 @@ public class TimeLineController { TimeLineController.this.showFullRange(); } else { //prompt user to pick specific event and time range - ShowInTimelineDialog showInTimelineDilaog = - (file == null) + ShowInTimelineDialog showInTimelineDilaog + = (file == null) ? new ShowInTimelineDialog(TimeLineController.this, artifact) : new ShowInTimelineDialog(TimeLineController.this, file); Optional dialogResult = showInTimelineDilaog.showAndWait(); @@ -569,13 +569,16 @@ public class TimeLineController { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void showTimeLine(AbstractFile file, BlackboardArtifact artifact) { // listen for case changes (specifically images being added, and case changes). - if (Case.isCaseOpen() && !listeningToAutopsy) { - IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener); - IngestManager.getInstance().addIngestJobEventListener(ingestJobListener); - Case.addPropertyChangeListener(caseListener); - listeningToAutopsy = true; + try { + Case.getCurrentCase(); + if (!listeningToAutopsy) { + IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener); + IngestManager.getInstance().addIngestJobEventListener(ingestJobListener); + Case.addPropertyChangeListener(caseListener); + listeningToAutopsy = true; + } + } catch (IllegalStateException ex) { } - Platform.runLater(() -> promptForRebuild(file, artifact)); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 300f787a53..a08ef06090 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1892,7 +1892,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } finally { try { - caseForJob.closeCase(); + Case.closeCurrentCase(); } catch (CaseActionException ex) { Manifest manifest = currentJob.getManifest(); throw new CaseManagementException(String.format("Error closing case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); From 18e3b39a3fc5132caa5d3f1aa90b23677d3d7369 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Sun, 22 Jan 2017 00:02:02 -0500 Subject: [PATCH 07/20] Bug fixing and polishing new case create/open/close infrastructure --- .../sleuthkit/autopsy/casemodule/Case.java | 11 +- .../autopsy/casemodule/CaseActionHelper.java | 3 +- .../autopsy/casemodule/CaseCloseAction.java | 54 ++++++++-- .../autopsy/casemodule/CaseOpenAction.java | 101 ++++++++++++------ 4 files changed, 123 insertions(+), 46 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index a7615403ec..1b91d37a82 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -749,6 +749,7 @@ public class Case { } progressIndicator.start(Bundle.Case_progressMessage_preparingToCloseCase()); +// LOGGER.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS RJCTOD try { /* * Closing a case is always done in the same non-UI thread that @@ -772,7 +773,6 @@ public class Case { releaseSharedCaseDirLock(caseName); } } - currentCase = null; return null; }); if (RuntimeProperties.runningWithGUI()) { @@ -780,11 +780,16 @@ public class Case { -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); } future.get(); +// LOGGER.log(Level.INFO, "Closed case {0} in directory = {1}", new Object[]{metadata.getCaseName(), metadata.getCaseDirectory()}); //NON-NLS RJCTODO + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseClosed(); + } + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); } catch (InterruptedException | ExecutionException ex) { if (ex instanceof ExecutionException) { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); // RJCTODO } else { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); // RJCTODO } } finally { currentCase = null; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java index 46eb180ba4..a488a05fb1 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java @@ -42,7 +42,7 @@ class CaseActionHelper { * @param * @return True if the current case, if any, is closed, false otherwise. */ - // RJCTODO: Be sure to test this! + // RJCTODO: Be sure to test this! Discard this class static boolean closeCaseAndContinueAction() { if (IngestManager.getInstance().isIngestRunning()) { NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( @@ -60,7 +60,6 @@ class CaseActionHelper { } } WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - new T try { Case.closeCurrentCase(); } catch (CaseActionException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java index 1da9ab0061..3090d3d18b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java @@ -19,10 +19,16 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Component; +import java.awt.Cursor; import java.awt.event.ActionEvent; +import java.util.concurrent.ExecutionException; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; +import javax.swing.SwingWorker; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; @@ -31,13 +37,12 @@ import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action to close the current case and pop up the start up window that - * allows a user to open anothjer case. This action should only be enabled when - * there is a current case. - * - * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). + * allows a user to open another case. */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.CaseCloseAction") @ActionRegistration(displayName = "#CTL_CaseCloseAct", lazy = false) @@ -66,9 +71,46 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese */ @Override public void actionPerformed(ActionEvent e) { - if (CaseActionHelper.closeCaseAndContinueAction()) { - StartupWindowProvider.getInstance().open(); + /* + * If ingest is running, give the user the option to abort changing + * cases. + */ + if (IngestManager.getInstance().isIngestRunning()) { + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), // RJCTODO + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), // RJCTODO + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object response = DialogDisplayer.getDefault().notify(descriptor); + if (DialogDescriptor.NO_OPTION == response) { + return; + } } + + /* + * Close the case. + */ + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + Case.closeCurrentCase(); + return null; + } + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + // RJCTODO: Pop up error and log + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + StartupWindowProvider.getInstance().open(); + } + }.execute(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 62604a2d39..26404fbfd7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -22,11 +22,16 @@ import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JOptionPane; +import javax.swing.SwingWorker; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; @@ -35,11 +40,10 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action that opens an existing case. - * - * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). */ @ServiceProvider(service = CaseOpenAction.class) public final class CaseOpenAction extends CallableSystemAction implements ActionListener { @@ -72,41 +76,68 @@ public final class CaseOpenAction extends CallableSystemAction implements Action */ @Override public void actionPerformed(ActionEvent e) { - if (CaseActionHelper.closeCaseAndContinueAction()) { - - /** - * Pop up a file chooser to allow the user to select a case metadata - * file (.aut file). - */ - int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); - if (retval == JFileChooser.APPROVE_OPTION) { - /* - * Close the startup window, if it is open. - */ - StartupWindowProvider.getInstance().close(); - - /* - * Try to open the case associated with the case metadata file - * the user selected. - */ - final String path = fileChooser.getSelectedFile().getPath(); - String dirPath = fileChooser.getSelectedFile().getParent(); - ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator))); - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - Case.openCurrentCase(path); - } catch (CaseActionException ex) { - LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - StartupWindowProvider.getInstance().open(); - } + /* + * If ingest is running, give the user the option to abort changing + * cases. + */ + if (IngestManager.getInstance().isIngestRunning()) { + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), // RJCTODO + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), // RJCTODO + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object response = DialogDisplayer.getDefault().notify(descriptor); + if (DialogDescriptor.NO_OPTION == response) { + return; } } + + /** + * Pop up a file chooser to allow the user to select a case metadata + * file (.aut file). + */ + int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); + if (retval == JFileChooser.APPROVE_OPTION) { + /* + * Close the startup window, if it is open. + */ + StartupWindowProvider.getInstance().close(); + + /* + * Try to open the case associated with the case metadata file the + * user selected. + */ + final String path = fileChooser.getSelectedFile().getPath(); + String dirPath = fileChooser.getSelectedFile().getParent(); + ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator))); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + Case.openCurrentCase(path); + return null; + } + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().open(); + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }.execute(); + } } @Override From e8b74dd1db781b1e1d190658992eee99f286148c Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Sun, 22 Jan 2017 17:20:38 -0500 Subject: [PATCH 08/20] Bug fixes for multi-user case infrastructure --- Core/nbproject/project.xml | 1 + .../sleuthkit/autopsy/actions/ExitAction.java | 3 +- .../autopsy/casemodule/Bundle.properties | 7 - .../autopsy/casemodule/Bundle_ja.properties | 7 - .../sleuthkit/autopsy/casemodule/Case.java | 657 ++++++++++-------- .../autopsy/casemodule/CaseActionHelper.java | 78 --- .../autopsy/casemodule/CaseCloseAction.java | 11 +- .../autopsy/casemodule/CaseMetadata.java | 53 +- .../autopsy/casemodule/CaseOpenAction.java | 10 +- .../casemodule/CasePropertiesForm.java | 10 +- .../casemodule/CollaborationMonitor.java | 13 +- .../casemodule/DeleteCurrentCaseAction.java | 6 +- .../autopsy/casemodule/ImageDSProcessor.java | 2 +- .../casemodule/LocalDiskDSProcessor.java | 2 +- .../casemodule/LocalFilesDSProcessor.java | 2 +- .../casemodule/NewCaseWizardAction.java | 51 +- .../casemodule/NewCaseWizardPanel1.java | 5 +- .../casemodule/OpenRecentCasePanel.java | 2 +- .../autopsy/casemodule/RecentCases.java | 31 +- .../autopsy/casemodule/RecentItems.java | 68 +- .../casemodule/SingleUserCaseConverter.java | 4 +- .../autopsy/casemodule/UpdateRecentCases.java | 33 +- .../autopsy/core/RuntimeProperties.java | 25 +- .../autopsy/corecomponents/Installer.java | 13 +- .../AutoIngestDataSourceProcessor.java | 5 +- .../AutopsyService.java | 2 +- .../LoggingProgressIndicator.java | 5 +- .../ModalDialogProgressIndicator.java | 6 +- .../ProgressIndicator.java | 2 +- .../ProgressPanel.form | 2 +- .../ProgressPanel.java | 11 +- .../autopsy/ingest/IngestManager.java | 2 +- .../KeywordSearchService.java | 34 +- .../hashdatabase/SilentProgressIndicator.java | 2 +- .../sleuthkit/autopsy/report/ReportExcel.java | 2 +- .../autopsy/report/ReportGenerator.java | 2 +- .../sleuthkit/autopsy/report/ReportHTML.java | 4 +- .../taggedhashes/AddTaggedHashesToHashDb.java | 2 +- .../autopsy/test/TestAutopsyService.java | 6 +- .../actions/SaveSnapshotAsReport.java | 2 +- .../autoingest/AutoIngestCase.java | 2 - .../AutoIngestCaseDeletedEvent.java | 10 - .../autoingest/AutoIngestCaseManager.java | 4 +- .../AutoIngestCasePrioritizedEvent.java | 2 +- .../autoingest/AutoIngestJob.java | 136 +--- .../autoingest/AutoIngestJobEvent.java | 11 - .../autoingest/AutoIngestJobLogger.java | 2 +- .../autoingest/AutoIngestJobStartedEvent.java | 3 - .../autoingest/AutoIngestJobStatusEvent.java | 3 - .../autoingest/AutoIngestManager.java | 385 +++++----- .../autoingest/AutopsyManifestFileParser.java | 31 - .../experimental/autoingest/Manifest.java | 49 -- .../autoingest/ManifestFileParser.java | 6 - .../autoingest/ManifestNodeData.java | 3 - .../experimental/autoingest/PathUtils.java | 4 +- .../autopsy/keywordsearch/IndexFinder.java | 2 +- .../autopsy/keywordsearch/IndexUpgrader.java | 2 +- .../keywordsearch/SolrSearchService.java | 45 +- 58 files changed, 798 insertions(+), 1085 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java rename Core/src/org/sleuthkit/autopsy/{corecomponentinterfaces => framework}/AutoIngestDataSourceProcessor.java (92%) rename Core/src/org/sleuthkit/autopsy/{corecomponentinterfaces => framework}/AutopsyService.java (99%) rename Core/src/org/sleuthkit/autopsy/{casemodule => framework}/LoggingProgressIndicator.java (93%) rename Core/src/org/sleuthkit/autopsy/{casemodule => framework}/ModalDialogProgressIndicator.java (97%) rename Core/src/org/sleuthkit/autopsy/{corecomponentinterfaces => framework}/ProgressIndicator.java (98%) rename Core/src/org/sleuthkit/autopsy/{casemodule => framework}/ProgressPanel.form (91%) rename Core/src/org/sleuthkit/autopsy/{casemodule => framework}/ProgressPanel.java (96%) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 28a8815bca..6e9df6d3f1 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -242,6 +242,7 @@ org.sleuthkit.autopsy.events org.sleuthkit.autopsy.externalresults org.sleuthkit.autopsy.filesearch + org.sleuthkit.autopsy.framework org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java index 62cc26b532..8a76b6855e 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -43,8 +43,7 @@ final public class ExitAction implements ActionListener { try { Case.closeCurrentCase(); } catch (CaseActionException ex) { - // RJCTODO: Pop up here - Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case", ex); //NON-NLS + Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case on exit", ex); //NON-NLS } finally { LifecycleManager.getDefault().exit(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index a3fbca6426..f7a21711f5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -98,12 +98,9 @@ AddImageWizardIngestConfigVisual.getName.text=Configure Ingest Modules AddImageWizardIterator.stepXofN=Step {0} of {1} AddLocalFilesTask.localFileAdd.progress.text=Adding\: {0}/{1} Case.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open\! -Case.create.exception.msg=Error creating a case\: {0} in dir {1} Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-user. -Case.open.exception.blankCase.msg=Case name is blank. Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the database with the following path has been made\:\n {0} Case.open.msgDlg.updated.title=Case Database Schema Update -Case.open.exception.checkFile.msg=Case file must have {0} extension. Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-user. Case.checkImgExist.confDlg.doesntExist.msg={0} has detected that one of the images associated with \n\ this case are missing. Would you like to search for them now?\n\ @@ -113,9 +110,6 @@ Please note that you will still be able to browse directories and generate repor if you choose No, but you will not be able to view file content or run the ingest process. Case.checkImgExist.confDlg.doesntExist.title=Missing Image Case.addImg.exception.msg=Error adding image to the case -Case.closeCase.exception.msg=Error while trying to close the current case. -Case.deleteCase.exception.msg=Error deleting the case dir\: {0} -Case.deleteCase.exception.msg2=Error deleting the case dir\: {0} Case.updateCaseName.exception.msg=Error while trying to update the case name. Case.updateExaminer.exception.msg=Error while trying to update the examiner. Case.updateCaseNum.exception.msg=Error while trying to update the case number. @@ -240,4 +234,3 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close -ProgressPanel.progressMessage.text=Message diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 54716ba8cb..7f2500d454 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -1,4 +1,3 @@ -Case.closeCase.exception.msgCTL_AddImage=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0... CTL_AddImageButton=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0 CTL_CaseCloseAct=\u30b1\u30fc\u30b9\u3092\u9589\u3058\u308b CTL_CaseNewAction=\u65b0\u898f\u30b1\u30fc\u30b9... @@ -80,21 +79,15 @@ AddImageWizardIngestConfigVisual.getName.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30 AddImageWizardIterator.stepXofN=\u30b9\u30c6\u30c3\u30d7{0}\uff0f{1} AddLocalFilesTask.localFileAdd.progress.text=\u8ffd\u52a0\u4e2d\uff1a{0}/{1} Case.getCurCase.exception.noneOpen=\u4f5c\u696d\u4e2d\u306e\u30b1\u30fc\u30b9\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\uff1b\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\uff01 -Case.create.exception.msg=\u30b1\u30fc\u30b9\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a\u30c7\u30a3\u30ec\u30af\u30c8\u30ea{1}\u306e{0} -Case.open.exception.blankCase.msg=\u30b1\u30fc\u30b9\u540d\u304c\u7a7a\u767d\u3067\u3059\u3002 Case.open.msgDlg.updated.msg=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002\n\u6b21\u306e\u30d1\u30b9\u3092\u6301\u3064\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u30b3\u30d4\u30fc\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\uff1a\n\ {0} Case.open.msgDlg.updated.title=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0 -Case.open.exception.checkFile.msg=\u30b1\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u306f{0}\u62e1\u5f35\u5b50\u304c\u5fc5\u8981\u3067\u3059\u3002 Case.checkImgExist.confDlg.doesntExist.msg={0} \u304c\u3053\u306e\u30b1\u30fc\u30b9\u306b\u95a2\u9023\u3059\u308b\u30a4\u30e1\u30fc\u30b8\u306e\u3046\u3061\uff11\u3064\u304c\u6b20\u843d\u3057\u3066\u3044\u308b\u306e\u3092\u691c\u51fa\u3057\u307e\u3057\u305f\u3002\u305d\u308c\u3092\u4eca\u304b\u3089\u691c\u7d22\u3057\u307e\u3059\u304b\uff1f\n\n\ \u4ee5\u524d\u3001\u30a4\u30e1\u30fc\u30b8\u306f\u6b21\u306b\u3042\u308a\u307e\u3057\u305f\uff1a\n\ {1}\n\ \u3044\u3044\u3048\u3092\u9078\u629e\u3057\u3066\u3082\u3001\u4eca\u5f8c\u3082\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u95b2\u89a7\u3057\u3001\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u304c\u3067\u304d\u307e\u3059\u304c\u3001\n\u30d5\u30a1\u30a4\u30eb\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u8868\u793a\u307e\u305f\u306f\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30d7\u30ed\u30bb\u30b9\u306e\u5b9f\u884c\u304c\u3067\u304d\u306a\u304f\u306a\u308a\u307e\u3059\u3002 Case.checkImgExist.confDlg.doesntExist.title=\u6b20\u843d\u3057\u3066\u3044\u308b\u30a4\u30e1\u30fc\u30b8 Case.addImg.exception.msg=\u30b1\u30fc\u30b9\u306b\u30a4\u30e1\u30fc\u30b8\u3092\u8ffd\u52a0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f -Case.closeCase.exception.msg=\u4f5c\u696d\u4e2d\u306e\u30b1\u30fc\u30b9\u3092\u9589\u3058\u308b\u6700\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 -Case.deleteCase.exception.msg=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a{0} -Case.deleteCase.exception.msg2=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a{0} Case.updateCaseName.exception.msg=\u30b1\u30fc\u30b9\u540d\u3092\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 Case.updateExaminer.exception.msg=\u8abf\u67fb\u62c5\u5f53\u8005\u3092\u66f4\u65b0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 Case.updateCaseNum.exception.msg=\u30b1\u30fc\u30b9\u756a\u53f7\u3092\u66f4\u65b0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 1b91d37a82..c41d225b6d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -51,7 +51,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.concurrent.GuardedBy; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openide.util.Lookup; @@ -76,22 +75,26 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService.CaseContext; import org.sleuthkit.autopsy.corecomponentinterfaces.CoreComponentControl; -import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.DriveUtils; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; +import org.sleuthkit.autopsy.framework.AutopsyService; +import org.sleuthkit.autopsy.framework.AutopsyService.CaseContext; +import org.sleuthkit.autopsy.framework.LoggingProgressIndicator; +import org.sleuthkit.autopsy.framework.ModalDialogProgressIndicator; +import org.sleuthkit.autopsy.framework.ProgressIndicator; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.CaseDbConnectionInfo; @@ -107,13 +110,11 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class Case { - /* - * Constants. - */ private static final int NAME_LOCK_TIMOUT_HOURS = 12; private static final int SHARED_DIR_LOCK_TIMOUT_HOURS = 12; private static final int RESOURCE_LOCK_TIMOUT_HOURS = 12; private static final int MAX_SANITIZED_CASE_NAME_LEN = 47; + private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db"; private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS private static final String CACHE_FOLDER = "Cache"; //NON-NLS private static final String EXPORT_FOLDER = "Export"; //NON-NLS @@ -121,55 +122,33 @@ public class Case { private static final String REPORTS_FOLDER = "Reports"; //NON-NLS private static final String TEMP_FOLDER = "Temp"; //NON-NLS private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; - private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS // RJCTODO - private static final Logger LOGGER = Logger.getLogger(Case.class.getName()); - private static final ExecutorService caseActionExecutor = Executors.newSingleThreadExecutor(); + private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS + private static final Logger logger = Logger.getLogger(Case.class.getName()); + private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); /* - * A single-threaded executor used to guarantee that the case directory lock - * for the current case is acquired and released in the same thread, as - * required by the coordination service. - */ - private static ExecutorService caseLockingExecutor; - - /* - * The following group of fields is state associated with the "current case" - * concept. This state is managed by a set of static methods that could be - * refactored into a case manager class, which would be a step towards - * supporting multiple open cases. RJCTODO: Write a story about this + * The application name, used to make the title of the main application + * window [application] name when there is no open case and [application + * name]-[curent case display name] when there is an open case. Initialized + * by getting the main window title before a case has been opened. * * TODO (JIRA-2231): Make the application name a RuntimeProperties item set * by Installers. */ - @GuardedBy("Case.class") private static String appName; + /* - * The one and only open case. + * The following fields are the state associated with the currently open + * case. The coordination service lock on the case directory of the + * currently open is used to prevent deletion of a multi-user case by this + * node if it is open in another node. The case locking executor is a + * single-threaded executor to guarantee that the case directory lock is + * acquired and released in the same thread, as required by the coordination + * service. */ - @GuardedBy("Case.class") private static Case currentCase; - /* - * A coordination service lock on the case directory of the one and only - * open case. Used to prevent deletion of a multi-user case by this node if - * it is open in another node. - */ - @GuardedBy("Case.class") - private static CoordinationService.Lock currentCaseDirLock; // RJCTODO: Move into case - /* - * The collaboration monitor for the current case. It is specific to the - * current case because it uses an event channel with a name derived from - * the name of the current case. - */ - @GuardedBy("Case.class") - private static CollaborationMonitor collaborationMonitor; // RJCTODO: Move into case - /* - * The publisher for case events, both locally and, if the case is a - * multi-user case, to other nodes. This is part of the state for the - * current case because it opens and closes an event channel with a name - * derived from the name of the current case. - */ - @GuardedBy("Case.class") - private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); + private static CoordinationService.Lock currentCaseDirLock; + private static ExecutorService caseLockingExecutor; /* * Case instance data. @@ -177,6 +156,7 @@ public class Case { private CaseMetadata caseMetadata; private SleuthkitCase caseDb; private SleuthkitErrorReporter sleuthkitErrorReporter; + private CollaborationMonitor collaborationMonitor; private Services services; private boolean hasDataSources; @@ -221,9 +201,12 @@ public class Case { /** * Gets the localized display name for this case type. * - * @return The dis[play name. + * @return The display name. */ - @Messages({"Case_caseType_singleUser=Single-user case", "Case_caseType_multiUser=Multi-user case"}) + @Messages({ + "Case_caseType_singleUser=Single-user case", + "Case_caseType_multiUser=Multi-user case" + }) String getLocalizedDisplayName() { if (fromString(typeName) == SINGLE_USER_CASE) { return Bundle.Case_caseType_singleUser(); @@ -242,12 +225,12 @@ public class Case { } /** - * Tests the equality of the type name of this case type with a case - * type name. + * Tests the equality of the type name of this case type with another + * case type name. * - * @param otherTypeName A case type name + * @param otherTypeName A case type name, * - * @return True or false + * @return True or false, * * @deprecated Do not use. */ @@ -259,8 +242,8 @@ public class Case { }; /** - * An enumeration of the events (property change events) a case may publish - * (fire). + * An enumeration of the case events (property change events) a case may + * publish (fire). */ public enum Events { @@ -272,8 +255,8 @@ public class Case { NAME, /** * The number of the current case has changed. The old value of the - * PropertyChangeEvent is the old number (type: String), the new value - * is the new case number (type: String). + * PropertyChangeEvent is the old case number (type: String), the new + * value is the new case number (type: String). */ NUMBER, /** @@ -287,7 +270,7 @@ public class Case { * made. The old and new values of the PropertyChangeEvent are null. * Cast the PropertyChangeEvent to * org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent to - * access event data. + * access additional event data. */ ADDING_DATA_SOURCE, /** @@ -295,7 +278,7 @@ public class Case { * The old and new values of the PropertyChangeEvent are null. Cast the * PropertyChangeEvent to * org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent - * to access event data. + * to access additional event data. */ ADDING_DATA_SOURCE_FAILED, /** @@ -303,7 +286,7 @@ public class Case { * of the PropertyChangeEvent is null, the new value is the newly-added * data source (type: Content). Cast the PropertyChangeEvent to * org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent to - * access event data. + * access additional event data. */ DATA_SOURCE_ADDED, /** @@ -327,7 +310,7 @@ public class Case { */ REPORT_ADDED, /** - * A report has been added to the current case. The old value of the + * A report has been deleted from the current case. The old value of the * PropertyChangeEvent is the report (type: Report), the new value is * null. */ @@ -340,8 +323,7 @@ public class Case { BLACKBOARD_ARTIFACT_TAG_ADDED, /** * A tag has been removed from an artifact associated with the current - * case. The old value of the PropertyChangeEvent is is the tag info - * (type: + * case. The old value of the PropertyChangeEvent is the tag info (type: * BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo), * the new value is null. */ @@ -428,11 +410,7 @@ public class Case { * Checks if a case display name is valid, i.e., does not include any * characters that cannot be used in file names. * - * - * RJCTODO: Not really needed any more with display name concept; what - * happens in auto ingest? Make sure these things are in sanitize...etc. - * - * @param caseName The case name. + * @param caseName The candidate case name. * * @return True or false. */ @@ -462,13 +440,16 @@ public class Case { * exception. */ @Messages({ - "Case.creationException.illegalCaseName=Could not create case: case name contains illegal characters.", "# {0} - exception message", "Case.creationException.couldNotCreateCase=Could not create case: {0}", + "Case.exceptionMessage.illegalCaseName=Case name contains illegal characters.", + "Case.exceptionMessage.lockAcquisitionInterrupted=Acquiring locks was interrupted.", "Case.progressIndicatorTitle.creatingCase=Creating Case", - "Case.progressIndicatorCancelButton.cancelLabel=Cancel", - "Case.progressMessage.preparingToCreateCase=Preparing to create case...", - "Case.progressMessage.acquiringLocks=Acquiring locks...",}) - public synchronized static void createCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + "Case.progressIndicatorCancelButton.label=Cancel", + "Case.progressMessage.preparing=Preparing...", + "Case.progressMessage.acquiringLocks=Acquiring locks...", + "Case.progressMessage.finshing=Finishing..." + }) + public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { /* * If running with the desktop GUI, this needs to be done before any * cases are created or opened so that the application name can be @@ -496,9 +477,9 @@ public class Case { try { caseName = sanitizeCaseName(caseDisplayName); } catch (IllegalCaseNameException ex) { - throw new CaseActionException(Bundle.Case_creationException_illegalCaseName(), ex); + throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(Bundle.Case_exceptionMessage_illegalCaseName()), ex); } - LOGGER.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS + logger.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS /* * Set up either a GUI progress indicator or a logging progress @@ -507,11 +488,14 @@ public class Case { CancelButtonListener listener = new CancelButtonListener(); ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_creatingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_cancelLabel()}, Bundle.Case_progressIndicatorCancelButton_cancelLabel(), null, listener); + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_creatingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), null, listener); } else { progressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start(Bundle.Case_progressMessage_preparingToCreateCase()); + Case newCase = new Case(); + CaseContext caseContext = new CaseContext(newCase, progressIndicator); + listener.setCaseContext(caseContext); + progressIndicator.start(Bundle.Case_progressMessage_preparing()); /* * Creating a case is always done in the same non-UI thread that will be @@ -521,8 +505,7 @@ public class Case { * required by the coordination service. */ try { - Future innerFuture = getCaseLockingExecutor().submit(() -> { - Case newCase = new Case(); + Future future = getCaseLockingExecutor().submit(() -> { if (CaseType.SINGLE_USER_CASE == caseType) { newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator); } else { @@ -542,48 +525,54 @@ public class Case { /* * Finally, acquire an exclusive case resources lock to * ensure only one node at a time can - * create/open/upgrade the case resources. + * create/open/upgrade/close the case resources. */ try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseName)) { assert (null != resourcesLock); - newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator); + try { + newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator); + } catch (CaseActionException ex) { + /* + * Release the case directory lock immediately + * if there was a problem opening the case. + */ + if (CaseType.MULTI_USER_CASE == caseType) { + releaseSharedCaseDirLock(caseName); + } + throw ex; + } } } } return newCase; }); if (RuntimeProperties.runningWithGUI()) { - listener.setCaseActionFuture(innerFuture); - SwingUtilities.invokeLater(() - -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + listener.setCaseActionFuture(future); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); } - currentCase = innerFuture.get(); - LOGGER.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + currentCase = future.get(); + logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS if (RuntimeProperties.runningWithGUI()) { updateGUIForCaseOpened(); } eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); } catch (InterruptedException | ExecutionException ex) { - if (CaseType.MULTI_USER_CASE == caseType) { - releaseSharedCaseDirLock(caseName); - } if (ex instanceof InterruptedException) { - throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase("Interrupted during locks acquisition"), ex); //RJCTODO + throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); } else { throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(ex.getCause().getMessage()), ex); } } finally { - progressIndicator.finish(""); // RJCTODO: Is this right message? Nope + progressIndicator.finish(Bundle.Case_progressMessage_finshing()); if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() - -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); } } } /** - * Opens an existing Autopsy case and makes it the current case. + * Opens an existing case and makes it the current case. * * @param caseMetadataFilePath The path of the case metadata (.aut) file. * @@ -595,10 +584,9 @@ public class Case { @Messages({ "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}", "Case.progressIndicatorTitle.openingCase=Opening Case", - "Case.progressMessage.preparingToOpenCase=Preparing to open case...", - "Case.creationException.couldNotCreateMetadataFile=Could not create case: failed to create case metadata file." + "Case.exceptionMessage.failedToReadMetadata=Failed to read metadata." }) - public synchronized static void openCurrentCase(String caseMetadataFilePath) throws CaseActionException { + public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException { /* * If running with the desktop GUI, this needs to be done before any * cases are created or opened so that the application name can be @@ -618,7 +606,7 @@ public class Case { closeCurrentCase(); } - LOGGER.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS + logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS try { CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); @@ -629,11 +617,14 @@ public class Case { CancelButtonListener listener = new CancelButtonListener(); ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_openingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_cancelLabel()}, Bundle.Case_progressIndicatorCancelButton_cancelLabel(), null, listener); + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_openingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), null, listener); } else { progressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start(Bundle.Case_progressMessage_preparingToOpenCase()); + Case caseToOpen = new Case(); + CaseContext caseContext = new CaseContext(caseToOpen, progressIndicator); + listener.setCaseContext(caseContext); + progressIndicator.start(Bundle.Case_progressMessage_preparing()); /* * Opening a case is always done in the same non-UI thread that will @@ -646,9 +637,8 @@ public class Case { String caseName = metadata.getCaseName(); try { Future future = getCaseLockingExecutor().submit(() -> { - Case openedCase = new Case(); if (CaseType.SINGLE_USER_CASE == caseType) { - openedCase.open(metadata, progressIndicator); + caseToOpen.open(metadata, progressIndicator); } else { /* * First, acquire a shared case directory lock that will @@ -661,48 +651,63 @@ public class Case { /* * Next, acquire an exclusive case resources lock to * ensure only one node at a time can - * create/open/upgrade case resources. + * create/open/upgrade/close case resources. */ try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseName())) { assert (null != resourcesLock); - openedCase.open(metadata, progressIndicator); + try { + caseToOpen.open(metadata, progressIndicator); + } catch (CaseActionException ex) { + /* + * Release the case directory lock immediately + * if there was a problem opening the case. + */ + if (CaseType.MULTI_USER_CASE == caseType) { + releaseSharedCaseDirLock(caseName); + } + throw ex; + } } } - return openedCase; + return caseToOpen; }); if (RuntimeProperties.runningWithGUI()) { listener.setCaseActionFuture(future); - SwingUtilities.invokeLater(() - -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); } future.get(); currentCase = future.get(); - LOGGER.log(Level.INFO, "Opened case {0} in directory = {1}", new Object[]{metadata.getCaseName(), metadata.getCaseDirectory()}); //NON-NLS + logger.log(Level.INFO, "Opened case with metadata file path {0}", caseMetadataFilePath); //NON-NLS if (RuntimeProperties.runningWithGUI()) { updateGUIForCaseOpened(); } eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); } catch (InterruptedException | ExecutionException ex) { - if (CaseType.SINGLE_USER_CASE == caseType) { - releaseSharedCaseDirLock(caseName); - } if (ex instanceof ExecutionException) { throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); } else { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); } } finally { - progressIndicator.finish(""); // RJCTODO: Is this right message? + progressIndicator.finish(Bundle.Case_progressMessage_finshing()); if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() - -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); } } } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Failed to access case metadata"), ex); + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_failedToReadMetadata()), ex); } } + /** + * Checks if a case, the current case, is open. + * + * @return True or false. + */ + public static boolean isCaseOpen() { + return currentCase != null; + } + /** * Gets the current case, if there is one. * @@ -710,11 +715,10 @@ public class Case { * * @throws IllegalStateException if there is no current case. */ - public synchronized static Case getCurrentCase() { + public static Case getCurrentCase() { if (currentCase != null) { return currentCase; } else { - // This is for backawards compatibility. RJCTODO: Reference story throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen")); } } @@ -728,11 +732,10 @@ public class Case { * exception. */ @Messages({ - "Case.caseActionException.noCurrentCase=There is no open case", "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}", - "Case.progressIndicatorTitle.closingCase=Closing Case", - "Case.progressMessage.preparingToCloseCase=Preparing to close case...",}) - public synchronized static void closeCurrentCase() throws CaseActionException { + "Case.progressIndicatorTitle.closingCase=Closing Case" + }) + public static void closeCurrentCase() throws CaseActionException { if (null == currentCase) { return; } @@ -741,15 +744,23 @@ public class Case { * Set up either a GUI progress indicator or a logging progress * indicator. */ + CancelButtonListener listener = new CancelButtonListener(); ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_closingCase(), null, null, null, null); + progressIndicator = new ModalDialogProgressIndicator( + Bundle.Case_progressIndicatorTitle_closingCase(), + new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, + Bundle.Case_progressIndicatorCancelButton_label(), + null, + listener); } else { progressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start(Bundle.Case_progressMessage_preparingToCloseCase()); + CaseContext caseContext = new CaseContext(currentCase, progressIndicator); + listener.setCaseContext(caseContext); + progressIndicator.start(Bundle.Case_progressMessage_preparing()); -// LOGGER.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS RJCTOD + logger.log(Level.INFO, "Closing case with metadata file path {0}", currentCase.getCaseMetadata().getFilePath()); //NON-NLS try { /* * Closing a case is always done in the same non-UI thread that @@ -763,7 +774,14 @@ public class Case { currentCase.close(progressIndicator); } else { String caseName = currentCase.getCaseMetadata().getCaseName(); - try { + /* + * Acquire an exclusive case resources lock to ensure only + * one node at a time can create/open/upgrade/close the case + * resources. + */ + progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(currentCase.getName())) { + assert (null != resourcesLock); currentCase.close(progressIndicator); } finally { /* @@ -776,27 +794,31 @@ public class Case { return null; }); if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() - -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + listener.setCaseActionFuture(future); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); } future.get(); -// LOGGER.log(Level.INFO, "Closed case {0} in directory = {1}", new Object[]{metadata.getCaseName(), metadata.getCaseDirectory()}); //NON-NLS RJCTODO + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof ExecutionException) { + throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(ex.getCause().getMessage()), ex); + } else { + throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); + } + } finally { + /* + * The case is no longer the current case, even if an exception was + * thrown. + */ + logger.log(Level.INFO, "Closed case with metadata file path {0}", currentCase.getCaseMetadata().getFilePath()); //NON-NLS + Case closedCase = currentCase; + currentCase = null; if (RuntimeProperties.runningWithGUI()) { updateGUIForCaseClosed(); } - eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); - } catch (InterruptedException | ExecutionException ex) { - if (ex instanceof ExecutionException) { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); // RJCTODO - } else { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); // RJCTODO - } - } finally { - currentCase = null; - progressIndicator.finish(""); // RJCTODO: Is this right message? Nope + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null)); + progressIndicator.finish(Bundle.Case_progressMessage_finshing()); if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() - -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); } } } @@ -809,9 +831,9 @@ public class Case { * and may be a wrapper for a lower-level * exception. */ - public synchronized static void deleteCurrentCase() throws CaseActionException { + public static void deleteCurrentCase() throws CaseActionException { if (null == currentCase) { - throw new CaseActionException(Bundle.Case_caseActionException_noCurrentCase()); + return; } CaseMetadata metadata = currentCase.getCaseMetadata(); closeCurrentCase(); @@ -829,72 +851,93 @@ public class Case { * and may be a wrapper for a lower-level * exception. */ - public static synchronized void deleteCase(CaseMetadata metadata) throws CaseActionException { - if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(currentCase.getCaseDirectory())) { - // RJCTODO: Throw + @Messages({ + "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}", + "Case.progressIndicatorTitle.deletingCase=Deleting Case", + "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.", + "Case.progressMessage.deletingTextIndex=Deleting text index...", + "Case.progressMessage.deletingCaseDatabase=Deleting case database..." + }) + public static void deleteCase(CaseMetadata metadata) throws CaseActionException { + if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(metadata.getCaseDirectory())) { + throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase())); } + logger.log(Level.INFO, "Deleting case with metadata file path {0}", metadata.getFilePath()); //NON-NLS try { /* * Set up either a GUI progress indicator or a logging progress * indicator. */ + CancelButtonListener listener = new CancelButtonListener(); ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_closingCase(), null, null, null, null); // RJCTODO + progressIndicator = new ModalDialogProgressIndicator( + Bundle.Case_progressIndicatorTitle_deletingCase(), + new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, + Bundle.Case_progressIndicatorCancelButton_label(), + null, + listener); } else { progressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start(Bundle.Case_progressMessage_preparingToCloseCase()); + progressIndicator.start(Bundle.Case_progressMessage_preparing()); Future future = getCaseLockingExecutor().submit(() -> { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - cleanupDeletedCase(metadata); + cleanupDeletedCase(metadata, progressIndicator); } else { - String caseName = currentCase.getCaseMetadata().getCaseName(); /* * First, acquire an exclusive case directory lock. The case * cannot be deleted if another node has it open. */ - try (CoordinationService.Lock dirLock = acquireExclusiveLock(metadata.getCaseDirectory())) { + progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + try (CoordinationService.Lock dirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDatabasePath())) { assert (null != dirLock); - /* - * Try to unload/delete the Solr core from the Solr - * server. Do this before deleting the case directory - * because the index files are in the case directory and - * the deletion will fail if the core is not unloaded - * first. - */ - // RJCTODO: Need a method for this in keyword search service, code is in AIM at the moment /* - * Delete the case database from the database server. + * Delete the text index. */ - CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); - Class.forName("org.postgresql.Driver"); //NON-NLS - try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS - Statement statement = connection.createStatement();) { - String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS - statement.execute(deleteCommand); + progressIndicator.start(Bundle.Case_progressMessage_deletingTextIndex()); + for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) { + searchService.deleteTextIndex(metadata.getTextIndexName()); } - cleanupDeletedCase(metadata); + if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { + /* + * Delete the case database from the database + * server. The case database for a single-user case + * is in the case directory and will be deleted whe + * it is deleted. + */ + progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDatabase()); + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS + statement.execute(deleteCommand); + } + } + + cleanupDeletedCase(metadata, progressIndicator); } } return null; }); + logger.log(Level.INFO, "Deleted case with metadata file path {0}", metadata.getFilePath()); //NON-NLS future.get(); } catch (InterruptedException | ExecutionException ex) { if (ex instanceof ExecutionException) { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); // RJCTODO + throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(ex.getCause().getMessage()), ex); } else { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase("Interrupted during locks acquisition"), ex); // RJCTODO + throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); } } } /** * Sanitizes the case name for use as a PostgreSQL database name and in - * ActiveMQ event channle (topic) names. + * ActiveMQ event channel (topic) names. * * PostgreSQL: * http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html 63 @@ -1016,19 +1059,6 @@ public class Case { } } - /** - * Deletes a case directory. - * - * @param casePath A case directory path. - * - * @return True if the deletion succeeded, false otherwise. - */ - // RJCTODO: Get rid of this foolishness - static boolean deleteCaseDirectory(File casePath) { - LOGGER.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS - return FileUtil.deleteDir(casePath); - } - /** * Gets the paths of data sources that are images. * @@ -1046,7 +1076,7 @@ public class Case { } } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS + logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS } return imgPaths; } @@ -1072,18 +1102,28 @@ public class Case { appName = WindowManager.getDefault().getMainWindow().getTitle(); }); } catch (InterruptedException | InvocationTargetException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception getting main window title", ex); + logger.log(Level.SEVERE, "Unexpected exception getting main window title", ex); } } } - // RJCTODO - private static void cleanupDeletedCase(CaseMetadata metadata) { + /** + * Deletes the case directory of a deleted case and removes the case form + * the Recent Cases menu. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.deletingCaseDirectory=Deleting case directory..." + }) + private static void cleanupDeletedCase(CaseMetadata metadata, ProgressIndicator progressIndicator) { /* * Delete the case directory. */ + progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDirectory()); if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { - LOGGER.log(Level.SEVERE, "Failed to fully delete case directory {0}", metadata.getCaseDirectory()); + logger.log(Level.SEVERE, "Failed to fully delete case directory {0}", metadata.getCaseDirectory()); } /* @@ -1147,29 +1187,16 @@ public class Case { * @param caseDir The full path of the case directory. */ private static void releaseSharedCaseDirLock(String caseDir) { - // RJCTODO: Guard currentCaseLock? Yep! if (currentCaseDirLock != null) { try { currentCaseDirLock.release(); currentCaseDirLock = null; } catch (CoordinationService.CoordinationServiceException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex); + logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex); } } } - private static CoordinationService.Lock acquireExclusiveLock(String nodePath) throws CaseActionException { - try { - CoordinationService.Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, nodePath); - if (null == lock) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); // RJCTODO - } - return lock; - } catch (CoordinationServiceException ex) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex); // RJCTODO - } - } - /** * Acquires an exclusive case resources lock. * @@ -1204,7 +1231,6 @@ public class Case { SleuthkitCase caseDb = currentCase.getSleuthkitCase(); - // RJCTODO: Test both of these conditions /* * If the case database was upgraded for a new schema and a backup * database was created, notify the user. @@ -1233,13 +1259,13 @@ public class Case { if (!fileExists) { int ret = JOptionPane.showConfirmDialog( WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", appName, path), // RJCTODO: Don't use app name here + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", appName, path), NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), JOptionPane.YES_NO_OPTION); if (ret == JOptionPane.YES_OPTION) { MissingImageDialog.makeDialog(obj_id, caseDb); } else { - LOGGER.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS // RJCTODO + logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS } } } @@ -1259,7 +1285,7 @@ public class Case { * of recent cases to the recent cases menu item and the * open/create case dialog. */ - RecentCases.getInstance().addRecentCase(currentCase.getName(), currentCase.getCaseMetadata().getFilePath().toString()); // update the recent cases + RecentCases.getInstance().addRecentCase(currentCase.getDisplayName(), currentCase.getCaseMetadata().getFilePath().toString()); /* * Open the top components (windows within the main application @@ -1273,7 +1299,7 @@ public class Case { * Reset the main window title to be [application name] - [case * name], instead of just the application name. */ - addCaseNameToMainWindowTitle(currentCase.getName()); + addCaseNameToMainWindowTitle(currentCase.getDisplayName()); }); } } @@ -1341,6 +1367,25 @@ public class Case { return caseLockingExecutor; } + /** + * Empties the temp subdirectory for the current case. + */ + private static void clearTempSubDir(String tempSubDirPath) { + File tempFolder = new File(tempSubDirPath); + if (tempFolder.isDirectory()) { + File[] files = tempFolder.listFiles(); + if (files.length > 0) { + for (File file : files) { + if (file.isDirectory()) { + FileUtil.deleteDir(file); + } else { + file.delete(); + } + } + } + } + } + /** * Gets the case database. * @@ -1382,7 +1427,6 @@ public class Case { * * @return The case name. */ - // RJCTODO: Check all uses of this! public String getName() { return getCaseMetadata().getCaseName(); } @@ -1552,7 +1596,7 @@ public class Case { } } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS + logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS } return timezones; } @@ -1588,7 +1632,7 @@ public class Case { try { hasDataSources = (getDataSources().size() > 0); } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS + logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS } } return hasDataSources; @@ -1741,10 +1785,8 @@ public class Case { } /** - * Updates the case name. - * - * This should not be called from the EDT. - * + * Updates the case display name name. + * * @param oldCaseName The old case name. * @param oldPath The old path name. * @param newCaseName The new case name. @@ -1752,11 +1794,11 @@ public class Case { */ void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException { try { - caseMetadata.setCaseName(newCaseName); + caseMetadata.setCaseDisplayName(newCaseName); eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName)); SwingUtilities.invokeLater(() -> { try { - RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case + RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); addCaseNameToMainWindowTitle(newCaseName); } catch (Exception ex) { Logger.getLogger(Case.class.getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS @@ -1795,7 +1837,9 @@ public class Case { @Messages({ "Case.progressMessage.creatingCaseDirectory=Creating case directory...", "Case.progressMessage.creatingCaseDatabase=Creating case database...", - "Case.progressMessage.creatingCaseMetadataFile=Creating case metadata file..." + "Case.progressMessage.creatingCaseMetadataFile=Creating case metadata file...", + "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file.", + "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database." }) private void open(String caseDir, String caseName, String caseDisplayName, String caseNumber, String examiner, CaseType caseType, ProgressIndicator progressIndicator) throws CaseActionException { /* @@ -1830,7 +1874,7 @@ public class Case { String dbName = null; try { if (caseType == CaseType.SINGLE_USER_CASE) { - dbName = Paths.get(caseDir, "autopsy.db").toString(); // RJCTODO: Move to a const, fix non-relative path, need opening db message here + dbName = Paths.get(caseDir, SINGLE_USER_CASE_DB_NAME).toString(); this.caseDb = SleuthkitCase.newCase(dbName); } else if (caseType == CaseType.MULTI_USER_CASE) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); @@ -1839,9 +1883,9 @@ public class Case { this.caseDb = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); } } catch (TskCoreException ex) { - throw new CaseActionException(String.format("Error creating the case database %s: %s", dbName, ex.getMessage()), ex); //NON-NLS RJCTOD + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(), ex); } catch (UserPreferencesException ex) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); // RJCTODO + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); } /* @@ -1851,19 +1895,26 @@ public class Case { try { this.caseMetadata = new CaseMetadata(caseDir, caseType, caseName, caseDisplayName, caseNumber, examiner, dbName); } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_creationException_couldNotCreateMetadataFile(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex); } open(progressIndicator); } /** - * Opens an exsiting case. RJCTODO + * Opens an existing case. * - * @param metadata - * @param progressIndicator + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. * - * @throws CaseActionException + * @throws CaseActionException if there is a problem opening the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ + @Messages({ + "Case.progressMessage.openingCaseDatabase=Opening case database...", + "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database." + }) private void open(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { this.caseMetadata = metadata; @@ -1871,6 +1922,7 @@ public class Case { * Open the case database. */ try { + progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { this.caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath()); } else if (UserPreferences.getIsMultiUserModeEnabled()) { @@ -1878,14 +1930,13 @@ public class Case { this.caseDb = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); } catch (UserPreferencesException ex) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); // RJCTODO: What does this say? + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); } } else { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled")); } } catch (TskCoreException ex) { - // RJCTODO: Need proper exception message - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex); } open(progressIndicator); } @@ -1899,9 +1950,12 @@ public class Case { @Messages({ "Case.progressMessage.switchingLogDirectory=Switching log directory...", "Case.progressMessage.settingUpTskErrorReporting=Setting up SleuthKit error reporting...", - "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", "Case.progressMessage.openingCaseLevelServices=Opening case-level services...", - "Case.progressMessage.openingApplicationServiceResources=Opening case-specific application service resources...",}) + "Case.progressMessage.openingApplicationServiceResources=Opening case-specific application service resources...", + "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...", + "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesOpenError=Could not open case resources for {0} service: {1}", + "# {0} - service name", "Case.servicesException.notificationTitle={0} Service Error" + }) private void open(ProgressIndicator progressIndicator) { /* * Switch to writing to the application logs in the logs subdirectory. @@ -1934,14 +1988,19 @@ public class Case { */ progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator); - String serviceName = ""; for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { try { - serviceName = service.getServiceName(); service.openCaseResources(context); } catch (AutopsyService.AutopsyServiceException ex) { - // RJCTODO: Pop up error here? - Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to open case resources", serviceName), ex); + /* + * The case-specific application service resources are not + * essential. Log an error and notify the user, but do not + * throw. + */ + Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex); + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), Bundle.Case_servicesException_serviceResourcesOpenError(service.getServiceName(), ex.getLocalizedMessage()))); + } } } @@ -1950,21 +2009,40 @@ public class Case { * other nodes. */ if (CaseType.MULTI_USER_CASE == this.caseMetadata.getCaseType()) { + progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications()); try { - eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, this.getCaseMetadata().getCaseName())); - collaborationMonitor = new CollaborationMonitor(); + eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, this.getName())); + collaborationMonitor = new CollaborationMonitor(this.getName()); } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) { - // RJCTODO: Explain why this is not a fatal error - LOGGER.log(Level.SEVERE, "Failed to setup for collaboration", ex); //NON-NLS - MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")); + /* + * The collaboration monitor and event channel are not + * essential. Log an error and notify the user, but do not + * throw. + */ + logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"))); + } } } } - /* - * RJCTODO + /** + * Closes the case. + * + * @param progressIndicator A progress indicator. */ + @Messages({ + "Case.progressMessage.cancellingIngestJobs=Cancelling ingest jobs...", + "Case.progressMessage.notifyingCaseEventSubscribers=Notifying case event subscribers...", + "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", + "Case.progressMessage.closingCaseLevelServices=Closing case-level services...", + "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...", + "Case.progressMessage.tearingDownNetworkCommunications=Tearing down network communications...", + "Case.progressMessage.closingCaseDatabase=Closing case database...", + "Case.progressMessage.tearingDownTskErrorReporting=Tearing down SleuthKit error reporting..." + }) private void close(ProgressIndicator progressIndicator) { /* * Cancel all ingest jobs. @@ -1972,12 +2050,14 @@ public class Case { * TODO (JIRA-2227): Case closing should wait for ingest to stop to * avoid changing the case database while ingest is still using it. */ + progressIndicator.progress(Bundle.Case_progressMessage_cancellingIngestJobs()); IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); /* * Notify all local case event subscribers that the case is closed and * all interactions with the current case are no longer permitted. */ + progressIndicator.progress(Bundle.Case_progressMessage_notifyingCaseEventSubscribers()); eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), currentCase, null)); /* @@ -1985,6 +2065,7 @@ public class Case { * a multi-user case. */ if (CaseType.MULTI_USER_CASE == currentCase.getCaseType()) { + progressIndicator.progress(Bundle.Case_progressMessage_tearingDownNetworkCommunications()); if (null != collaborationMonitor) { collaborationMonitor.shutdown(); } @@ -1995,6 +2076,7 @@ public class Case { * Allow all registered applications ervices providers to close * resources related to the case. */ + progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources()); AutopsyService.CaseContext context = new AutopsyService.CaseContext(currentCase, progressIndicator); String serviceName = ""; for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { @@ -2004,49 +2086,34 @@ public class Case { service.closeCaseResources(context); } } catch (AutopsyService.AutopsyServiceException ex) { - Case.LOGGER.log(Level.SEVERE, String.format("%s service failed to close case resources", serviceName), ex); + Case.logger.log(Level.SEVERE, String.format("%s service failed to close case resources", serviceName), ex); } } /* * Close the case-level services. */ + progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices()); try { currentCase.getServices().close(); } catch (IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Error closing internal case services for %s", getName()), ex); + logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex); } /* * Close the case database */ + progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase()); this.caseDb.close(); /* * Disconnect the SleuthKit layer error reporter. */ + progressIndicator.progress(Bundle.Case_progressMessage_tearingDownTskErrorReporting()); this.caseDb.removeErrorObserver(this.sleuthkitErrorReporter); - // RJCTODO: Reset the log directory? - } - - /** - * Empties the temp subdirectory for the current case. - */ - private static void clearTempSubDir(String tempSubDirPath) { - File tempFolder = new File(tempSubDirPath); - if (tempFolder.isDirectory()) { - File[] files = tempFolder.listFiles(); - if (files.length > 0) { - for (File file : files) { - if (file.isDirectory()) { - FileUtil.deleteDir(file); - } else { - file.delete(); - } - } - } - } + progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); + Logger.setLogDirectory(PlatformUtil.getLogDirectory()); } /** @@ -2064,19 +2131,6 @@ public class Case { } - private final static class IllegalCaseNameException extends Exception { - - private static final long serialVersionUID = 1L; - - private IllegalCaseNameException(String message) { - super(message); - } - - private IllegalCaseNameException(String message, Throwable cause) { - super(message, cause); - } - } - private final static class CancelButtonListener implements ActionListener { private Future caseActionFuture; @@ -2086,7 +2140,7 @@ public class Case { this.caseActionFuture = caseActionFuture; } - private void setCaseContext(CaseContext caseContext) { // RJCTODO: USe this + private void setCaseContext(CaseContext caseContext) { this.caseContext = caseContext; } @@ -2102,6 +2156,36 @@ public class Case { } + /** + * An exception to throw when a case name with invalid characters is + * encountered. + */ + final static class IllegalCaseNameException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an exception to throw when a case name with invalid + * characters is encountered. + * + * @param message The exception message. + */ + IllegalCaseNameException(String message) { + super(message); + } + + /** + * Constructs an exception to throw when a case name with invalid + * characters is encountered. + * + * @param message The exception message. + * @param cause The exceptin cause. + */ + IllegalCaseNameException(String message, Throwable cause) { + super(message, cause); + } + } + /** * Creates a new, single-user Autopsy case. * @@ -2123,7 +2207,7 @@ public class Case { */ @Deprecated public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException { - createCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE); + createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE); } /** @@ -2148,7 +2232,7 @@ public class Case { */ @Deprecated public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { - createCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType); + createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType); } /** @@ -2164,19 +2248,7 @@ public class Case { */ @Deprecated public static void open(String caseMetadataFilePath) throws CaseActionException { - openCurrentCase(caseMetadataFilePath); - } - - /** - * Checks if case is currently open. - * - * @return True or false. - * - * @deprecated Do not use, this method is not relaible. - */ - @Deprecated - public static boolean isCaseOpen() { - return currentCase != null; + openAsCurrentCase(caseMetadataFilePath); } /** @@ -2337,7 +2409,4 @@ public class Case { deleteReports(reports); } - @Deprecated - public static final String propStartup = "LBL_StartupDialog"; //NON-NLS - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java deleted file mode 100644 index a488a05fb1..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionHelper.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2017 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.awt.Cursor; -import java.util.logging.Level; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; -import org.openide.util.NbBundle; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestJob; -import org.sleuthkit.autopsy.ingest.IngestManager; - -/** - * A utility class for common code for case actions. - */ -class CaseActionHelper { - - /** - * Tries to closes the current case, if any, with a checks to see if ingest - * is running; if it is, the user is given the opportunity to let the ingest - * continue and stop the case action. - * - * @param - * @return True if the current case, if any, is closed, false otherwise. - */ - // RJCTODO: Be sure to test this! Discard this class - static boolean closeCaseAndContinueAction() { - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), // RJCTODO - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), // RJCTODO - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object choice = DialogDisplayer.getDefault().notify(descriptor); - if (null != choice && DialogDescriptor.YES_OPTION == choice) { - IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED); - // RJCTODO; refer to JIRA here for blocking wait on cancel... - return true; - } else { - return false; - } - } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - Case.closeCurrentCase(); - } catch (CaseActionException ex) { - // RJCTODO: Pop up here - Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.SEVERE, "Error closing case.", ex); //NON-NLS - } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - return true; - } - - /** - * Private contructor to prevent instantiation. - */ - private CaseActionHelper() { - } -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java index 3090d3d18b..6556f97dc0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java @@ -22,6 +22,7 @@ import java.awt.Component; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -38,11 +39,12 @@ import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action to close the current case and pop up the start up window that - * allows a user to open another case. + * allows a user to open another case. */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.CaseCloseAction") @ActionRegistration(displayName = "#CTL_CaseCloseAct", lazy = false) @@ -52,6 +54,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; public final class CaseCloseAction extends CallableSystemAction implements Presenter.Toolbar { private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(CaseCloseAction.class.getName()); private final JButton toolbarButton = new JButton(); /** @@ -77,8 +80,8 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese */ if (IngestManager.getInstance().isIngestRunning()) { NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), // RJCTODO - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), // RJCTODO + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); @@ -105,7 +108,7 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese try { get(); } catch (InterruptedException | ExecutionException ex) { - // RJCTODO: Pop up error and log + logger.log(Level.SEVERE, "Error closing the current case", ex); } WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); StartupWindowProvider.getInstance().open(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 865a8c8eec..9f12944278 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -169,45 +169,10 @@ public final class CaseMetadata { * * @return The case display name. */ - // RJCTODO: Deal with the change public String getCaseName() { return caseName; } - - /** - * Sets the case display name. This does not change the name of the case - * directory, the case database, or the text index name. - * - * @param caseName A case display name. - */ - // RJCTODO: Deal with the change, remove this - void setCaseName(String caseName) throws CaseMetadataException { - String oldCaseName = caseName; - this.caseName = caseName; - try { - writeToFile(); - } catch (CaseMetadataException ex) { - this.caseName = oldCaseName; - throw ex; - } - } - /** - * Sets the text index name. - * - * @param caseTextIndexName The text index name. - */ - void setTextIndexName(String caseTextIndexName) throws CaseMetadataException { - String oldIndexName = caseTextIndexName; - this.textIndexName = caseTextIndexName; - try { - writeToFile(); - } catch (CaseMetadataException ex) { - this.textIndexName = oldIndexName; - throw ex; - } - } - /** * Gets the case display name. * @@ -223,7 +188,6 @@ public final class CaseMetadata { * * @param caseName A case display name. */ - // RJCTODO: Deal with the change void setCaseDisplayName(String caseName) throws CaseMetadataException { String oldCaseName = caseName; this.caseDisplayName = caseName; @@ -282,6 +246,22 @@ public final class CaseMetadata { } } + /** + * Sets the text index name. + * + * @param caseTextIndexName The text index name. + */ + void setTextIndexName(String caseTextIndexName) throws CaseMetadataException { + String oldIndexName = caseTextIndexName; + this.textIndexName = caseTextIndexName; + try { + writeToFile(); + } catch (CaseMetadataException ex) { + this.textIndexName = oldIndexName; + throw ex; + } + } + /** * Gets the text index name. * @@ -349,7 +329,6 @@ public final class CaseMetadata { * @throws CaseMetadataException If there is an error writing to the case * metadata file. */ - // RJCTODO: Should we have a backup copy of the file in case of error? private void writeToFile() throws CaseMetadataException { try { /* diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 26404fbfd7..6fb60d9e38 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -43,7 +43,9 @@ import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.ingest.IngestManager; /** - * An action that opens an existing case. + * An action that opens an existing case. It is associated with the Case/Open + * menu item via the layer.xml file, with a toolbar button, and with a button on + * the startup window. */ @ServiceProvider(service = CaseOpenAction.class) public final class CaseOpenAction extends CallableSystemAction implements ActionListener { @@ -82,8 +84,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action */ if (IngestManager.getInstance().isIngestRunning()) { NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), // RJCTODO - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), // RJCTODO + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); @@ -116,7 +118,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action @Override protected Void doInBackground() throws Exception { - Case.openCurrentCase(path); + Case.openAsCurrentCase(path); return null; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java index e0c4e75cab..e49887c931 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java @@ -77,15 +77,15 @@ class CasePropertiesForm extends javax.swing.JPanel { */ CasePropertiesForm(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException { initComponents(); - caseNameTextField.setText(currentCase.getName()); + caseNameTextField.setText(currentCase.getDisplayName()); String caseNumber = currentCase.getNumber(); - if (!caseNumber.equals("")) { + if (!caseNumber.isEmpty()) { caseNumberField.setText(caseNumber); } else { caseNumberField.setText("N/A"); } String examiner = currentCase.getExaminer(); - if (!examiner.equals("")) { + if (!examiner.isEmpty()) { examinerField.setText(examiner); } else { examinerField.setText("N/A"); @@ -303,13 +303,13 @@ class CasePropertiesForm extends javax.swing.JPanel { * @param evt The action event */ private void updateCaseNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateCaseNameButtonActionPerformed - String oldCaseName = Case.getCurrentCase().getName(); + String oldCaseName = Case.getCurrentCase().getDisplayName(); String newCaseName = caseNameTextField.getText(); // check if the old and new case name is not equal if (!oldCaseName.equals(newCaseName)) { // check if the case name is empty - if (newCaseName.trim().equals("")) { + if (newCaseName.trim().isEmpty()) { JOptionPane.showMessageDialog(caller, NbBundle.getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.empty.msg"), diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java index bdb3ae29de..6042c24d12 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -77,8 +77,13 @@ final class CollaborationMonitor { * collaborating nodes, informs the user of collaboration tasks on other * nodes using progress bars, and monitors the health of key collaboration * services. + * + * @param eventChannelPrefix The prefix for the remote events channel. + * + * @throws + * org.sleuthkit.autopsy.casemodule.CollaborationMonitor.CollaborationMonitorException */ - CollaborationMonitor() throws CollaborationMonitorException { + CollaborationMonitor(String eventChannelPrefix) throws CollaborationMonitorException { /** * Get the local host name so it can be used to identify the source of * collaboration tasks broadcast by this node. @@ -91,9 +96,7 @@ final class CollaborationMonitor { */ eventPublisher = new AutopsyEventPublisher(); try { - Case openedCase = Case.getCurrentCase(); - String channelPrefix = openedCase.getTextIndexName(); - eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, channelPrefix)); + eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, eventChannelPrefix)); } catch (AutopsyEventException ex) { throw new CollaborationMonitorException("Failed to initialize", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java index ff60df9070..0de0c2365b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java @@ -54,6 +54,7 @@ final class DeleteCurrentCaseAction extends CallableSystemAction { try { Case currentCase = Case.getCurrentCase(); String caseName = currentCase.getName(); + String caseDirectory = currentCase.getCaseDirectory(); /* * Do a confirmation dialog and close the current case if the user @@ -68,16 +69,15 @@ final class DeleteCurrentCaseAction extends CallableSystemAction { NotifyDescriptor.NO_OPTION)); if (null != response && DialogDescriptor.YES_OPTION == response) { try { - Case.deleteCurrentCase(); // RJCTODO: Test this! + Case.deleteCurrentCase(); } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Failed to delete case %s", caseName), ex); + logger.log(Level.SEVERE, String.format("Failed to delete case %s at %s", caseName, caseDirectory), ex); JOptionPane.showMessageDialog( null, Bundle.Case_deleteCaseFailureMessageBox_message(ex.getMessage()), Bundle.Case_deleteCaseFailureMessageBox_title(), JOptionPane.ERROR_MESSAGE); } - // RJCTODO: Fix this // because the "Delete Case" button is in the "CaseProperties" window, we have to close that window when we delete the case. CasePropertiesAction.closeCasePropertiesWindow(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index 1cceb7ab12..f76f620413 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.DataSourceUtils; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor; /** * A image file data source processor that implements the DataSourceProcessor diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java index 015f7751ea..305bb3dd90 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java @@ -30,7 +30,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.DriveUtils; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor; /** * A local drive data source processor that implements the DataSourceProcessor diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java index 9c19cb2cfc..e207734714 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java @@ -29,7 +29,7 @@ import org.openide.util.lookup.ServiceProviders; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor; /** * A local/logical files and/or directories data source processor that diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index b85215a2be..41a1ae7366 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -27,9 +27,10 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JComponent; import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.WizardDescriptor; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; @@ -37,7 +38,9 @@ import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.SystemAction; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case.CaseType; +import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action that creates and runs the new case wizard. @@ -50,13 +53,28 @@ final class NewCaseWizardAction extends CallableSystemAction { @Override public void performAction() { - if (CaseActionHelper.closeCaseAndContinueAction()) { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // RJCTODO: Is this right? - runNewCaseWizard(); + /* + * If ingest is running, give the user the option to abort changing + * cases. + */ + // Is this right here? + if (IngestManager.getInstance().isIngestRunning()) { + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object response = DialogDisplayer.getDefault().notify(descriptor); + if (DialogDescriptor.NO_OPTION == response) { + return; + } } + runNewCaseWizard(); } private void runNewCaseWizard() { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); final WizardDescriptor wizardDescriptor = new WizardDescriptor(getNewCaseWizardPanels()); wizardDescriptor.setTitleFormat(new MessageFormat("{0}")); wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text")); @@ -72,7 +90,7 @@ final class NewCaseWizardAction extends CallableSystemAction { final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS - Case.createCurrentCase(createdDirectory, caseName, caseNumber, examiner, caseType); + Case.createAsCurrentCase(createdDirectory, caseName, caseNumber, examiner, caseType); return null; } @@ -84,16 +102,16 @@ final class NewCaseWizardAction extends CallableSystemAction { addImageAction.actionPerformed(null); } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, String.format("Error creating case %s", wizardDescriptor.getProperty("caseName")), ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - (ex instanceof ExecutionException ? ex.getCause().getMessage() : ex.getMessage()), - NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - StartupWindowProvider.getInstance().close(); // RC: Why close and open? - StartupWindowProvider.getInstance().open(); - }); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + (ex instanceof ExecutionException ? ex.getCause().getMessage() : ex.getMessage()), + NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().close(); + StartupWindowProvider.getInstance().open(); doFailedCaseCleanup(wizardDescriptor); + } finally { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } }.execute(); @@ -107,11 +125,8 @@ final class NewCaseWizardAction extends CallableSystemAction { private void doFailedCaseCleanup(WizardDescriptor wizardDescriptor) { String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS if (createdDirectory != null) { - Case.deleteCaseDirectory(new File(createdDirectory)); + FileUtil.deleteDir(new File(createdDirectory)); } - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java index 9cc94cd182..d22beb2d34 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java @@ -35,6 +35,7 @@ import org.openide.WizardDescriptor; import org.openide.WizardValidationException; import org.openide.util.HelpCtx; import org.sleuthkit.autopsy.casemodule.Case.CaseType; +import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** @@ -175,7 +176,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel { try { - Case.openCurrentCase(casePath); + Case.openAsCurrentCase(casePath); } catch (CaseActionException ex) { logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java index 2204da444a..0637e8e90c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,13 +30,13 @@ import java.util.List; import java.util.logging.Level; import javax.swing.JMenuItem; import org.apache.commons.lang.ArrayUtils; +import org.openide.filesystems.FileUtil; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; -import org.openide.filesystems.FileUtil; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** * The action in this class is to clear the list of "Recent Cases". The @@ -45,14 +45,13 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ final class RecentCases extends CallableSystemAction implements Presenter.Menu { - static final int LENGTH = 6; - static final String NAME_PROP_KEY = "LBL_RecentCase_Name"; //NON-NLS - static final String PATH_PROP_KEY = "LBL_RecentCase_Path"; //NON-NLS - static final RecentCase BLANK_RECENTCASE = new RecentCase("", ""); - - private final static RecentCases INSTANCE = new RecentCases(); - - private Deque recentCases; // newest case is last case + private static final long serialVersionUID = 1L; + private static final int LENGTH = 6; + private static final String NAME_PROP_KEY = "LBL_RecentCase_Name"; //NON-NLS + private static final String PATH_PROP_KEY = "LBL_RecentCase_Path"; //NON-NLS + private static final RecentCase BLANK_RECENTCASE = new RecentCase("", ""); + private final static RecentCases instance = new RecentCases(); + private final Deque recentCases; // newest case is last case /** * Gets the instance of the RecentCases singleton. @@ -61,8 +60,8 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { * @return INSTANCE the RecentCases singleton */ static public RecentCases getInstance() { - INSTANCE.refreshRecentCases(); - return INSTANCE; + instance.refreshRecentCases(); + return instance; } /** @@ -84,7 +83,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { } // Load recentCases from properties - recentCases = new LinkedList(); + recentCases = new LinkedList<>(); for (int i = 0; i < LENGTH; i++) { final RecentCase rc = new RecentCase(getName(i), getPath(i)); @@ -256,7 +255,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { */ @Override public void actionPerformed(ActionEvent e) { - UpdateRecentCases.hasRecentCase = false; + UpdateRecentCases.setHasRecentCase(false); recentCases.clear(); @@ -375,7 +374,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { int i = 0; String currentCaseName = null; try { - currentCaseName = Case.getCurrentCase().getName(); + currentCaseName = Case.getCurrentCase().getDisplayName(); } catch (IllegalStateException ex) { // in case there is no current case. } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index 693b294c1c..ae1606c417 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -25,14 +25,17 @@ import java.awt.event.ActionListener; import java.io.File; import java.util.logging.Level; import javax.swing.JOptionPane; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; /** - * An action listener that opens a recent case. - * - * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). + * An action listener for a specific case, associated with a Recent Cases menu + * item for the case by a DynamicMenuContent content JMenuItem. */ class RecentItems implements ActionListener { @@ -41,7 +44,9 @@ class RecentItems implements ActionListener { private final String caseMetaDataFilePath; /** - * Constructs an action listener that opens a recent case. + * Constructs an action listener for a specific case, associated with a + * Recent Cases menu item for the case by a DynamicMenuContent content + * JMenuItem. * * @param caseName The name of the case. * @param caseMetaDataFilePath The path to the case metadata file. @@ -52,37 +57,42 @@ class RecentItems implements ActionListener { } /** - * Opens the recent case. + * Opens the case associated with the action. * * @param e the action event */ @Override public void actionPerformed(ActionEvent e) { - if (CaseActionHelper.closeCaseAndContinueAction()) { - if (caseName.isEmpty() || caseMetaDataFilePath.isEmpty() || (!new File(caseMetaDataFilePath).exists())) { - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName), - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), - JOptionPane.ERROR_MESSAGE); - RecentCases.getInstance().removeRecentCase(caseName, caseMetaDataFilePath); - EventQueue.invokeLater(() -> { - StartupWindowProvider.getInstance().open(); - }); - } else { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - Case.openCurrentCase(caseMetaDataFilePath); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - StartupWindowProvider.getInstance().open(); - } + /* + * If ingest is running, give the user the option to abort changing + * cases. + */ + if (IngestManager.getInstance().isIngestRunning()) { + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), + NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object response = DialogDisplayer.getDefault().notify(descriptor); + if (DialogDescriptor.NO_OPTION == response) { + return; } } + + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + Case.openAsCurrentCase(caseMetaDataFilePath); + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().open(); + } finally { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index 87c010e25b..6d57f3daac 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -48,7 +48,7 @@ import org.sleuthkit.datamodel.TskData; */ public class SingleUserCaseConverter { - static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS // RJCTODO + private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS private static final String AUTOPSY_DB_FILE = "autopsy.db"; //NON-NLS private static final String DOTAUT = CaseMetadata.getFileExtension(); //NON-NLS private static final String TIMELINE_FOLDER = "Timeline"; //NON-NLS @@ -196,7 +196,7 @@ public class SingleUserCaseConverter { CaseMetadata newCaseMetadata = new CaseMetadata(icd.getCaseOutputFolder().toString(), CaseType.MULTI_USER_CASE, icd.getNewCaseName(), - icd.getNewCaseName(), // RJCTODO + icd.getNewCaseName(), oldCaseMetadata.getCaseNumber(), oldCaseMetadata.getExaminer(), dbName); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java index 4d00a60606..29be82fe48 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,17 +30,22 @@ import org.openide.util.actions.SystemAction; */ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent { - int length; - static boolean hasRecentCase = false; + private static final long serialVersionUID = 1L; + private static int NUM_CASES_TO_DISPLAY; + private static boolean hasRecentCase = false; /** * the constructor */ UpdateRecentCases() { // display last 5 cases. - length = RecentCases.LENGTH - 1; + NUM_CASES_TO_DISPLAY = 5; } + static void setHasRecentCase(boolean value) { + hasRecentCase = value; + } + /** * Creates main menu/popup menu items. Null values will be later replaced by * JSeparators. This method is called for popups and for menus. It's called @@ -53,10 +58,10 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent { public JComponent[] getMenuPresenters() { String[] caseName = RecentCases.getInstance().getRecentCaseNames(); String[] casePath = RecentCases.getInstance().getRecentCasePaths(); - JComponent[] comps = new JComponent[length + 2]; // + 2 for separator and clear menu + JComponent[] comps = new JComponent[NUM_CASES_TO_DISPLAY + 2]; // + 2 for separator and clear menu // if it has the recent menus, add them to the component list - for (int i = 0; i < length; i++) { + for (int i = 0; i < NUM_CASES_TO_DISPLAY; i++) { if ((!caseName[i].equals(""))) { JMenuItem menuItem = new JMenuItem(caseName[i]); menuItem.setActionCommand(caseName[i].toUpperCase()); @@ -68,11 +73,11 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent { // if it has recent case, create clear menu if (hasRecentCase) { - comps[length] = new JSeparator(); + comps[NUM_CASES_TO_DISPLAY] = new JSeparator(); JMenuItem clearMenu = new JMenuItem( NbBundle.getMessage(UpdateRecentCases.class, "UpdateRecentCases.menuItem.clearRecentCases.text")); clearMenu.addActionListener(SystemAction.get(RecentCases.class)); - comps[length + 1] = clearMenu; + comps[NUM_CASES_TO_DISPLAY + 1] = clearMenu; } // otherwise, just create a disabled empty menu else { comps = new JComponent[1]; @@ -85,17 +90,15 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent { } /** - * Updates main menu presenters. This method is called only by the main menu - * processing. + * Updates the Recent Cases menu items. * - * @param jcs the previously used menu items returned by previous call to - * getMenuPresenters() or synchMenuPresenters() + * @param menuItems A set of Recent Case menu items to be updated. * - * @return menu a new set of items to show in menu. Can be either an updated - * old set of instances or a completely new one. + * @return A updated set of recent case menu items to show in the Recent + * Cases menu. */ @Override - public JComponent[] synchMenuPresenters(JComponent[] jcs) { + public JComponent[] synchMenuPresenters(JComponent[] menuItems) { return getMenuPresenters(); } } diff --git a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java index 605fee4135..17999cc363 100644 --- a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java +++ b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2015 Basis Technology Corp. + * Copyright 2013-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,18 +59,33 @@ public class RuntimeProperties { * Private constructor to prevent creation of instances of this class. */ private RuntimeProperties() { - } - private final static class RuntimePropertiesException extends Exception { + /** + * Exception to throw if there is an error setting a runtime property. + */ + public final static class RuntimePropertiesException extends Exception { private static final long serialVersionUID = 1L; - private RuntimePropertiesException(String message) { + /** + * Constructor for an exception to throw if there is an error setting + * a runtime property. + * + * @param message The exception message. + */ + public RuntimePropertiesException(String message) { super(message); } - private RuntimePropertiesException(String message, Throwable cause) { + /** + * Constructor for an exception to throw if there is an error setting + * a runtime property. + * + * @param message The exception message. + * @param cause The cause of the error. + */ + public RuntimePropertiesException(String message, Throwable cause) { super(message, cause); } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java index 48c3127027..3bf74e42e8 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java @@ -46,7 +46,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; public class Installer extends ModuleInstall { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(Installer.class.getName()); + private static final Logger logger = Logger.getLogger(Installer.class.getName()); private static Installer instance; public synchronized static Installer getDefault() { @@ -82,10 +82,9 @@ public class Installer extends ModuleInstall { final String caseFile = argsProcessor.getDefaultArg(); if (caseFile != null && !caseFile.isEmpty() && caseFile.endsWith(CaseMetadata.getFileExtension()) && new File(caseFile).exists()) { //NON-NLS try { - Case.openCurrentCase(caseFile); + Case.openAsCurrentCase(caseFile); } catch (CaseActionException ex) { - // RJCTODO: SHould there be a popup here? - LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseFile), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseFile), ex); //NON-NLS } return; } @@ -105,7 +104,7 @@ public class Installer extends ModuleInstall { try { Case.closeCurrentCase(); } catch (CaseActionException ex) { - LOGGER.log(Level.SEVERE, "Error closing current case", ex); //NON-NLS + logger.log(Level.SEVERE, "Error closing current case", ex); //NON-NLS } } @@ -124,7 +123,7 @@ public class Installer extends ModuleInstall { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { - LOGGER.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS + logger.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS } // Store the keys that deal with menu items @@ -140,7 +139,7 @@ public class Installer extends ModuleInstall { try { UIManager.setLookAndFeel(info.getClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { - LOGGER.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS + logger.log(Level.WARNING, "Error setting OS-X look-and-feel", ex); //NON-NLS } break; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutoIngestDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java similarity index 92% rename from Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutoIngestDataSourceProcessor.java rename to Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java index f7d50235da..e4acab8234 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutoIngestDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java @@ -16,9 +16,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.corecomponentinterfaces; +package org.sleuthkit.autopsy.framework; import java.nio.file.Path; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; /** * Interface implemented by DataSourceProcessors in order to be supported by diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutopsyService.java b/Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java similarity index 99% rename from Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutopsyService.java rename to Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java index 1bbc43b2c8..e4fa6958db 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutopsyService.java +++ b/Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.corecomponentinterfaces; +package org.sleuthkit.autopsy.framework; import org.sleuthkit.autopsy.casemodule.Case; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java similarity index 93% rename from Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java rename to Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java index 4b99312fc9..3fcad59638 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java @@ -16,16 +16,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.framework; import java.util.logging.Level; -import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; /** * A progress indicator that writes progress to the Autopsy application log. */ -class LoggingProgressIndicator implements ProgressIndicator { +public final class LoggingProgressIndicator implements ProgressIndicator { private final Logger LOGGER = Logger.getLogger(LoggingProgressIndicator.class.getName()); private int totalWorkUnits; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java similarity index 97% rename from Core/src/org/sleuthkit/autopsy/casemodule/ModalDialogProgressIndicator.java rename to Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java index 19ff954ddf..f967ce1592 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ModalDialogProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.framework; import java.awt.Dialog; import java.awt.event.ActionListener; @@ -24,7 +24,7 @@ import javax.swing.SwingUtilities; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.util.HelpCtx; -import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; +import org.sleuthkit.autopsy.framework.ProgressIndicator; /** * A progress indicator that displays progress using a modal dialog with a @@ -58,7 +58,7 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { public ActionListener getListener() { return listener; } - + @Override public void start(String message, int totalWorkUnits) { SwingUtilities.invokeLater(new Runnable() { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java rename to Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java index 2550f954b4..0f51d98d75 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.corecomponentinterfaces; +package org.sleuthkit.autopsy.framework; /** * An interface for progress indicators. A progress indicator can run in diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.form b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form similarity index 91% rename from Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.form rename to Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form index d3377cf55e..59678117af 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.form +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form @@ -42,7 +42,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.java rename to Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java index 613c2dd9c1..6d4e46cc42 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java @@ -16,27 +16,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.framework; /** - * RJCTODO + * A progress panel consisting of a message label and a progress bar. */ class ProgressPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - /** - * RJCTODO - */ ProgressPanel() { initComponents(); this.progressBar.setMinimum(0); } - /** - * RJCTODO - * @param message - */ void setMessage(String message) { this.progressMessage.setText(message); } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 41745ce498..40fb95c2fa 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -379,7 +379,7 @@ public class IngestManager { * Case.getTextIndexName() API. */ Case openedCase = Case.getCurrentCase(); - String channelPrefix = openedCase.getTextIndexName(); + String channelPrefix = openedCase.getName(); if (Case.CaseType.MULTI_USER_CASE == openedCase.getCaseType()) { jobEventPublisher.openRemoteEventChannel(String.format(JOB_EVENT_CHANNEL_NAME, channelPrefix)); moduleEventPublisher.openRemoteEventChannel(String.format(MODULE_EVENT_CHANNEL_NAME, channelPrefix)); diff --git a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java index af43facec9..59c9eefef8 100644 --- a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java +++ b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,36 +19,42 @@ package org.sleuthkit.autopsy.keywordsearchservice; import java.io.Closeable; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; /** - * An implementation of a keyword search service. - * + * An interface for implementations of a keyword search service. + * * TODO (AUT-2158: This interface should not extend Closeable. */ public interface KeywordSearchService extends Closeable { /** - * Takes a Blackboard artifact and adds all of its attributes to the keyword - * search index. + * Tries to connect to the keyword search service server. * - * @param artifact + * @param host The hostname or IP address of the service. + * @param port The port used by the service. + * + * @throws KeywordSearchServiceException if cannot connect. + */ + public void tryConnect(String host, int port) throws KeywordSearchServiceException; + + /** + * Adds an artifact to the keyword search text index as a concantenation of + * all of its attributes. + * + * @param artifact The artifact to index. * * @throws org.sleuthkit.datamodel.TskCoreException */ public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException; /** - * Checks if we can communicate with the KeywordSearchService using the - * passed-in host and port. Closes the connection upon exit. Throws if it - * cannot communicate. + * Deletes the keyword search text index for a case. * - * @param host the remote hostname or IP address of the server - * @param port the remote port of the server - * - * @throws KeywordSearchServiceException + * @param textIndexName The text index name. */ - public void tryConnect(String host, int port) throws KeywordSearchServiceException; + public void deleteTextIndex(String textIndexName); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java index c47b53a618..5c57ff2914 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java @@ -18,7 +18,7 @@ */ package org.sleuthkit.autopsy.modules.hashdatabase; -import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; +import org.sleuthkit.autopsy.framework.ProgressIndicator; /** * A "silent" or "null" progress indicator. diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index c3aba3434f..c1cffc9243 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -301,7 +301,7 @@ class ReportExcel implements TableReportModule { row = sheet.createRow(rowIndex); row.setRowStyle(setStyle); row.createCell(0).setCellValue(NbBundle.getMessage(this.getClass(), "ReportExcel.cellVal.caseName")); - row.createCell(1).setCellValue(currentCase.getName()); + row.createCell(1).setCellValue(currentCase.getDisplayName()); ++rowIndex; row = sheet.createRow(rowIndex); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 6f32fd2f65..afad0d5746 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -93,7 +93,7 @@ class ReportGenerator { DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); Date date = new Date(); String dateNoTime = dateFormat.format(date); - this.reportPath = currentCase.getReportDirectory() + File.separator + currentCase.getName() + " " + dateNoTime + File.separator; + this.reportPath = currentCase.getReportDirectory() + File.separator + currentCase.getDisplayName() + " " + dateNoTime + File.separator; this.errorList = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 3a7da57e3f..4b643e2a02 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -857,7 +857,7 @@ class ReportHTML implements TableReportModule { iconPath = "favicon.ico"; } index.append("\n").append(reportTitle).append(" ").append( - NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getName())).append( + NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append( "\n"); //NON-NLS index.append("\n"); //NON-NLS @@ -1017,7 +1017,7 @@ class ReportHTML implements TableReportModule { Date date = new Date(); String datetime = datetimeFormat.format(date); - String caseName = currentCase.getName(); + String caseName = currentCase.getDisplayName(); String caseNumber = currentCase.getNumber(); String examiner = currentCase.getExaminer(); int imagecount; diff --git a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java index 3348fdcafd..1de1db7eaa 100755 --- a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java +++ b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java @@ -90,7 +90,7 @@ public class AddTaggedHashesToHashDb implements GeneralReportModule { if (content instanceof AbstractFile) { if (null != ((AbstractFile) content).getMd5Hash()) { try { - hashSet.addHashes(tag.getContent(), Case.getCurrentCase().getName()); + hashSet.addHashes(tag.getContent(), Case.getCurrentCase().getDisplayName()); } catch (TskCoreException ex) { Logger.getLogger(AddTaggedHashesToHashDb.class.getName()).log(Level.SEVERE, "Error adding hash for obj_id = " + tag.getContent().getId() + " to hash database " + hashSet.getHashSetName(), ex); failedExports.add(tag.getContent().getName()); diff --git a/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java index d0493384e6..0cd8897ff7 100644 --- a/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java +++ b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java @@ -20,14 +20,14 @@ package org.sleuthkit.autopsy.test; import java.util.logging.Level; import org.openide.util.lookup.ServiceProvider; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; -import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.framework.AutopsyService; +import org.sleuthkit.autopsy.framework.ProgressIndicator; /** * An implementation of the Autopsy service interface used for test purposes. */ -@ServiceProvider(service = AutopsyService.class) +//@ServiceProvider(service = AutopsyService.class) public class TestAutopsyService implements AutopsyService { private static final Logger LOGGER = Logger.getLogger(TestAutopsyService.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index 7f0bf14f3a..04117963c7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -100,7 +100,7 @@ public class SaveSnapshotAsReport extends Action { setEventHandler(actionEvent -> { //capture generation date and use to make default report name Date generationDate = new Date(); - final String defaultReportName = FileUtil.escapeFileName(currentCase.getName() + " " + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss").format(generationDate)); //NON_NLS + final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss").format(generationDate)); //NON_NLS BufferedImage snapshot = SwingFXUtils.fromFXImage(nodeSupplier.get().snapshot(null, null), null); //prompt user to pick report name diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java index 646191de39..6d37ca2a26 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java @@ -46,7 +46,6 @@ class AutoIngestCase implements Comparable { * * @param caseDirectoryPath The case directory path. */ - // RJCTODO: Throw instead of reporting error, let client decide what to do. AutoIngestCase(Path caseDirectoryPath) { this.caseDirectoryPath = caseDirectoryPath; caseName = PathUtils.caseNameFromCaseDirectoryPath(caseDirectoryPath); @@ -100,7 +99,6 @@ class AutoIngestCase implements Comparable { * * @return The last accessed date. */ - // RJCTODO: Throw instead of reporting error, let client decide what to do. Date getLastAccessedDate() { try { BasicFileAttributes fileAttrs = Files.readAttributes(metadataFilePath, BasicFileAttributes.class); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java index ab559de58e..3d3ff8951f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java @@ -45,20 +45,10 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa this.nodeName = nodeName; } - /** - * RJCTODO - * - * @return - */ String getCaseName() { return caseName; } - /** - * RJCTODO - * - * @return - */ String getNodeName() { return nodeName; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java index e187cec054..1b95cd87be 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java @@ -72,7 +72,6 @@ final class AutoIngestCaseManager { * this machine, but review mode is only for looking at cases created by * automated ingest. */ - // RJCTODO: Write a story about this. FileObject root = FileUtil.getConfigRoot(); FileObject openRecentCasesMenu = root.getFileObject("Menu/Case/OpenRecentCase"); if (openRecentCasesMenu != null) { @@ -108,7 +107,7 @@ final class AutoIngestCaseManager { /* * Open the case. */ - Case.openCurrentCase(caseMetadataFilePath.toString()); + Case.openAsCurrentCase(caseMetadataFilePath.toString()); /** * Disable the add data source action in auto ingest examiner mode. This @@ -117,7 +116,6 @@ final class AutoIngestCaseManager { * enables the menus on EDT by calling SwingUtilities.invokeLater(), we * have to do the same thing here to maintain the order of execution. */ - // RJCTODO: Write a story about this. SwingUtilities.invokeLater(() -> { CallableSystemAction.get(AddImageAction.class).setEnabled(false); }); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java index 0687bc9c2e..5fbf380601 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java @@ -25,7 +25,7 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; * Event published when an automated ingest manager prioritizes all or part of a * case. */ -public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { // RJCTODO: Rename to AutoIngestPrioritizationEvent +public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { private static final long serialVersionUID = 1L; private final String caseName; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index ada7f61206..8372205fc6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -24,6 +24,7 @@ import java.nio.file.Paths; import java.time.Instant; import java.util.Comparator; import java.util.Date; +import java.util.Objects; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; @@ -55,9 +56,9 @@ public final class AutoIngestJob implements Comparable, Serializa @GuardedBy("this") transient private IngestJob ingestJob; @GuardedBy("this") - transient private boolean cancelled; // RJCTODO: Document + transient private boolean cancelled; @GuardedBy("this") - transient private boolean completed; // RJCTODO: Document + transient private boolean completed; @GuardedBy("this") private Date completedDate; @GuardedBy("this") @@ -81,7 +82,6 @@ public final class AutoIngestJob implements Comparable, Serializa * indicate the the job is not completed, i.e., new * Date(0L). */ - // RJCTODO: The null case directory is error-prone and the nodeName is confusing. AutoIngestJob(Manifest manifest, Path caseDirectoryPath, int priority, String nodeName, Stage stage, Date completedDate, boolean errorsOccurred) { this.manifest = manifest; if (null != caseDirectoryPath) { @@ -112,7 +112,6 @@ public final class AutoIngestJob implements Comparable, Serializa * * @return True or false */ - // RJCTODO: Use this or lose this synchronized boolean hasCaseDirectoryPath() { return (false == this.caseDirectoryPath.isEmpty()); } @@ -161,21 +160,10 @@ public final class AutoIngestJob implements Comparable, Serializa return this.priority; } - /** - * RJCTODO - * - * @param newStage - */ synchronized void setStage(Stage newStage) { setStage(newStage, Date.from(Instant.now())); } - /** - * RJCTODO - * - * @param state - * @param stateStartedDate - */ synchronized void setStage(Stage newState, Date stateStartedDate) { if (Stage.CANCELLING == this.stage && Stage.COMPLETED != newState) { return; @@ -184,29 +172,14 @@ public final class AutoIngestJob implements Comparable, Serializa this.stageStartDate = stateStartedDate; } - /** - * RJCTODO: - * - * @return - */ synchronized Stage getStage() { return this.stage; } - /** - * RJCTODO - * - * @return - */ synchronized Date getStageStartDate() { return this.stageStartDate; } - /** - * RJCTODO - * - * @return - */ synchronized StageDetails getStageDetails() { String description; Date startDate; @@ -223,7 +196,7 @@ public final class AutoIngestJob implements Comparable, Serializa if (!ingestModuleHandle.isCancelled()) { description = ingestModuleHandle.displayName(); } else { - description = String.format(Stage.CANCELLING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); // RJCTODO: FIx this + description = String.format(Stage.CANCELLING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); } } else { /** @@ -248,26 +221,14 @@ public final class AutoIngestJob implements Comparable, Serializa this.dataSourceProcessor = dataSourceProcessor; } - /** - * RJCTODO - */ - // RJCTODO: Consider moving this class into AIM and making this private synchronized void setIngestJob(IngestJob ingestJob) { this.ingestJob = ingestJob; } - /** - * RJCTODO - */ - // RJCTODO: Consider moving this class into AIM and making this private. - // Or move the AID into a separate package. Or do not worry about it. synchronized IngestJob getIngestJob() { return this.ingestJob; } - /** - * RJCTODO - */ synchronized void cancel() { setStage(Stage.CANCELLING); cancelled = true; @@ -280,26 +241,15 @@ public final class AutoIngestJob implements Comparable, Serializa } } - /** - * RJCTODO - */ synchronized boolean isCancelled() { return cancelled; } - /** - * RJCTODO - */ synchronized void setCompleted() { setStage(Stage.COMPLETED); completed = true; } - /** - * RJCTODO - * - * @return - */ synchronized boolean isCompleted() { return completed; } @@ -321,7 +271,7 @@ public final class AutoIngestJob implements Comparable, Serializa * @return True or false. */ synchronized Date getCompletedDate() { - return completedDate; // RJCTODO: Consider returning null if == 0 (epoch) + return completedDate; } /** @@ -342,23 +292,10 @@ public final class AutoIngestJob implements Comparable, Serializa return this.errorsOccurred; } - /** - * RJCTODO Gets name of the node associated with the job, possibly a remote - * hose if the job is in progress. - * - * @return The node name. - */ String getNodeName() { return nodeName; } - /** - * RJCTODO - * - * @param obj - * - * @return - */ @Override public boolean equals(Object obj) { if (!(obj instanceof AutoIngestJob)) { @@ -370,26 +307,12 @@ public final class AutoIngestJob implements Comparable, Serializa return this.getManifest().getFilePath().equals(((AutoIngestJob) obj).getManifest().getFilePath()); } - /** - * RJCTODO - * - * @return - */ @Override public int hashCode() { - // RJCTODO: Update this - int hash = 7; -// hash = 71 * hash + Objects.hashCode(this.dateCreated); + int hash = 71 * (Objects.hashCode(this.caseDirectoryPath)); return hash; } - /** - * RJCTODO Default sorting is by ready file creation date, descending - * - * @param o - * - * @return - */ @Override public int compareTo(AutoIngestJob o) { return -this.getManifest().getDateFileCreated().compareTo(o.getManifest().getDateFileCreated()); @@ -401,14 +324,6 @@ public final class AutoIngestJob implements Comparable, Serializa */ static class ReverseDateCompletedComparator implements Comparator { - /** - * RJCTODO - * - * @param o1 - * @param o2 - * - * @return - */ @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { return -o1.getStageStartDate().compareTo(o2.getStageStartDate()); @@ -420,14 +335,6 @@ public final class AutoIngestJob implements Comparable, Serializa */ public static class PriorityComparator implements Comparator { - /** - * RJCTODO - * - * @param job - * @param anotherJob - * - * @return - */ @Override public int compare(AutoIngestJob job, AutoIngestJob anotherJob) { return -(job.getPriority().compareTo(anotherJob.getPriority())); @@ -442,14 +349,6 @@ public final class AutoIngestJob implements Comparable, Serializa */ static class AlphabeticalComparator implements Comparator { - /** - * RJCTODO - * - * @param o1 - * @param o2 - * - * @return - */ @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { if (o1.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { @@ -462,10 +361,6 @@ public final class AutoIngestJob implements Comparable, Serializa } } - /** - * RJCTODO - */ - // RJCTODO: Combine this enum with StageDetails to make a single class. enum Stage { PENDING("Pending"), @@ -494,40 +389,21 @@ public final class AutoIngestJob implements Comparable, Serializa } - /** - * RJCTODO - */ @Immutable static final class StageDetails { private final String description; private final Date startDate; - /** - * RJCTODO - * - * @param description - * @param startDate - */ private StageDetails(String description, Date startDate) { this.description = description; this.startDate = startDate; } - /** - * RJCTODO - * - * @return - */ String getDescription() { return this.description; } - /** - * RJCTODO - * - * @return - */ Date getStartDate() { return this.startDate; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java index e1c9464d00..55248e0a9c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java @@ -22,28 +22,17 @@ import java.io.Serializable; import javax.annotation.concurrent.Immutable; import org.sleuthkit.autopsy.events.AutopsyEvent; -/** - * RJCTODO - */ @Immutable abstract class AutoIngestJobEvent extends AutopsyEvent implements Serializable { private static final long serialVersionUID = 1L; private final AutoIngestJob job; - /** - * RJCTODO - * - */ AutoIngestJobEvent(AutoIngestManager.Event eventSubType, AutoIngestJob job) { super(eventSubType.toString(), null, null); this.job = job; } - /** - * RJCTODO - * @return - */ AutoIngestJob getJob() { return this.job; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java index e9406950fd..161a20286e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java @@ -192,7 +192,7 @@ final class AutoIngestJobLogger { * to acquire an exclusive lock on the * log file. */ - void logDataSourceProcessorCancelled() throws AutoIngestJobLoggerException, InterruptedException { // RJCTODO: Is this used now? + void logDataSourceProcessorCancelled() throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.WARNING, "Cancelled adding data source to case"); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java index de2ef46ffd..b80157b3b4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java @@ -28,9 +28,6 @@ public final class AutoIngestJobStartedEvent extends AutoIngestJobEvent implemen private static final long serialVersionUID = 1L; - /** - * RJCTODO - */ public AutoIngestJobStartedEvent(AutoIngestJob job) { super(AutoIngestManager.Event.JOB_STARTED, job); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java index 23a444f7d7..153cc04ced 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java @@ -28,9 +28,6 @@ public final class AutoIngestJobStatusEvent extends AutoIngestJobEvent implement private static final long serialVersionUID = 1L; - /** - * RJCTODO - */ public AutoIngestJobStatusEvent(AutoIngestJob job) { super(AutoIngestManager.Event.JOB_STATUS_UPDATED, job); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index a08ef06090..eb45ae0df4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -18,11 +18,9 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; import java.io.File; import java.io.IOException; import static java.nio.file.FileVisitOption.FOLLOW_LINKS; @@ -38,9 +36,6 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; -import org.sleuthkit.autopsy.modules.vmextractor.VirtualMachineFinder; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.datamodel.CaseDbConnectionInfo; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -54,13 +49,13 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.Observable; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -69,65 +64,50 @@ import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; -import javax.swing.filechooser.FileFilter; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import org.apache.commons.io.FilenameUtils; +import org.apache.solr.client.solrj.impl.HttpSolrServer; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.openide.modules.InstalledFileLocator; import org.sleuthkit.autopsy.casemodule.Case.CaseType; -import org.sleuthkit.autopsy.casemodule.GeneralFilter; -import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; -import org.sleuthkit.autopsy.core.RuntimeProperties; -import org.sleuthkit.autopsy.core.ServicesMonitor; -import org.sleuthkit.autopsy.core.UserPreferencesException; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; -import org.sleuthkit.autopsy.coreutils.ExecUtil; -import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.events.AutopsyEvent; -import org.sleuthkit.autopsy.events.AutopsyEventPublisher; -import org.sleuthkit.autopsy.ingest.IngestJob; -import org.sleuthkit.autopsy.ingest.IngestJobSettings; -import org.sleuthkit.datamodel.Content; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; -import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; -import org.apache.solr.client.solrj.impl.HttpSolrServer; -import org.openide.util.Lookup; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; +import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.core.ServicesMonitor.ServicesMonitorException; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; -import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.events.AutopsyEventException; -import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; -import org.sleuthkit.autopsy.ingest.IngestJobStartResult; -import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.events.AutopsyEventPublisher; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAlertFile.AutoIngestAlertFileException; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobLogger.AutoIngestJobLoggerException; import org.sleuthkit.autopsy.experimental.autoingest.FileExporter.FileExportException; import org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException; import org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus; -import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PENDING; -import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PROCESSING; import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.COMPLETED; import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.DELETED; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; -import org.sleuthkit.autopsy.coreutils.FileUtil; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAlertFile.AutoIngestAlertFileException; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobLogger.AutoIngestJobLoggerException; +import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PENDING; +import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PROCESSING; +import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; +import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException; +import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobStartResult; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.datamodel.Content; /** * An auto ingest manager is responsible for processing auto ingest jobs defined @@ -240,7 +220,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang eventPublisher.openRemoteEventChannel(EVENT_CHANNEL_NAME); SYS_LOGGER.log(Level.INFO, "Opened auto ingest event channel"); } catch (AutopsyEventException ex) { - throw new AutoIngestManagerStartupException("Failed to open aut ingest event channel", ex); + SYS_LOGGER.log(Level.SEVERE, "Failed to open auto ingest event channel", ex); + throw new AutoIngestManagerStartupException("Failed to open auto ingest event channel", ex); } rootInputDirectory = Paths.get(AutoIngestUserPreferences.getAutoModeImageFolder()); rootOutputDirectory = Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder()); @@ -249,7 +230,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang jobProcessingTaskFuture = jobProcessingExecutor.submit(jobProcessingTask); jobStatusPublishingExecutor.scheduleAtFixedRate(new PeriodicJobStatusEventTask(), JOB_STATUS_EVENT_INTERVAL_SECONDS, JOB_STATUS_EVENT_INTERVAL_SECONDS, TimeUnit.SECONDS); eventPublisher.addSubscriber(EVENT_LIST, instance); - RuntimeProperties.setRunningWithGUI(false); + try { + RuntimeProperties.setRunningWithGUI(false); + SYS_LOGGER.log(Level.INFO, "Set running with desktop GUI runtime property to false"); + } catch (RuntimeProperties.RuntimePropertiesException ex) { + SYS_LOGGER.log(Level.SEVERE, "Failed to set running with desktop GUI runtime property to false", ex); + throw new AutoIngestManagerStartupException("Failed to set running with desktop GUI runtime property to false", ex); + } state = State.RUNNING; errorState = ErrorState.NONE; } @@ -483,7 +470,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } for (AutoIngestJob job : hostNamesToRunningJobs.values()) { runningJobs.add(job); - runningJobs.sort(new AutoIngestJob.AlphabeticalComparator()); // RJCTODO: This sort should be done in the AID + runningJobs.sort(new AutoIngestJob.AlphabeticalComparator()); } } if (null != completedJobs) { @@ -502,12 +489,12 @@ public final class AutoIngestManager extends Observable implements PropertyChang } inputScanExecutor.submit(new InputDirScanTask()); } - + /** * Start a scan of the input directories and wait for scan to complete. */ - void scanInputDirsAndWait(){ - if (State.RUNNING != state) { + void scanInputDirsAndWait() { + if (State.RUNNING != state) { return; } SYS_LOGGER.log(Level.INFO, "Starting input scan of {0}", rootInputDirectory); @@ -684,18 +671,22 @@ public final class AutoIngestManager extends Observable implements PropertyChang return CaseDeletionResult.FAILED; } - /* - * Acquire an exclusive lock on the case so it can be safely deleted. - * This will fail if the case is open for review or a deletion operation - * on this case is already in progress on another node. - */ CaseDeletionResult result = CaseDeletionResult.FULLY_DELETED; List manifestFileLocks = new ArrayList<>(); - try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString())) { - if (null == caseLock) { - return CaseDeletionResult.FAILED; - } + try { synchronized (jobsLock) { + /* + * Get the case metadata. + */ + CaseMetadata metaData; + Path caseMetaDataFilePath = Paths.get(caseDirectoryPath.toString(), caseName + CaseMetadata.getFileExtension()); + try { + metaData = new CaseMetadata(caseMetaDataFilePath); + } catch (CaseMetadata.CaseMetadataException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Failed to get case metadata file %s for case %s at %s", caseMetaDataFilePath, caseName, caseDirectoryPath), ex); + return CaseDeletionResult.FAILED; + } + /* * Do a fresh input directory scan. */ @@ -703,12 +694,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang scanner.scan(); Set manifestPaths = casesToManifests.get(caseName); if (null == manifestPaths) { - SYS_LOGGER.log(Level.SEVERE, "No manifest paths found for case {0}", caseName); + SYS_LOGGER.log(Level.SEVERE, String.format("No manifest paths found for case %s at %s", caseName, caseDirectoryPath)); return CaseDeletionResult.FAILED; } /* - * Get all of the required manifest locks. + * Get exclusive locks on all of the manifests for the case. + * This will exclude other auot ingest nodes from doing anything + * with the case. */ for (Path manifestPath : manifestPaths) { try { @@ -719,20 +712,18 @@ public final class AutoIngestManager extends Observable implements PropertyChang return CaseDeletionResult.FAILED; } } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to acquire manifest lock for %s for case %s", manifestPath, caseName), ex); + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to acquire manifest lock for %s for case %s at %s", manifestPath, caseName, caseDirectoryPath), ex); return CaseDeletionResult.FAILED; } } - /* - * Get the case metadata. - */ - CaseMetadata metaData; - Path caseMetaDataFilePath = Paths.get(caseDirectoryPath.toString(), caseName + CaseMetadata.getFileExtension()); try { - metaData = new CaseMetadata(caseMetaDataFilePath); - } catch (CaseMetadata.CaseMetadataException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Failed to delete case metadata file %s for case %s", caseMetaDataFilePath, caseName)); + /* + * Physically delete the case. + */ + Case.deleteCase(metaData); + } catch (CaseActionException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Failed to physically delete case %s at %s", caseName, caseDirectoryPath), ex); return CaseDeletionResult.FAILED; } @@ -745,56 +736,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang nodeData.setStatus(ManifestNodeData.ProcessingStatus.DELETED); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); } catch (InterruptedException | CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set delete flag on manifest data for %s for case %s", manifestPath, caseName), ex); + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set delete flag on manifest data for %s for case %s at %s", manifestPath, caseName, caseDirectoryPath), ex); return CaseDeletionResult.PARTIALLY_DELETED; } } - /* - * Try to unload/delete the Solr core from the Solr server. Do - * this before deleting the case directory because the index - * files are in the case directory and the deletion will fail if - * the core is not unloaded first. - */ - String textIndexName = metaData.getTextIndexName(); - try { - unloadSolrCore(metaData.getTextIndexName()); - } catch (Exception ex) { - /* - * Could be a problem, or it could be that the core was - * already unloaded (e.g., by the server due to resource - * constraints). - */ - SYS_LOGGER.log(Level.WARNING, String.format("Error deleting text index %s for %s", textIndexName, caseName), ex); //NON-NLS - } - - /* - * Delete the case database from the database server. - */ - String caseDatabaseName = metaData.getCaseDatabaseName(); - try { - deleteCaseDatabase(caseDatabaseName); - } catch (SQLException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Unable to delete case database %s for %s", caseDatabaseName, caseName), ex); //NON-NLS - result = CaseDeletionResult.PARTIALLY_DELETED; - } catch (UserPreferencesException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error accessing case database connection info, unable to delete case database %s for %s", caseDatabaseName, caseName), ex); //NON-NLS - result = CaseDeletionResult.PARTIALLY_DELETED; - } catch (ClassNotFoundException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Cannot load database driver, unable to delete case database %s for %s", caseDatabaseName, caseName), ex); //NON-NLS - result = CaseDeletionResult.PARTIALLY_DELETED; - } - - /* - * Delete the case directory. - */ - File caseDirectory = caseDirectoryPath.toFile(); - FileUtil.deleteDir(caseDirectory); - if (caseDirectory.exists()) { - SYS_LOGGER.log(Level.SEVERE, String.format("Failed to delete case directory %s for case %s", caseDirectoryPath, caseName)); - return CaseDeletionResult.PARTIALLY_DELETED; - } - /* * Remove the jobs for the case from the pending jobs queue and * completed jobs list. @@ -809,27 +755,27 @@ public final class AutoIngestManager extends Observable implements PropertyChang notifyObservers(Event.CASE_DELETED); return result; - } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error acquiring coordination service lock on case %s", caseName), ex); - return CaseDeletionResult.FAILED; - } finally { + /* + * Always release the manifest locks, regardless of the outcome. + */ for (Lock lock : manifestFileLocks) { try { lock.release(); } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Failed to release manifest file lock when deleting case %s", caseName), ex); + SYS_LOGGER.log(Level.SEVERE, String.format("Failed to release manifest file lock when deleting case %s at %s", caseName, caseDirectoryPath), ex); } } } } - + /** * Get the current snapshot of the job lists. + * * @return Snapshot of jobs lists */ - JobsSnapshot getCurrentJobsSnapshot(){ - synchronized(jobsLock){ + JobsSnapshot getCurrentJobsSnapshot() { + synchronized (jobsLock) { List runningJobs = new ArrayList<>(); getJobs(null, runningJobs, null); return new JobsSnapshot(pendingJobs, runningJobs, completedJobs); @@ -895,9 +841,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang * Starts the process of cancelling the current job. * * Note that the current job is included in the running list for a while - * because it can take some time - * for the automated ingest process for the job to be shut down in - * an orderly fashion. + * because it can take some time for the automated ingest process for the + * job to be shut down in an orderly fashion. */ void cancelCurrentJob() { if (State.RUNNING != state) { @@ -1655,8 +1600,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @throws CoordinationServiceException if there is an error while * acquiring or releasing a * manifest file lock. - * @throws InterruptedException if the thread is interrupted while - * reading the lock data + * @throws InterruptedException if the thread is interrupted + * while reading the lock data */ private Lock dequeueAndLockNextJob() throws CoordinationServiceException, InterruptedException { SYS_LOGGER.log(Level.INFO, "Checking pending jobs queue for ready job, enforcing max jobs per case"); @@ -1694,8 +1639,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @throws CoordinationServiceException if there is an error while * acquiring or releasing a * manifest file lock. - * @throws InterruptedException if the thread is interrupted while - * reading the lock data + * @throws InterruptedException if the thread is interrupted + * while reading the lock data */ private Lock dequeueAndLockNextJob(boolean enforceMaxJobsPerCase) throws CoordinationServiceException, InterruptedException { Lock manifestLock = null; @@ -1714,18 +1659,18 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ continue; } - + ManifestNodeData nodeData = new ManifestNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - if(! nodeData.getStatus().equals(PENDING)){ + if (!nodeData.getStatus().equals(PENDING)) { /* - * Due to a timing issue or a missed event, - * a non-pending job has ended up on the pending queue. - * Skip the job and remove it from the queue. + * Due to a timing issue or a missed event, a + * non-pending job has ended up on the pending queue. + * Skip the job and remove it from the queue. */ iterator.remove(); continue; } - + if (enforceMaxJobsPerCase) { int currentJobsForCase = 0; for (AutoIngestJob runningJob : hostNamesToRunningJobs.values()) { @@ -1806,9 +1751,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (jobProcessingTaskFuture.isCancelled()) { currentJob.cancel(); } - + nodeData = new ManifestNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath)); - if(currentJob.isCompleted() || currentJob.isCancelled()){ + if (currentJob.isCompleted() || currentJob.isCancelled()) { nodeData.setStatus(COMPLETED); Date completedDate = new Date(); currentJob.setCompletedDate(completedDate); @@ -1820,7 +1765,6 @@ public final class AutoIngestManager extends Observable implements PropertyChang } coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath, nodeData.toArray()); - boolean retry = (!currentJob.isCancelled() && !currentJob.isCompleted()); SYS_LOGGER.log(Level.INFO, "Completed processing of {0}, retry = {1}", new Object[]{manifestPath, retry}); if (currentJob.isCancelled()) { @@ -1983,41 +1927,33 @@ public final class AutoIngestManager extends Observable implements PropertyChang String caseName = manifest.getCaseName(); SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()}); currentJob.setStage(AutoIngestJob.Stage.OPENING_CASE); - try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, 12, TimeUnit.HOURS)) { // RJCTODO: New lock type! - if (null != caseLock) { - try { - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, caseName); - if (null != caseDirectoryPath) { - Path metadataFilePath = caseDirectoryPath.resolve(manifest.getCaseName() + CaseMetadata.getFileExtension()); - Case.openCurrentCase(metadataFilePath.toString()); - } else { - caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); - Case.createCurrentCase(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); - /* - * Sleep a bit before releasing the lock to ensure - * that the new case folder is visible on the - * network. - */ - Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); - } - currentJob.setCaseDirectoryPath(caseDirectoryPath); - Case caseForJob = Case.getCurrentCase(); - SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); - return caseForJob; - - } catch (CaseActionException ex) { - throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); - } catch (IllegalStateException ex) { - /* - * Deal with the unfortunate fact that - * Case.getCurrentCase throws IllegalStateException. - */ - throw new CaseManagementException(String.format("Error getting current case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); - } - + try { + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, caseName); + if (null != caseDirectoryPath) { + Path metadataFilePath = caseDirectoryPath.resolve(manifest.getCaseName() + CaseMetadata.getFileExtension()); + Case.openAsCurrentCase(metadataFilePath.toString()); } else { - throw new CaseManagementException(String.format("Timed out acquiring case name lock for %s for %s", manifest.getCaseName(), manifest.getFilePath())); + caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); + Case.createAsCurrentCase(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); + /* + * Sleep a bit before releasing the lock to ensure that the + * new case folder is visible on the network. + */ + Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } + currentJob.setCaseDirectoryPath(caseDirectoryPath); + Case caseForJob = Case.getCurrentCase(); + SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); + return caseForJob; + + } catch (CaseActionException ex) { + throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + } catch (IllegalStateException ex) { + /* + * Deal with the unfortunate fact that Case.getCurrentCase + * throws IllegalStateException. + */ + throw new CaseManagementException(String.format("Error getting current case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); } } @@ -2118,7 +2054,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * Sleep to allow ingest event subscribers to do their event * handling. */ - Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); // RJCTODO: Change the setting description to be more generic + Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } if (currentJob.isCancelled() || jobProcessingTaskFuture.isCancelled()) { @@ -2200,7 +2136,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang for (AutoIngestDataSourceProcessor processor : processorCandidates) { try { int confidence = processor.canProcess(dataSource.getPath()); - if(confidence > 0){ + if (confidence > 0) { validDataSourceProcessorsMap.put(processor, confidence); } } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) { @@ -2219,16 +2155,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang SYS_LOGGER.log(Level.WARNING, "Unsupported data source {0} for {1}", new Object[]{dataSource.getPath(), manifestPath}); // NON-NLS return; } - + // Get an ordered list of data source processors to try List validDataSourceProcessors = validDataSourceProcessorsMap.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); + .sorted(Map.Entry.comparingByValue().reversed()) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); synchronized (ingestLock) { // Try each DSP in decreasing order of confidence - for(AutoIngestDataSourceProcessor selectedProcessor:validDataSourceProcessors){ + for (AutoIngestDataSourceProcessor selectedProcessor : validDataSourceProcessors) { jobLogger.logDataSourceProcessorSelected(selectedProcessor.getDataSourceType()); SYS_LOGGER.log(Level.INFO, "Identified data source type for {0} as {1}", new Object[]{manifestPath, selectedProcessor.getDataSourceType()}); try { @@ -2249,7 +2185,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang SYS_LOGGER.log(Level.SEVERE, "All data source processors failed to process {0}", dataSource.getPath()); jobLogger.logFailedToAddDataSource(); // Throw an exception. It will get caught & handled upstream and will result in AIM auto-pause. - throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Failed to process " + dataSource.getPath() + " with all data source processors"); + throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Failed to process " + dataSource.getPath() + " with all data source processors"); } } finally { currentJob.setDataSourceProcessor(null); @@ -2325,7 +2261,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang jobLogger.logDataSourceProcessorCancelled(); } } - + /** * Analyzes the data source content returned by the data source * processor using the configured set of data source level and file @@ -2370,7 +2306,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ ingestLock.wait(); IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot(); - for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { // RJCTODO: Are "child" jobs IngestJobs or DataSourceIngestJobs? + for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { if (!snapshot.isCancelled()) { List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); if (!cancelledModules.isEmpty()) { @@ -2421,7 +2357,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } finally { IngestManager.getInstance().removeIngestJobEventListener(ingestJobEventListener); - currentJob.setIngestJob(null); // RJCTODO: Consider moving AutoIngestJob into AutoIngestManager so that this method can be made private + currentJob.setIngestJob(null); } } @@ -2686,7 +2622,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * remote jobs. The auto ingest job status event is sent only if auto ingest * manager has a currently running auto ingest job. */ - private final class PeriodicJobStatusEventTask implements Runnable { // RJCTODO: Rename to StatusPublishingTask, especially when publishing to the system dashboard + private final class PeriodicJobStatusEventTask implements Runnable { private final long MAX_SECONDS_WITHOUT_UPDATE = JOB_STATUS_EVENT_INTERVAL_SECONDS * MAX_MISSED_JOB_STATUS_UPDATES; @@ -2704,14 +2640,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang notifyObservers(Event.JOB_STATUS_UPDATED); eventPublisher.publishRemotely(new AutoIngestJobStatusEvent(currentJob)); } - - if(AutoIngestUserPreferences.getStatusDatabaseLoggingEnabled()){ + + if (AutoIngestUserPreferences.getStatusDatabaseLoggingEnabled()) { String message; boolean isError = false; - if(getErrorState().equals(ErrorState.NONE)){ - if(currentJob != null){ - message = "Processing " + currentJob.getManifest().getDataSourceFileName() + - " for case " + currentJob.getManifest().getCaseName(); + if (getErrorState().equals(ErrorState.NONE)) { + if (currentJob != null) { + message = "Processing " + currentJob.getManifest().getDataSourceFileName() + + " for case " + currentJob.getManifest().getCaseName(); } else { message = "Paused or waiting for next case"; } @@ -2719,9 +2655,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang message = getErrorState().toString(); isError = true; } - try{ + try { StatusDatabaseLogger.logToStatusDatabase(message, isError); - } catch (SQLException | UserPreferencesException ex){ + } catch (SQLException | UserPreferencesException ex) { SYS_LOGGER.log(Level.WARNING, "Failed to update status database", ex); } } @@ -2778,7 +2714,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang /* * Events published by an auto ingest manager. The events are published * locally to auto ingest manager clients that register as observers and are - * broadcast to other auto ingest nodes. // RJCTODO: Is this true? + * broadcast to other auto ingest nodes. */ enum Event { @@ -2794,31 +2730,31 @@ public final class AutoIngestManager extends Observable implements PropertyChang } /** - * The current auto ingest error state. + * The current auto ingest error state. */ private enum ErrorState { - NONE ("None"), - COORDINATION_SERVICE_ERROR ("Coordination service error"), + NONE("None"), + COORDINATION_SERVICE_ERROR("Coordination service error"), SHARED_CONFIGURATION_DOWNLOAD_ERROR("Shared configuration download error"), - SERVICES_MONITOR_COMMUNICATION_ERROR ("Services monitor communication error"), - DATABASE_SERVER_ERROR ("Database server error"), - KEYWORD_SEARCH_SERVER_ERROR ("Keyword search server error"), - CASE_MANAGEMENT_ERROR ("Case management error"), - ANALYSIS_STARTUP_ERROR ("Analysis startup error"), - FILE_EXPORT_ERROR ("File export error"), - ALERT_FILE_ERROR ("Alert file error"), - JOB_LOGGER_ERROR ("Job logger error"), - DATA_SOURCE_PROCESSOR_ERROR ("Data source processor error"), - UNEXPECTED_EXCEPTION ("Unknown error"); - + SERVICES_MONITOR_COMMUNICATION_ERROR("Services monitor communication error"), + DATABASE_SERVER_ERROR("Database server error"), + KEYWORD_SEARCH_SERVER_ERROR("Keyword search server error"), + CASE_MANAGEMENT_ERROR("Case management error"), + ANALYSIS_STARTUP_ERROR("Analysis startup error"), + FILE_EXPORT_ERROR("File export error"), + ALERT_FILE_ERROR("Alert file error"), + JOB_LOGGER_ERROR("Job logger error"), + DATA_SOURCE_PROCESSOR_ERROR("Data source processor error"), + UNEXPECTED_EXCEPTION("Unknown error"); + private final String desc; - - private ErrorState(String desc){ + + private ErrorState(String desc) { this.desc = desc; } - + @Override - public String toString(){ + public String toString() { return desc; } } @@ -2853,7 +2789,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @return The jobs collection. */ List getPendingJobs() { - return this.pendingJobs; + return Collections.unmodifiableList(this.pendingJobs); } /** @@ -2862,7 +2798,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @return The jobs collection. */ List getRunningJobs() { - return this.runningJobs; + return Collections.unmodifiableList(this.runningJobs); } /** @@ -2871,14 +2807,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @return The jobs collection. */ List getCompletedJobs() { - return this.completedJobs; + return Collections.unmodifiableList(this.completedJobs); } } - /** - * RJCTODO - */ enum CaseDeletionResult { FAILED, PARTIALLY_DELETED, diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 380bbb15a9..0fe33092ca 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -34,9 +34,6 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; -/** - * RJCTODO - */ @Immutable @ServiceProvider(service = ManifestFileParser.class) public final class AutopsyManifestFileParser implements ManifestFileParser { @@ -47,14 +44,6 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { private static final String DEVICE_ID_XPATH = "/Manifest/Collection/Image/ID/text()"; private static final String DATA_SOURCE_NAME_XPATH = "/Manifest/Collection/Image/Name/text()"; - - /** - * RJCTODO - * - * @param filePath - * - * @return - */ @Override public boolean fileIsManifest(Path filePath) { boolean fileIsManifest = false; @@ -71,15 +60,6 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { return fileIsManifest; } - /** - * RJCTODO - * - * @param filePath - * - * @return - * - * @throws org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException - */ @Override public Manifest parse(Path filePath) throws ManifestFileParserException { if (!fileIsManifest(filePath)) { @@ -102,17 +82,6 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { } } - /** - * RJCTODO - * - * @param manifestFilePath - * - * @return - * - * @throws ParserConfigurationException - * @throws SAXException - * @throws IOException - */ private Document createManifestDOM(Path manifestFilePath) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java index 3acf895a9d..e5d7f1a6a3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java @@ -29,9 +29,6 @@ import java.util.HashMap; import java.util.Map; import javax.annotation.concurrent.Immutable; -/** - * RJCTODO - */ @Immutable public final class Manifest implements Serializable { @@ -43,17 +40,6 @@ public final class Manifest implements Serializable { private final String dataSourcePath; private final Map manifestProperties; - /** - * RJCTODO - * - * @param manifestFilePath - * @param caseName - * @param deviceId - * @param dataSourcePath - * @param manifestProperties - * - * @throws IOException - */ public Manifest(Path manifestFilePath, String caseName, String deviceId, Path dataSourcePath, Map manifestProperties) throws IOException { this.filePath = manifestFilePath.toString(); BasicFileAttributes attrs = Files.readAttributes(manifestFilePath, BasicFileAttributes.class); @@ -64,65 +50,30 @@ public final class Manifest implements Serializable { this.manifestProperties = new HashMap<>(manifestProperties); } - /** - * RJCTODO - * - * @return - */ public Path getFilePath() { return Paths.get(this.filePath); } - /** - * RJCTODO - * - * @return - * @throws IOException - */ public Date getDateFileCreated() { return this.dateFileCreated; } - /** - * RJCTODO - * - * @return - */ public String getCaseName() { return caseName; } - /** - * RJCTODO - * - * @return - */ public String getDeviceId() { return deviceId; } - /** - * RJCTODO - * - * @return - */ public Path getDataSourcePath() { return Paths.get(dataSourcePath); } - /** - * RJCTODO - * @return - */ public String getDataSourceFileName() { return Paths.get(dataSourcePath).getFileName().toString(); } - /** - * RJCTODO - * - * @return - */ public Map getManifestProperties() { return new HashMap<>(manifestProperties); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java index 14111b0410..00fb2b9a5f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java @@ -20,17 +20,11 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.nio.file.Path; -/** - * RJCTODO: - */ public interface ManifestFileParser { boolean fileIsManifest(Path filePath); Manifest parse(Path filePath) throws ManifestFileParserException; - /** - * Exception thrown if a manifest file cannot be parsed. RJCTODO - */ public final static class ManifestFileParserException extends Exception { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java index e4e272edd3..498ac38cee 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java @@ -99,9 +99,6 @@ final class ManifestNodeData { * * @return True or false. */ - // RJCTODO: This is confusing, consider changing the API so that the use case is to - // check the length of the node data from the coordination service before - // constructing an instance of this object. That would be much more clear! boolean coordSvcNodeDataWasSet() { return this.coordSvcNodeDataWasSet; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java index 622912f95a..852ab4714f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java @@ -69,7 +69,7 @@ final class PathUtils { * * @return A list of the output case folder paths. */ - static List findCaseFolders(Path folderToSearch) { // RJCTODO: Rename + static List findCaseFolders(Path folderToSearch) { File searchFolder = new File(folderToSearch.toString()); if (!searchFolder.isDirectory()) { return Collections.emptyList(); @@ -135,7 +135,7 @@ final class PathUtils { * * @return A case folder path with a time stamp suffix. */ - static Path createCaseFolderPath(Path caseFoldersPath, String caseName) { // RJCTODO: Rename + static Path createCaseFolderPath(Path caseFoldersPath, String caseName) { String folderName = caseName + "_" + TimeStampUtils.createTimeStamp(); return Paths.get(caseFoldersPath.toString(), folderName); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java index 3e7a7c4ec7..80b686751b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java @@ -31,7 +31,7 @@ import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.math.NumberUtils; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.framework.AutopsyService; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.UNCPathUtilities; import org.sleuthkit.autopsy.coreutils.PlatformUtil; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java index ead8609d32..c07385e277 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.logging.Level; import org.apache.commons.lang.math.NumberUtils; import org.openide.modules.InstalledFileLocator; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.framework.AutopsyService; import org.sleuthkit.autopsy.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index c1d5771c99..f238062e1f 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -30,7 +30,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; import org.sleuthkit.autopsy.core.RuntimeProperties; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.framework.AutopsyService; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; @@ -55,6 +55,14 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { ArtifactTextExtractor extractor = new ArtifactTextExtractor(); + /** + * Adds an artifact to the keyword search text index as a concantenation of + * all of its attributes. + * + * @param artifact The artifact to index. + * + * @throws org.sleuthkit.datamodel.TskCoreException + */ @Override public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException { if (artifact == null) { @@ -77,21 +85,12 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { } /** - * Checks if we can communicate with Solr using the passed-in host and port. - * Closes the connection upon exit. Throws if it cannot communicate with - * Solr. + * Tries to connect to the keyword search service. * - * When issues occur, it attempts to diagnose them by looking at the - * exception messages, returning the appropriate user-facing text for the - * exception received. This method expects the Exceptions messages to be in - * English and compares against English text. - * - * @param host the remote hostname or IP address of the Solr server - * @param port the remote port for Solr - * - * @throws - * org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException + * @param host The hostname or IP address of the service. + * @param port The port used by the service. * + * @throws KeywordSearchServiceException if cannot connect. */ @Override public void tryConnect(String host, int port) throws KeywordSearchServiceException { @@ -138,6 +137,24 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { } } + /** + * Deletes the keyword search text index for a case. + * + * @param textIndexName The text index name. + */ + @Override + public void deleteTextIndex(String textIndexName) { + /* + * Send a core unload request to the Solr server, with the parameters + * that request deleting the index and the instance directory + * (deleteInstanceDir removes everything related to the core, the index + * directory, the configuration files, etc.) set to true. + */ +// String url = "http://" + UserPreferences.getIndexingServerHost() + ":" + UserPreferences.getIndexingServerPort() + "/solr"; +// HttpSolrServer solrServer = new HttpSolrServer(url); +// org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(textIndexName, true, true, solrServer); + } + @Override public void close() throws IOException { } From 01586226313caff0fdbc791e381d9c3a69ce808d Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Sun, 22 Jan 2017 17:21:01 -0500 Subject: [PATCH 09/20] Bug fixes for multi-user case infrastructure --- Core/src/org/sleuthkit/autopsy/framework/Bundle.properties | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/framework/Bundle.properties diff --git a/Core/src/org/sleuthkit/autopsy/framework/Bundle.properties b/Core/src/org/sleuthkit/autopsy/framework/Bundle.properties new file mode 100644 index 0000000000..7fcb87097c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/framework/Bundle.properties @@ -0,0 +1,5 @@ +# To change this license header, choose License Headers in Project Properties. +# To change this template file, choose Tools | Templates +# and open the template in the editor. + +ProgressPanel.progressMessage.text=Message From e7ecd2e75670e80deff9493df0a7d7c46ddc8b35 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 24 Jan 2017 00:36:36 -0500 Subject: [PATCH 10/20] Bug fixes for Case actions --- .../sleuthkit/autopsy/actions/ExitAction.java | 3 +- .../autopsy/actions/OpenLogFolderAction.java | 33 ++--- .../actions/OpenOutputFolderAction.java | 20 ++- .../ShowIngestProgressSnapshotAction.java | 34 +++-- .../sleuthkit/autopsy/casemodule/Case.java | 56 +++++---- .../autopsy/casemodule/CaseCloseAction.java | 36 +++--- ...tCaseAction.java => CaseDeleteAction.java} | 55 +++++--- .../autopsy/casemodule/CaseMetadata.java | 93 +++++++------- .../autopsy/casemodule/CaseNewAction.java | 7 +- .../autopsy/casemodule/CaseOpenAction.java | 19 +-- .../casemodule/CasePropertiesAction.java | 76 ++++-------- .../casemodule/CasePropertiesForm.form | 3 - .../casemodule/CasePropertiesForm.java | 117 ++++++------------ 13 files changed, 260 insertions(+), 292 deletions(-) rename Core/src/org/sleuthkit/autopsy/casemodule/{DeleteCurrentCaseAction.java => CaseDeleteAction.java} (60%) diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java index 8a76b6855e..ea8b3c8857 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -30,7 +30,8 @@ import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.coreutils.Logger; /** - * An action wired in to the Case/Exit menu item. + * The action associated with the Case/Exit menu item. It closes the current + * case, if any, and shuts down the application. */ @ActionRegistration(displayName = "Exit", iconInMenu = true) @ActionReference(path = "Menu/Case", position = 1000, separatorBefore = 999) diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java index 75721867ab..a127425651 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2017 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,32 +35,37 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; /** - * Action to open the log subdirectory for the currently open case, or the log - * subdirectory of the user directory if there is no current case. + * The action associated with the Help/Open Log Folder menu item. It opens a + * file explorer window for the log subdirectory for the currently open case, or + * the log subdirectory of the user directory if there is no current case. */ @ActionRegistration(displayName = "#CTL_OpenLogFolder", iconInMenu = true) @ActionReference(path = "Menu/Help", position = 1750) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenLogFolderAction", category = "Help") public final class OpenLogFolderAction implements ActionListener { - private static final Logger LOGGER = Logger.getLogger(OpenLogFolderAction.class.getName()); + private static final Logger logger = Logger.getLogger(OpenLogFolderAction.class.getName()); @Override public void actionPerformed(ActionEvent e) { File logDir; - try { - Case currentCase = Case.getCurrentCase(); - logDir = new File(currentCase.getLogDirectoryPath()); - } catch (IllegalStateException ex) { - /* - * No open case. - */ + if (Case.isCaseOpen()) { + try { + Case currentCase = Case.getCurrentCase(); + logDir = new File(currentCase.getLogDirectoryPath()); + } catch (IllegalStateException ex) { + /* + * The case + */ + logDir = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "var" + File.separator + "log"); + } + } else { logDir = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "var" + File.separator + "log"); } try { if (logDir.exists() == false) { - LOGGER.log(Level.SEVERE, String.format("The log subdirectory %s does not exist", logDir)); + logger.log(Level.SEVERE, String.format("The log subdirectory %s does not exist", logDir)); NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "OpenLogFolder.error1", logDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); @@ -69,12 +74,12 @@ public final class OpenLogFolderAction implements ActionListener { Desktop.getDesktop().open(logDir); } } catch (IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Could not open log directory %s", logDir), ex); + logger.log(Level.SEVERE, String.format("Could not open log directory %s", logDir), ex); NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "OpenLogFolder.CouldNotOpenLogFolder", logDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(notifyDescriptor); } } - + } diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index b57e1b607f..93b5b7527e 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -35,16 +35,19 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; /** - * Action to open the subdirectory of the current case that contains the output - * files. + * The action associated with the Tools/Open Output Folder menu item. It opens a + * file explorer window for the root output directory for the currently open + * case. If the case is a single-user case, this is the case directory. If the + * case is a multi-user case, this is a subdirectory of the case directory + * specific to the host machine. */ @ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = true) @ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help") public final class OpenOutputFolderAction extends CallableSystemAction { - private static final Logger LOGGER = Logger.getLogger(OpenOutputFolderAction.class.getName()); private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(OpenOutputFolderAction.class.getName()); @Override public void performAction() { @@ -56,7 +59,7 @@ public final class OpenOutputFolderAction extends CallableSystemAction { try { Desktop.getDesktop().open(outputDir); } catch (IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to open output folder %s", outputDir), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Failed to open output folder %s", outputDir), ex); //NON-NLS NotifyDescriptor descriptor = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder", outputDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(descriptor); @@ -67,19 +70,14 @@ public final class OpenOutputFolderAction extends CallableSystemAction { DialogDisplayer.getDefault().notify(descriptor); } } catch (IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "OpenOutputFolderAction enabled with no current case", ex); //NON-NLS + logger.log(Level.SEVERE, "OpenOutputFolderAction enabled with no current case", ex); //NON-NLS JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.noCaseOpen")); } } @Override public boolean isEnabled() { - try { - Case.getCurrentCase(); - return true; - } catch (IllegalStateException ex) { - return false; - } + return Case.isCaseOpen(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java index 842bcabdbf..68c86f2a44 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2014 Basis Technology Corp. + * + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,19 +29,18 @@ import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; -@ActionID( - category = "Help", - id = "org.sleuthkit.autopsy.actions.ShowIngestProgressSnapshotAction" -) -@ActionRegistration( - displayName = "#CTL_ShowIngestProgressSnapshotAction", - lazy = false -) +/** + * The action associated with the Help/Get Ingest Progress Snapshot menu item. + * It opens a the Ingest Progress Snapshot dialog. + */ +@ActionID(category = "Help", id = "org.sleuthkit.autopsy.actions.ShowIngestProgressSnapshotAction") +@ActionRegistration(displayName = "#CTL_ShowIngestProgressSnapshotAction", lazy = false) @ActionReference(path = "Menu/Help", position = 1125) @Messages("CTL_ShowIngestProgressSnapshotAction=Ingest Status Details") public final class ShowIngestProgressSnapshotAction extends CallableSystemAction implements ActionListener { private static final String ACTION_NAME = NbBundle.getMessage(ShowIngestProgressSnapshotAction.class, "ShowIngestProgressSnapshotAction.actionName.text"); + private static final long serialVersionUID = 1L; @Override public void performAction() { @@ -60,16 +59,11 @@ public final class ShowIngestProgressSnapshotAction extends CallableSystemAction @Override public boolean isEnabled() { - try { - Case.getCurrentCase(); - return true; - } catch (IllegalStateException ex) { - return false; - } + return Case.isCaseOpen(); } @Override public boolean asynchronous() { - return false; // run on edt + return false; } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index c41d225b6d..4999481a13 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -128,9 +128,10 @@ public class Case { /* * The application name, used to make the title of the main application - * window [application] name when there is no open case and [application - * name]-[curent case display name] when there is an open case. Initialized - * by getting the main window title before a case has been opened. + * window [application name] when there is no open case and [curent case + * display name] - [application name] when there is an open case. + * Initialized by getting the main window title before a case has been + * opened. * * TODO (JIRA-2231): Make the application name a RuntimeProperties item set * by Installers. @@ -749,9 +750,9 @@ public class Case { if (RuntimeProperties.runningWithGUI()) { progressIndicator = new ModalDialogProgressIndicator( Bundle.Case_progressIndicatorTitle_closingCase(), - new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, - Bundle.Case_progressIndicatorCancelButton_label(), - null, + new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, + Bundle.Case_progressIndicatorCancelButton_label(), + null, listener); } else { progressIndicator = new LoggingProgressIndicator(); @@ -873,10 +874,10 @@ public class Case { ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { progressIndicator = new ModalDialogProgressIndicator( - Bundle.Case_progressIndicatorTitle_deletingCase(), - new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, - Bundle.Case_progressIndicatorCancelButton_label(), - null, + Bundle.Case_progressIndicatorTitle_deletingCase(), + new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, + Bundle.Case_progressIndicatorCancelButton_label(), + null, listener); } else { progressIndicator = new LoggingProgressIndicator(); @@ -891,7 +892,7 @@ public class Case { * cannot be deleted if another node has it open. */ progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); - try (CoordinationService.Lock dirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDatabasePath())) { + try (CoordinationService.Lock dirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory())) { assert (null != dirLock); /* @@ -1277,7 +1278,7 @@ public class Case { CallableSystemAction.get(AddImageAction.class).setEnabled(true); CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); - CallableSystemAction.get(DeleteCurrentCaseAction.class).setEnabled(true); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); /* @@ -1296,8 +1297,8 @@ public class Case { } /* - * Reset the main window title to be [application name] - [case - * name], instead of just the application name. + * Reset the main window title to be [curent case display name] + * - [application name], instead of just the application name. */ addCaseNameToMainWindowTitle(currentCase.getDisplayName()); }); @@ -1323,7 +1324,7 @@ public class Case { CallableSystemAction.get(AddImageAction.class).setEnabled(false); CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); - CallableSystemAction.get(DeleteCurrentCaseAction.class).setEnabled(false); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); /* @@ -1334,7 +1335,7 @@ public class Case { /* * Reset the main window title to be just the application name, - * instead of [application name] - [case name]. + * instead of [curent case display name] - [application name]. */ Frame mainWindow = WindowManager.getDefault().getMainWindow(); mainWindow.setTitle(appName); @@ -1786,7 +1787,7 @@ public class Case { /** * Updates the case display name name. - * + * * @param oldCaseName The old case name. * @param oldPath The old path name. * @param newCaseName The new case name. @@ -1873,10 +1874,21 @@ public class Case { progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase()); String dbName = null; try { - if (caseType == CaseType.SINGLE_USER_CASE) { - dbName = Paths.get(caseDir, SINGLE_USER_CASE_DB_NAME).toString(); - this.caseDb = SleuthkitCase.newCase(dbName); - } else if (caseType == CaseType.MULTI_USER_CASE) { + if (CaseType.SINGLE_USER_CASE == caseType) { + /* + * For single-user cases, the case database is a SQLite database + * with a fixed name and is physically located in the root of + * the case directory. + */ + dbName = SINGLE_USER_CASE_DB_NAME; + this.caseDb = SleuthkitCase.newCase(Paths.get(caseDir, SINGLE_USER_CASE_DB_NAME).toString()); + } else if (CaseType.MULTI_USER_CASE == caseType) { + /* + * For multi-user cases, the case database is a PostgreSQL + * database with a name consiting of the case name with a time + * stamp suffix and is physically located on the database + * server. + */ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); Date date = new Date(); dbName = caseName + "_" + dateFormat.format(date); @@ -1924,7 +1936,7 @@ public class Case { try { progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - this.caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath()); + this.caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), metadata.getCaseDatabaseName()).toString()); } else if (UserPreferences.getIsMultiUserModeEnabled()) { try { this.caseDb = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java index 6556f97dc0..5c69c40091 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java @@ -43,14 +43,13 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; /** - * An action to close the current case and pop up the start up window that - * allows a user to open another case. + * The action associated with the Case/Close Case menu item and the Close Case + * toolbar button. It closes the current case and pops up the start up window + * that allows a user to open another case. */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.CaseCloseAction") @ActionRegistration(displayName = "#CTL_CaseCloseAct", lazy = false) -@ActionReferences(value = { - @ActionReference(path = "Toolbars/Case", position = 104) -}) +@ActionReferences(value = {@ActionReference(path = "Toolbars/Case", position = 104)}) public final class CaseCloseAction extends CallableSystemAction implements Presenter.Toolbar { private static final long serialVersionUID = 1L; @@ -58,19 +57,20 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese private final JButton toolbarButton = new JButton(); /** - * The constructor for this class + * Constructs the action associated with the Case/Close Case menu item and + * the Close Case toolbar button. */ public CaseCloseAction() { - putValue("iconBase", "org/sleuthkit/autopsy/images/close-icon.png"); // put the icon NON-NLS - putValue(Action.NAME, NbBundle.getMessage(CaseCloseAction.class, "CTL_CaseCloseAct")); // put the action Name + putValue("iconBase", "org/sleuthkit/autopsy/images/close-icon.png"); //NON-NLS + putValue(Action.NAME, NbBundle.getMessage(CaseCloseAction.class, "CTL_CaseCloseAct")); //NON-NLS toolbarButton.addActionListener(CaseCloseAction.this::actionPerformed); this.setEnabled(false); } /** - * Closes the current opened case. + * Closes the current case. * - * @param e the action event for this method + * @param e The action event. */ @Override public void actionPerformed(ActionEvent e) { @@ -117,17 +117,17 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese } /** - * This method does nothing. Use the "actionPerformed(ActionEvent e)" - * instead of this method. + * Closes the current case. */ @Override public void performAction() { + actionPerformed(null); } /** - * Gets the name of this action. This may be presented as an item in a menu. + * Gets the action name. * - * @return actionName + * @return The action name. */ @Override public String getName() { @@ -135,9 +135,9 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese } /** - * Gets the HelpCtx associated with implementing object + * Gets the help context. * - * @return HelpCtx or HelpCtx.DEFAULT_HELP + * @return The help context. */ @Override public HelpCtx getHelpCtx() { @@ -145,9 +145,9 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese } /** - * Returns the toolbar component of this action + * Returns the toolbar component of this action. * - * @return component the toolbar button + * @return The toolbar button */ @Override public Component getToolbarPresenter() { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java similarity index 60% rename from Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java rename to Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java index 0de0c2365b..c510d5e5b2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/DeleteCurrentCaseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java @@ -19,9 +19,11 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.event.ActionEvent; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.Action; import javax.swing.JOptionPane; +import javax.swing.SwingWorker; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; @@ -32,15 +34,16 @@ import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.coreutils.Logger; /** - * An action to delete the current case. + * The action associated with the Delete button of the Case Properties panel. It + * deletes the current case. */ -final class DeleteCurrentCaseAction extends CallableSystemAction { +final class CaseDeleteAction extends CallableSystemAction { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(DeleteCurrentCaseAction.class.getName()); + private static final Logger logger = Logger.getLogger(CaseDeleteAction.class.getName()); - DeleteCurrentCaseAction() { // Should pass in caller - putValue(Action.NAME, NbBundle.getMessage(DeleteCurrentCaseAction.class, "CTL_CaseDeleteAction")); + CaseDeleteAction() { + putValue(Action.NAME, NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction")); this.setEnabled(false); } @@ -68,18 +71,34 @@ final class DeleteCurrentCaseAction extends CallableSystemAction { null, NotifyDescriptor.NO_OPTION)); if (null != response && DialogDescriptor.YES_OPTION == response) { - try { - Case.deleteCurrentCase(); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Failed to delete case %s at %s", caseName, caseDirectory), ex); - JOptionPane.showMessageDialog( - null, - Bundle.Case_deleteCaseFailureMessageBox_message(ex.getMessage()), - Bundle.Case_deleteCaseFailureMessageBox_title(), - JOptionPane.ERROR_MESSAGE); - } - // because the "Delete Case" button is in the "CaseProperties" window, we have to close that window when we delete the case. - CasePropertiesAction.closeCasePropertiesWindow(); + + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + Case.deleteCurrentCase(); + return null; + } + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, String.format("Failed to delete case %s at %s", caseName, caseDirectory), ex); + JOptionPane.showMessageDialog( + null, + Bundle.Case_deleteCaseFailureMessageBox_message(ex.getMessage()), + Bundle.Case_deleteCaseFailureMessageBox_title(), + JOptionPane.ERROR_MESSAGE); + } + /* + * Close the Case Properties dialog that is the parent + * of the Delete button that invokes this action. + */ + CasePropertiesAction.closeCasePropertiesWindow(); + } + }.execute(); } } catch (IllegalStateException ex) { logger.log(Level.SEVERE, "Case delete action called with no current case", ex); @@ -92,7 +111,7 @@ final class DeleteCurrentCaseAction extends CallableSystemAction { @Override public String getName() { - return NbBundle.getMessage(DeleteCurrentCaseAction.class, "CTL_CaseDeleteAction"); + return NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction"); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 9f12944278..5cc9a206b6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -54,11 +54,11 @@ public final class CaseMetadata { private static final String FILE_EXTENSION = ".aut"; private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)"); private static final String SCHEMA_VERSION_ONE = "1.0"; - private static final String SCHEMA_VERSION_TWO = "2"; + private static final String SCHEMA_VERSION_TWO = "2.0"; private final static String AUTOPSY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS - private static final String CURRENT_SCHEMA_VERSION = "2.0"; + private static final String CURRENT_SCHEMA_VERSION = "3.0"; private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS private final static String CREATED_DATE_ELEMENT_NAME = "CreatedDate"; //NON-NLS @@ -67,6 +67,7 @@ public final class CaseMetadata { private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS private final static String CASE_ELEMENT_NAME = "Case"; //NON-NLS private final static String CASE_NAME_ELEMENT_NAME = "Name"; //NON-NLS + private final static String CASE_DISPLAY_NAME_ELEMENT_NAME = "DisplayName"; //NON-NLS private final static String CASE_NUMBER_ELEMENT_NAME = "Number"; //NON-NLS private final static String EXAMINER_ELEMENT_NAME = "Examiner"; //NON-NLS private final static String CASE_TYPE_ELEMENT_NAME = "CaseType"; //NON-NLS @@ -78,7 +79,7 @@ public final class CaseMetadata { private String caseDisplayName; private String caseNumber; private String examiner; - private String caseDatabase; + private String caseDatabaseName; private String textIndexName; private String createdDate; private String createdByVersion; @@ -96,16 +97,16 @@ public final class CaseMetadata { * Constructs an object that provides access to the case metadata stored in * a new case metadata file that is created using the supplied metadata. * - * @param caseDirectory The case directory. - * @param caseType The type of case. - * @param caseName The immutable name of the case. - * @param caseDisplayName The display name of the case, can be changed by - * a user. - * @param caseNumber The case number. - * @param examiner The name of the case examiner. - * @param caseDatabase For a single-user case, the full path to the - * case database file. For a multi-user case, the - * case database name. + * @param caseDirectory The case directory. + * @param caseType The type of case. + * @param caseName The immutable name of the case. + * @param caseDisplayName The display name of the case, can be changed by a + * user. + * @param caseNumber The case number. + * @param examiner The name of the case examiner. + * @param caseDatabase For a single-user case, the full path to the case + * database file. For a multi-user case, the case + * database name. * * @throws CaseMetadataException If the new case metadata file cannot be * created. @@ -117,7 +118,7 @@ public final class CaseMetadata { this.caseDisplayName = caseDisplayName; this.caseNumber = caseNumber; this.examiner = examiner; - this.caseDatabase = caseDatabase; + this.caseDatabaseName = caseDatabase; createdByVersion = Version.getVersion(); createdDate = CaseMetadata.DATE_FORMAT.format(new Date()); writeToFile(); @@ -172,7 +173,7 @@ public final class CaseMetadata { public String getCaseName() { return caseName; } - + /** * Gets the case display name. * @@ -181,7 +182,7 @@ public final class CaseMetadata { public String getCaseDisplayName() { return this.caseDisplayName; } - + /** * Sets the case display name. This does not change the name of the case * directory, the case database, or the text index name. @@ -198,7 +199,7 @@ public final class CaseMetadata { throw ex; } } - + /** * Gets the case number. * @@ -218,32 +219,12 @@ public final class CaseMetadata { } /** - * Gets the name of the case case database. + * Gets the name of the case database. * * @return The case database name. */ public String getCaseDatabaseName() { - if (caseType == Case.CaseType.MULTI_USER_CASE) { - return caseDatabase; - } else { - return Paths.get(caseDatabase).getFileName().toString(); - } - } - - /** - * Gets the full path to the case database file if the case is a single-user - * case. - * - * @return The full path to the case database file for a single-user case. - * - * @throws UnsupportedOperationException If called for a multi-user case. - */ - public String getCaseDatabasePath() throws UnsupportedOperationException { - if (caseType == Case.CaseType.SINGLE_USER_CASE) { - return caseDatabase; - } else { - throw new UnsupportedOperationException(); - } + return caseDatabaseName; } /** @@ -260,8 +241,8 @@ public final class CaseMetadata { this.textIndexName = oldIndexName; throw ex; } - } - + } + /** * Gets the text index name. * @@ -383,10 +364,11 @@ public final class CaseMetadata { * Create the children of the case element. */ createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName); + createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDisplayName); createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseNumber); createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, examiner); createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString()); - createChildElement(doc, caseElement, CASE_DATABASE_ELEMENT_NAME, caseDatabase); + createChildElement(doc, caseElement, CASE_DATABASE_ELEMENT_NAME, caseDatabaseName); createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName); } @@ -444,6 +426,11 @@ public final class CaseMetadata { } Element caseElement = (Element) caseElements.item(0); this.caseName = getElementTextContent(caseElement, CASE_NAME_ELEMENT_NAME, true); + if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO)) { + this.caseDisplayName = caseName; + } else { + this.caseDisplayName = getElementTextContent(caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, true); + } this.caseNumber = getElementTextContent(caseElement, CASE_NUMBER_ELEMENT_NAME, false); this.examiner = getElementTextContent(caseElement, EXAMINER_ELEMENT_NAME, false); this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true)); @@ -451,10 +438,10 @@ public final class CaseMetadata { throw new CaseMetadataException("Case metadata file corrupted"); } if (schemaVersion.equals(SCHEMA_VERSION_ONE)) { - this.caseDatabase = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true); + this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true); this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true); } else { - this.caseDatabase = getElementTextContent(caseElement, CASE_DATABASE_ELEMENT_NAME, true); + this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_ELEMENT_NAME, true); this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, true); } @@ -511,4 +498,22 @@ public final class CaseMetadata { } } + /** + * Gets the full path to the case database file if the case is a single-user + * case. + * + * @return The full path to the case database file for a single-user case. + * + * @throws UnsupportedOperationException If called for a multi-user case. + * @deprecated + */ + @Deprecated + public String getCaseDatabasePath() throws UnsupportedOperationException { + if (Case.CaseType.SINGLE_USER_CASE == caseType) { + return Paths.get(getCaseDirectory(), caseDatabaseName).toString(); + } else { + throw new UnsupportedOperationException(); + } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java index c3bd3fa7ce..2621277e44 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java @@ -26,9 +26,9 @@ import org.openide.util.actions.SystemAction; import org.openide.util.lookup.ServiceProvider; /** - * The action to create a new case. This action class is always enabled. - * - * IMPORTANT: Must be called in the Swing Event Dispatch Thread (EDT). + * The action associated with the Case/New Case menu item and the Create New + * Case button of the start up window that allows a user to open a case. It + * invokes the New Case wizard. */ @ServiceProvider(service = CaseNewActionInterface.class) public final class CaseNewAction extends CallableSystemAction implements CaseNewActionInterface { @@ -42,6 +42,7 @@ public final class CaseNewAction extends CallableSystemAction implements CaseNew @Override public void performAction() { + actionPerformed(null); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 6fb60d9e38..afd6a2cb9b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -43,21 +43,25 @@ import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.ingest.IngestManager; /** - * An action that opens an existing case. It is associated with the Case/Open - * menu item via the layer.xml file, with a toolbar button, and with a button on - * the startup window. + * The action associated with the Case/Open Case menu item via the layer.xml + * file, a toolbar button, and the Create New Case button of the start up window + * that allows a user to open a case. It opens an existing case. */ @ServiceProvider(service = CaseOpenAction.class) public final class CaseOpenAction extends CallableSystemAction implements ActionListener { - private static final Logger LOGGER = Logger.getLogger(CaseOpenAction.class.getName()); - private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS private static final long serialVersionUID = 1L; + private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS + private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName()); private final JFileChooser fileChooser = new JFileChooser(); private final FileFilter caseMetadataFileFilter; /** - * Constructs an action that opens an existing case. + * Constructs the action associated with the Case/Open Case menu item via + * the layer.xml file, a toolbar button, and the Create New Case button of + * the start up window that allows a user to open a case. It opens an + * existing case. + * */ public CaseOpenAction() { caseMetadataFileFilter = new FileNameExtensionFilter(NbBundle.getMessage(CaseOpenAction.class, "CaseOpenAction.autFilter.title", Version.getName(), CaseMetadata.getFileExtension()), CaseMetadata.getFileExtension().substring(1)); @@ -127,7 +131,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action try { get(); } catch (InterruptedException | ExecutionException ex) { - LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); JOptionPane.showMessageDialog( WindowManager.getDefault().getMainWindow(), @@ -144,6 +148,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action @Override public void performAction() { + actionPerformed(null); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java index 462a60d96a..3f80544101 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,98 +23,70 @@ import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.logging.Level; import javax.swing.Action; import javax.swing.JDialog; -import javax.swing.JFrame; +import javax.swing.SwingUtilities; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.coreutils.Logger; /** - * The action to pop up the Case Properties Form window. By using this form, - * user can update the case properties (for example: updates the case name and - * removes the image from the current case) - * - * @author jantonius + * The action associated with the Case/Case Properties menu item. It invokes the + * Case Properties dialog. */ final class CasePropertiesAction extends CallableSystemAction { - private static JDialog popUpWindow; + private static final long serialVersionUID = 1L; + private static JDialog casePropertiesDialog; - /** - * The CasePropertiesAction constructor - */ CasePropertiesAction() { - putValue(Action.NAME, NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction")); // put the action Name + putValue(Action.NAME, NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction")); this.setEnabled(false); Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - popUpWindow = null; + setEnabled(null != evt.getNewValue()); } }); } - /** - * Pop-up the Case Properties Form window where user can change the case - * properties (example: update case name and remove the image from the case) - */ @Override public void performAction() { - if (popUpWindow == null) { - // create the popUp window for it - String title = NbBundle.getMessage(this.getClass(), "CasePropertiesAction.window.title"); - popUpWindow = new JDialog((JFrame) WindowManager.getDefault().getMainWindow(), title, false); - try { - + SwingUtilities.invokeLater(() -> { + if (null == casePropertiesDialog) { + String title = NbBundle.getMessage(this.getClass(), "CasePropertiesAction.window.title"); + casePropertiesDialog = new JDialog(WindowManager.getDefault().getMainWindow(), title, false); CaseInformationPanel caseInformationPanel = new CaseInformationPanel(); caseInformationPanel.addCloseButtonAction((ActionEvent e) -> { - popUpWindow.dispose(); + casePropertiesDialog.setVisible(false); }); + casePropertiesDialog.add(caseInformationPanel); + casePropertiesDialog.setResizable(true); + casePropertiesDialog.pack(); - popUpWindow.add(caseInformationPanel); - popUpWindow.setResizable(true); - popUpWindow.pack(); - - // set the location of the popUp Window on the center of the screen Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); - double w = popUpWindow.getSize().getWidth(); - double h = popUpWindow.getSize().getHeight(); - popUpWindow.setLocation((int) ((screenDimension.getWidth() - w) / 2), (int) ((screenDimension.getHeight() - h) / 2)); - - popUpWindow.setVisible(true); - } catch (Exception ex) { - Logger.getLogger(CasePropertiesAction.class.getName()).log(Level.WARNING, "Error displaying Case Properties window.", ex); //NON-NLS + double w = casePropertiesDialog.getSize().getWidth(); + double h = casePropertiesDialog.getSize().getHeight(); + casePropertiesDialog.setLocation((int) ((screenDimension.getWidth() - w) / 2), (int) ((screenDimension.getHeight() - h) / 2)); + casePropertiesDialog.setVisible(true); } - } - popUpWindow.setVisible(true); - popUpWindow.toFront(); + casePropertiesDialog.setVisible(true); + casePropertiesDialog.toFront(); + }); } - /** - * Gets the name of this action. This may be presented as an item in a menu. - * - * @return actionName - */ @Override public String getName() { return NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction"); } - /** - * Gets the HelpCtx associated with implementing object - * - * @return HelpCtx or HelpCtx.DEFAULT_HELP - */ @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } static void closeCasePropertiesWindow() { - popUpWindow.dispose(); + casePropertiesDialog.setVisible(false); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form index e2b4940105..9b61cf90da 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form @@ -200,9 +200,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java index e49887c931..6bdee89046 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,9 @@ * limitations under the License. */ - /* - * CasePropertiesForm.java - * - * Created on Mar 14, 2011, 1:48:20 PM - */ package org.sleuthkit.autopsy.casemodule; -import java.io.File; +import java.nio.file.Paths; import java.util.Map; import java.util.logging.Level; import javax.swing.JOptionPane; @@ -37,44 +32,15 @@ import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.coreutils.Logger; /** - * The form where user can change / update the properties of the current case - * metadata. + * A panel that allows the user to view various properties of the current case + * and change the display name of the case. */ class CasePropertiesForm extends javax.swing.JPanel { - + private static final long serialVersionUID = 1L; - private Case current = null; private static JPanel caller; // panel for error - // Shrink a path to fit in targetLength (if necessary), by replaceing part - // of the path with "...". Ex: "C:\Users\bob\...\folder\other\Image.img" - private String shrinkPath(String path, int targetLength) { - if (path.length() > targetLength) { - String fill = "..."; - - int partsLength = targetLength - fill.length(); - - String front = path.substring(0, partsLength / 4); - int frontSep = front.lastIndexOf(File.separatorChar); - if (frontSep != -1) { - front = front.substring(0, frontSep + 1); - } - - String back = path.substring(partsLength * 3 / 4); - int backSep = back.indexOf(File.separatorChar); - if (backSep != -1) { - back = back.substring(backSep); - } - return back + fill + front; - } else { - return path; - } - } - - /** - * Creates new form CasePropertiesForm - */ CasePropertiesForm(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException { initComponents(); caseNameTextField.setText(currentCase.getDisplayName()); @@ -93,10 +59,10 @@ class CasePropertiesForm extends javax.swing.JPanel { crDateField.setText(crDate); caseDirField.setText(caseDir); current = currentCase; - + CaseMetadata caseMetadata = currentCase.getCaseMetadata(); if (caseMetadata.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { - dbNameField.setText(caseMetadata.getCaseDatabasePath()); + dbNameField.setText(Paths.get(caseMetadata.getCaseDirectory(), caseMetadata.getCaseDatabaseName()).toString()); } else { dbNameField.setText(caseMetadata.getCaseDatabaseName()); } @@ -162,11 +128,6 @@ class CasePropertiesForm extends javax.swing.JPanel { caseNameTextField.setFont(caseNameTextField.getFont().deriveFont(caseNameTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); caseNameTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNameTextField.text")); // NOI18N - caseNameTextField.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - caseNameTextFieldActionPerformed(evt); - } - }); updateCaseNameButton.setFont(updateCaseNameButton.getFont().deriveFont(updateCaseNameButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); updateCaseNameButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.updateCaseNameButton.text")); // NOI18N @@ -318,34 +279,36 @@ class CasePropertiesForm extends javax.swing.JPanel { JOptionPane.ERROR_MESSAGE); } else // check if case Name contain one of this following symbol: // \ / : * ? " < > | - if (newCaseName.contains("\\") || newCaseName.contains("/") || newCaseName.contains(":") - || newCaseName.contains("*") || newCaseName.contains("?") || newCaseName.contains("\"") - || newCaseName.contains("<") || newCaseName.contains(">") || newCaseName.contains("|")) { - String errorMsg = NbBundle - .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg"); - JOptionPane.showMessageDialog(caller, errorMsg, - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title"), - JOptionPane.ERROR_MESSAGE); - } else { - // ask for the confirmation first - String confMsg = NbBundle - .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.confMsg.msg", oldCaseName, - newCaseName); - NotifyDescriptor d = new NotifyDescriptor.Confirmation(confMsg, - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.confMsg.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - d.setValue(NotifyDescriptor.NO_OPTION); - - Object res = DialogDisplayer.getDefault().notify(d); - if (res != null && res == DialogDescriptor.YES_OPTION) { - // if user select "Yes" - String oldPath = current.getCaseMetadata().getFilePath().toString(); - try { - current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath); - } catch (Exception ex) { - Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS + { + if (newCaseName.contains("\\") || newCaseName.contains("/") || newCaseName.contains(":") + || newCaseName.contains("*") || newCaseName.contains("?") || newCaseName.contains("\"") + || newCaseName.contains("<") || newCaseName.contains(">") || newCaseName.contains("|")) { + String errorMsg = NbBundle + .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg"); + JOptionPane.showMessageDialog(caller, errorMsg, + NbBundle.getMessage(this.getClass(), + "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title"), + JOptionPane.ERROR_MESSAGE); + } else { + // ask for the confirmation first + String confMsg = NbBundle + .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.confMsg.msg", oldCaseName, + newCaseName); + NotifyDescriptor d = new NotifyDescriptor.Confirmation(confMsg, + NbBundle.getMessage(this.getClass(), + "CasePropertiesForm.updateCaseName.confMsg.title"), + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); + d.setValue(NotifyDescriptor.NO_OPTION); + + Object res = DialogDisplayer.getDefault().notify(d); + if (res != null && res == DialogDescriptor.YES_OPTION) { + // if user select "Yes" + String oldPath = current.getCaseMetadata().getFilePath().toString(); + try { + current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath); + } catch (CaseActionException ex) { + Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS + } } } } @@ -353,13 +316,9 @@ class CasePropertiesForm extends javax.swing.JPanel { }//GEN-LAST:event_updateCaseNameButtonActionPerformed private void deleteCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteCaseButtonActionPerformed - CallableSystemAction.get(DeleteCurrentCaseAction.class).actionPerformed(evt); + CallableSystemAction.get(CaseDeleteAction.class).actionPerformed(evt); }//GEN-LAST:event_deleteCaseButtonActionPerformed - private void caseNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_caseNameTextFieldActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_caseNameTextFieldActionPerformed - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel caseDirField; From a5ecb62da91fa8c10fca4998437f96b702f8b217 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 24 Jan 2017 09:47:19 -0500 Subject: [PATCH 11/20] Bug fixes for Case changes, etc. --- .../autopsy/casemodule/AddImageAction.java | 41 ++-- .../sleuthkit/autopsy/casemodule/Case.java | 26 ++- .../casemodule/NewCaseWizardAction.java | 10 +- .../casemodule/NewCaseWizardPanel1.java | 25 ++- .../casemodule/OpenRecentCasePanel.java | 2 - .../autopsy/casemodule/RecentItems.java | 36 ++-- .../DataContentTopComponent.java | 17 +- .../corecomponents/DataResultPanel.java | 14 +- .../DataResultTopComponent.java | 10 +- .../autopsy/corecomponents/FXVideoPanel.java | 4 +- .../corecomponents/MediaViewImagePanel.java | 21 +- .../diagnostics/PerformancePanelAction.java | 24 +-- .../DirectoryTreeTopComponent.java | 194 +++++++++--------- .../autopsy/filesearch/DateSearchFilter.java | 6 +- .../autopsy/filesearch/FileSearchAction.java | 7 +- .../SilentProgressIndicator.java | 2 +- .../autopsy/ingest/RunIngestAction.java | 19 +- .../hashdatabase/HashDbPanelSearchAction.java | 7 +- .../autopsy/timeline/OpenTimelineAction.java | 10 +- .../autopsy/timeline/TimeLineController.java | 14 +- 20 files changed, 222 insertions(+), 267 deletions(-) rename Core/src/org/sleuthkit/autopsy/{modules/hashdatabase => framework}/SilentProgressIndicator.java (96%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java index f6a102338e..76d678dec3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Component; +import java.awt.Cursor; import java.awt.Dialog; import java.awt.Dimension; import java.awt.event.ActionEvent; @@ -44,19 +45,14 @@ import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.openide.util.lookup.ServiceProvider; +import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.Image; /** - * The action to add an image to the current Case. This action should be - * disabled on creation and it will be enabled on new case creation or case - * opened. - * - * @author jantonius + * An action that invokes the Add Data Source wizard. */ -// TODO: need annotation because there's a "Lookup.getDefault().lookup(AddImageAction.class)" -// used in AddImageWizardPanel1 (among other places). It really shouldn't be done like that. @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.AddImageAction") @ActionRegistration(displayName = "#CTL_AddImage", lazy = false) @ActionReferences(value = { @@ -64,6 +60,9 @@ import org.sleuthkit.datamodel.Image; @ServiceProvider(service = AddImageAction.class) public final class AddImageAction extends CallableSystemAction implements Presenter.Toolbar { + private static final long serialVersionUID = 1L; + static final Dimension SIZE = new Dimension(875, 550); + // Keys into the WizardDescriptor properties that pass information between stages of the wizard // : // String: time zone that the image is from @@ -84,15 +83,13 @@ public final class AddImageAction extends CallableSystemAction implements Presen static final String NOFATORPHANS_PROP = "nofatorphans"; //NON-NLS static final Logger logger = Logger.getLogger(AddImageAction.class.getName()); - static final Dimension SIZE = new Dimension(875, 550); - private WizardDescriptor wizardDescriptor; private WizardDescriptor.Iterator iterator; private Dialog dialog; - private JButton toolbarButton = new JButton(); + private final JButton toolbarButton = new JButton(); /** - * The constructor for AddImageAction class + * Constructs an action that invokes the Add Data Source wizard. */ public AddImageAction() { putValue(Action.NAME, NbBundle.getMessage(AddImageAction.class, "CTL_AddImage")); // set the action Name @@ -109,11 +106,6 @@ public final class AddImageAction extends CallableSystemAction implements Presen this.setEnabled(false); // disable this action class } - /** - * Pop-up the "Add Image" wizard panel. - * - * @param e - */ @Override public void actionPerformed(ActionEvent e) { if (IngestManager.getInstance().isIngestRunning()) { @@ -126,6 +118,7 @@ public final class AddImageAction extends CallableSystemAction implements Presen } } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); iterator = new AddImageWizardIterator(this); wizardDescriptor = new WizardDescriptor(iterator); wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "AddImageAction.wizard.title")); @@ -138,6 +131,7 @@ public final class AddImageAction extends CallableSystemAction implements Presen dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); Dimension d = dialog.getSize(); dialog.setSize(SIZE); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); dialog.setVisible(true); dialog.toFront(); @@ -172,12 +166,9 @@ public final class AddImageAction extends CallableSystemAction implements Presen void runTask(Image newImage); } - /** - * This method does nothing. Use the "actionPerformed(ActionEvent e)" - * instead of this method. - */ @Override public void performAction() { + actionPerformed(null); } /** @@ -191,9 +182,9 @@ public final class AddImageAction extends CallableSystemAction implements Presen } /** - * Gets the HelpCtx associated with implementing object + * Gets the help context for this action. * - * @return HelpCtx or HelpCtx.DEFAULT_HELP + * @return The help context. */ @Override public HelpCtx getHelpCtx() { @@ -201,9 +192,9 @@ public final class AddImageAction extends CallableSystemAction implements Presen } /** - * Returns the toolbar component of this action + * Gets the toolbar component for this action. * - * @return component the toolbar button + * @return The toolbar button */ @Override public Component getToolbarPresenter() { @@ -214,7 +205,7 @@ public final class AddImageAction extends CallableSystemAction implements Presen } /** - * Set this action to be enabled/disabled + * Enables and disables this action. * * @param value whether to enable this action or not */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 4999481a13..486e751f83 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2215,7 +2215,7 @@ public class Case { * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. - * @deprecated + * @deprecated Use createAsCurrentCase instead. */ @Deprecated public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException { @@ -2240,7 +2240,7 @@ public class Case { * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. - * @deprecated + * @deprecated Use createAsCurrentCase instead. */ @Deprecated public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { @@ -2256,13 +2256,27 @@ public class Case { * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. - * @deprecated + * @deprecated Use openAsCurrentCase instead. */ @Deprecated public static void open(String caseMetadataFilePath) throws CaseActionException { openAsCurrentCase(caseMetadataFilePath); } +/** + * Closes this Autopsy case. + * + * @throws CaseActionException if there is a problem closing the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + * @deprecated Use closeCurrentCase instead. + */ + @Deprecated + public void closeCase() throws CaseActionException { + closeCurrentCase(); + } + /** * Invokes the startup dialog window. * @@ -2381,6 +2395,10 @@ public class Case { * @param imgId The ID of the image. * @param timeZone The time zone of the image. * + * @return + * + * @throws org.sleuthkit.autopsy.casemodule.CaseActionException + * * @deprecated As of release 4.0 */ @Deprecated @@ -2389,7 +2407,7 @@ public class Case { Image newDataSource = caseDb.getImageById(imgId); notifyDataSourceAdded(newDataSource, UUID.randomUUID()); return newDataSource; - } catch (Exception ex) { + } catch (TskCoreException ex) { throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 41a1ae7366..3f48161330 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -43,7 +43,9 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; /** - * An action that creates and runs the new case wizard. + * The action associated with the Case/New Case menu item, t toolbar button, and + * the button in the start up window that allows users to open cases action. It + * runs first the New Case wizard, then the Add Data Source wizard. */ final class NewCaseWizardAction extends CallableSystemAction { @@ -57,7 +59,6 @@ final class NewCaseWizardAction extends CallableSystemAction { * If ingest is running, give the user the option to abort changing * cases. */ - // Is this right here? if (IngestManager.getInstance().isIngestRunning()) { NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), @@ -98,6 +99,11 @@ final class NewCaseWizardAction extends CallableSystemAction { protected void done() { try { get(); + /* + * Run the Add Data Source wizard by invoking the Add + * Data Source wizard. + */ + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); AddImageAction addImageAction = SystemAction.get(AddImageAction.class); addImageAction.actionPerformed(null); } catch (InterruptedException | ExecutionException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java index d22beb2d34..737cbe8839 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,9 +23,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.logging.Level; - -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openide.DialogDescriptor; @@ -34,8 +31,10 @@ import org.openide.NotifyDescriptor; import org.openide.WizardDescriptor; import org.openide.WizardValidationException; import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** @@ -102,7 +101,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel listeners = new HashSet(1); // or can use ChangeSupport in NB 6.0 + private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 /** * Adds a listener to changes of the panel's validity. @@ -135,7 +134,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel it; synchronized (listeners) { - it = new HashSet(listeners).iterator(); + it = new HashSet<>(listeners).iterator(); } ChangeEvent ev = new ChangeEvent(this); while (it.hasNext()) { @@ -168,14 +167,14 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); JOptionPane.showMessageDialog( WindowManager.getDefault().getMainWindow(), ex.getMessage(), // Should be user-friendly diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index ae1606c417..1067ae1022 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -18,13 +18,11 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.Cursor; -import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.io.File; import java.util.logging.Level; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; @@ -80,19 +78,23 @@ class RecentItems implements ActionListener { } } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - Case.openAsCurrentCase(caseMetaDataFilePath); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - StartupWindowProvider.getInstance().open(); - } finally { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } + /* + * Open the case. + */ + new Thread(() -> { + try { + Case.openAsCurrentCase(caseMetaDataFilePath); + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), + NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().open(); + }); + } + }).start(); } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java index accb7d3f86..6f7c87ad0b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java @@ -164,16 +164,11 @@ public final class DataContentTopComponent extends TopComponent implements DataC @Override public boolean canClose() { /* - * Only allow this window to be closed when there's no case opened or no - * data sources in the open case. + * If this is the main content viewers top component in the bottom of + * the main window, only it to be closed when there's no case opened or + * no data sources in the open case. */ - // - try { - return !this.isDefault || Case.getCurrentCase().hasData() == false; - } catch (IllegalStateException ex) { - // Thrown if there is no current case. - return true; - } + return (!this.isDefault) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; } @Override @@ -196,8 +191,8 @@ public final class DataContentTopComponent extends TopComponent implements DataC */ public static List getNewWindowList() { return newWindowList; - } - + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 76ee694aa6..fa655ee088 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -445,7 +445,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C @Override public List getViewers() { - List ret = new ArrayList(); + List ret = new ArrayList<>(); for (UpdateWrapper w : viewers) { ret.add(w.getViewer()); } @@ -455,15 +455,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C public boolean canClose() { /* - * Only allow this window to be closed when there's no case opened or no - * data sources in the open case. + * If this is the main results panel in the main top component in the + * upper right of the main window, only allow it to be closed when + * there's no case opened or no data sources in the open case. */ - try { - return !this.isMain || Case.getCurrentCase().hasData() == false; - } catch (IllegalStateException ex) { - // Thrown if there is no current case. - return true; - } + return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index c8fb127335..e99a2f8aa9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -315,15 +315,11 @@ public class DataResultTopComponent extends TopComponent implements DataResult, @Override public boolean canClose() { /* - * Only allow this window to be closed when there's no case opened or no + * If this is the results top component in the upper right of the main + * window, only allow it to be closed when there's no case opened or no * data sources in the open case. */ - try { - return !this.isMain || Case.getCurrentCase().hasData() == false; - } catch (IllegalStateException ex) { - // Thrown if there is no current case. - return true; - } + return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java index 60823fc6cc..f12839bd20 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -110,9 +110,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { if (file.equals(currentFile)) { return; } - try { - Case.getCurrentCase(); - } catch (IllegalStateException ex) { + if (!Case.isCaseOpen()) { //handle in-between condition when case is being closed //and an image was previously selected return; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index b8826cbad9..f57bc27b9c 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -171,14 +171,10 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi } readImageTask = ImageUtils.newReadImageTask(file); readImageTask.setOnSucceeded(succeeded -> { - //Note that all error conditions are allready logged in readImageTask.succeeded() - try { - Case.getCurrentCase(); - } catch (IllegalStateException ex) { + if (!Case.isCaseOpen()) { /* - * Thrown if the current case is closed, so handle - * in-between condition when case is being closed and an - * image was previously selected + * Handle the in-between condition when case is being closed + * and an image was previously selected * * NOTE: I think this is unnecessary -jm */ @@ -201,13 +197,10 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi borderpane.setCursor(Cursor.DEFAULT); }); readImageTask.setOnFailed(failed -> { - try { - Case.getCurrentCase(); - } catch (IllegalStateException ex) { + if (!Case.isCaseOpen()) { /* - * Thrown if no current case. Handle in-between condition - * when case is being closed and an image was previously - * selected + * Handle in-between condition when case is being closed and + * an image was previously selected * * NOTE: I think this is unnecessary -jm */ diff --git a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java index 7b7c8b260b..4c54f587e8 100755 --- a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java +++ b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,14 +27,8 @@ import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.casemodule.Case; -@ActionID( - category = "Help", - id = "org.sleuthkit.autopsy.diagnostics.PerformancePanelAction" -) -@ActionRegistration( - displayName = "#CTL_PerformancePanelAction", - lazy=true -) +@ActionID(category = "Help", id = "org.sleuthkit.autopsy.diagnostics.PerformancePanelAction") +@ActionRegistration(displayName = "#CTL_PerformancePanelAction", lazy = true) @ActionReference(path = "Menu/Help", position = 1437) public final class PerformancePanelAction extends CallableSystemAction { @@ -45,15 +39,10 @@ public final class PerformancePanelAction extends CallableSystemAction { JDialog dialog = new PerformancePanel(); dialog.setVisible(true); } - + @Override public boolean isEnabled() { - try { - Case.getCurrentCase(); - return true; - } catch (IllegalStateException ex) { - return false; - } + return Case.isCaseOpen(); } @Override @@ -63,8 +52,9 @@ public final class PerformancePanelAction extends CallableSystemAction { @Override public boolean asynchronous() { - return false; // run on edt + return false; } + @Override public String getName() { return NbBundle.getMessage(PerformancePanelAction.class, "CTL_PerformancePanelAction"); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index e8ba24d2e9..468dcf5daa 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -349,98 +349,102 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - Case currentCase = Case.getCurrentCase(); + Case currentCase = null; + try { + currentCase = Case.getCurrentCase(); + } catch (IllegalStateException ex) { + /* + * No open case. + */ + } - // close the top component if there's no image in this case - if (currentCase.hasData() == false) { - //this.close(); - ((BeanTreeView) this.jScrollPane1).setRootVisible(false); // hide the root - } else { - // if there's at least one image, load the image and open the top component - List items = new ArrayList<>(); - final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); - items.add(new DataSources()); - items.add(new Views(tskCase)); - items.add(new Results(tskCase)); - items.add(new Tags()); - items.add(new Reports()); - contentChildren = new RootContentChildren(items); + // close the top component if there's no image in this case + if (null == currentCase || currentCase.hasData() == false) { + ((TreeView) this.jScrollPane1).setRootVisible(false); // hide the root + } else { + // if there's at least one image, load the image and open the top component + List items = new ArrayList<>(); + final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); + items.add(new DataSources()); + items.add(new Views(tskCase)); + items.add(new Results(tskCase)); + items.add(new Tags()); + items.add(new Reports()); + contentChildren = new RootContentChildren(items); - Node root = new AbstractNode(contentChildren) { - /** - * to override the right click action in the white blank - * space area on the directory tree window - */ - @Override - public Action[] getActions(boolean popup) { - return new Action[]{}; - } - - // Overide the AbstractNode use of DefaultHandle to return - // a handle which can be serialized without a parent - @Override - public Node.Handle getHandle() { - return new Node.Handle() { - @Override - public Node getNode() throws IOException { - return em.getRootContext(); - } - }; - } - }; - - root = new DirectoryTreeFilterNode(root, true); - - em.setRootContext(root); - em.getRootContext().setName(currentCase.getName()); - em.getRootContext().setDisplayName(currentCase.getName()); - ((BeanTreeView) this.jScrollPane1).setRootVisible(false); // hide the root - - // Reset the forward and back lists because we're resetting the root context - resetHistory(); - - Children childNodes = em.getRootContext().getChildren(); - TreeView tree = getTree(); - - Node results = childNodes.findChild(ResultsNode.NAME); - tree.expandNode(results); - - Children resultsChilds = results.getChildren(); - tree.expandNode(resultsChilds.findChild(KeywordHits.NAME)); - tree.expandNode(resultsChilds.findChild(ExtractedContent.NAME)); - - Accounts accounts = resultsChilds.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); - showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); - showRejectedCheckBox.setSelected(false); - - Node views = childNodes.findChild(ViewsNode.NAME); - Children viewsChilds = views.getChildren(); - for (Node n : viewsChilds.getNodes()) { - tree.expandNode(n); + Node root = new AbstractNode(contentChildren) { + /** + * to override the right click action in the white blank + * space area on the directory tree window + */ + @Override + public Action[] getActions(boolean popup) { + return new Action[]{}; } - tree.collapseNode(views); - - // if the dataResult is not opened - if (!dataResult.isOpened()) { - dataResult.open(); // open the data result top component as well when the directory tree is opened + // Overide the AbstractNode use of DefaultHandle to return + // a handle which can be serialized without a parent + @Override + public Node.Handle getHandle() { + return new Node.Handle() { + @Override + public Node getNode() throws IOException { + return em.getRootContext(); + } + }; } + }; - // select the first image node, if there is one - // (this has to happen after dataResult is opened, because the event - // of changing the selected node fires a handler that tries to make - // dataResult active) - if (childNodes.getNodesCount() > 0) { - try { - em.setSelectedNodes(new Node[]{childNodes.getNodeAt(0)}); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS - } - } + root = new DirectoryTreeFilterNode(root, true); + em.setRootContext(root); + em.getRootContext().setName(currentCase.getName()); + em.getRootContext().setDisplayName(currentCase.getName()); + ((TreeView) this.jScrollPane1).setRootVisible(false); // hide the root + + // Reset the forward and back lists because we're resetting the root context + resetHistory(); + + Children childNodes = em.getRootContext().getChildren(); + TreeView tree = getTree(); + + Node results = childNodes.findChild(ResultsNode.NAME); + tree.expandNode(results); + + Children resultsChilds = results.getChildren(); + tree.expandNode(resultsChilds.findChild(KeywordHits.NAME)); + tree.expandNode(resultsChilds.findChild(ExtractedContent.NAME)); + + Accounts accounts = resultsChilds.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); + showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); + showRejectedCheckBox.setSelected(false); + + Node views = childNodes.findChild(ViewsNode.NAME); + Children viewsChilds = views.getChildren(); + for (Node n : viewsChilds.getNodes()) { + tree.expandNode(n); } - } catch (IllegalStateException ex) { - // Thrown if there is no current case + + tree.collapseNode(views); + + // if the dataResult is not opened + if (!dataResult.isOpened()) { + dataResult.open(); // open the data result top component as well when the directory tree is opened + } + + // select the first image node, if there is one + // (this has to happen after dataResult is opened, because the event + // of changing the selected node fires a handler that tries to make + // dataResult active) + if (childNodes.getNodesCount() > 0) { + try { + em.setSelectedNodes(new Node[]{childNodes.getNodeAt(0)}); + } catch (PropertyVetoException ex) { + LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS + } + } + + } } finally { this.setCursor(null); } @@ -490,11 +494,12 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat @Override public boolean canClose() { - try { - return Case.getCurrentCase().hasData() == false; - } catch (IllegalStateException ex) { - return true; - } + /* + * Only allow the main tree view in the left side of the main window to + * be closed if there is no opne case or the open case has no data + * sources. + */ + return !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; } /** @@ -618,14 +623,12 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * @param newNodes */ private void respondSelection(final Node[] oldNodes, final Node[] newNodes) { - try { - Case.getCurrentCase(); - } catch (IllegalStateException ex) { + if (!Case.isCaseOpen()) { //handle in-between condition when case is being closed //and legacy selection events are pumped - return; + return; } - + // Some lock that prevents certain Node operations is set during the // ExplorerManager selection-change, so we must handle changes after the // selection-change event is processed. @@ -805,7 +808,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Set the selected node using a path to a previously selected node. * * @param previouslySelectedNodePath Path to a previously selected node. - * @param rootNodeName Name of the root node to match, may be null. + * @param rootNodeName Name of the root node to match, may be + * null. */ private void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName) { if (previouslySelectedNodePath == null) { diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java index 39b66fb244..716b63554d 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -94,7 +94,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { } catch (ParseException ex) { // for now, no need to show the error message to the user here } - if (!startDateValue.equals("")) { + if (!startDateValue.isEmpty()) { if (startDate != null) { fromDate = startDate.getTimeInMillis() / 1000; // divided by 1000 because we want to get the seconds, not miliseconds } @@ -114,7 +114,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { } catch (ParseException ex) { // for now, no need to show the error message to the user here } - if (!endDateValue.equals("")) { + if (!endDateValue.isEmpty()) { if (endDate != null) { toDate = endDate.getTimeInMillis() / 1000; // divided by 1000 because we want to get the seconds, not miliseconds } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java index 653faafc3a..603d9d1623 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java @@ -34,12 +34,7 @@ final class FileSearchAction extends CallableSystemAction implements FileSearchP FileSearchAction() { super(); - try { - Case.getCurrentCase(); - setEnabled(true); - } catch (IllegalStateException ex) { - setEnabled(false); - } + setEnabled(Case.isCaseOpen()); Case.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java rename to Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java index 5c57ff2914..8152a097c9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/SilentProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.modules.hashdatabase; +package org.sleuthkit.autopsy.framework; import org.sleuthkit.autopsy.framework.ProgressIndicator; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java index b7e26e2222..644b896bdd 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,14 +30,8 @@ import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.sleuthkit.autopsy.casemodule.Case; -@ActionID( - category = "Tools", - id = "org.sleuthkit.autopsy.ingest.RunIngestAction" -) -@ActionRegistration( - displayName = "#CTL_RunIngestAction", - lazy = false -) +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.ingest.RunIngestAction") +@ActionRegistration(displayName = "#CTL_RunIngestAction", lazy = false) @Messages("CTL_RunIngestAction=Run Ingest") public final class RunIngestAction extends CallableSystemAction implements Presenter.Menu, ActionListener { @@ -80,11 +74,6 @@ public final class RunIngestAction extends CallableSystemAction implements Prese @Override public boolean isEnabled() { - try { - Case.getCurrentCase(); - return true; - } catch (IllegalStateException ex) { - return false; - } + return Case.isCaseOpen() && Case.getCurrentCase().hasData(); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java index 16ee878210..d7471df562 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java @@ -42,12 +42,7 @@ class HashDbPanelSearchAction extends CallableSystemAction { HashDbPanelSearchAction() { super(); - try { - Case.getCurrentCase(); - setEnabled(true); - } catch (IllegalStateException ex) { - setEnabled(false); - } + setEnabled(Case.isCaseOpen()); Case.addPropertyChangeListener(new PropertyChangeListener() { @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index d7a35e0103..2982f383dd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -58,8 +58,7 @@ public final class OpenTimelineAction extends CallableSystemAction implements Pr private static TimeLineController timeLineController = null; private final JButton toolbarButton = new JButton(getName(), - new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS - + new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS /** * Invalidate the reference to the controller so that a new one will be @@ -81,12 +80,7 @@ public final class OpenTimelineAction extends CallableSystemAction implements Pr * disabled that check because if it is executed while a data source is * being added, it blocks the edt */ - try { - Case.getCurrentCase(); - return FX_INITED; - } catch (IllegalStateException ex) { - return false; - } + return Case.isCaseOpen() && FX_INITED; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index fc102541a2..2c5a6472ee 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -569,15 +569,11 @@ public class TimeLineController { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void showTimeLine(AbstractFile file, BlackboardArtifact artifact) { // listen for case changes (specifically images being added, and case changes). - try { - Case.getCurrentCase(); - if (!listeningToAutopsy) { - IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener); - IngestManager.getInstance().addIngestJobEventListener(ingestJobListener); - Case.addPropertyChangeListener(caseListener); - listeningToAutopsy = true; - } - } catch (IllegalStateException ex) { + if (Case.isCaseOpen() && !listeningToAutopsy) { + IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener); + IngestManager.getInstance().addIngestJobEventListener(ingestJobListener); + Case.addPropertyChangeListener(caseListener); + listeningToAutopsy = true; } Platform.runLater(() -> promptForRebuild(file, artifact)); } From efb48b4d5ce26ca5956b663d9be7413c2bcffa15 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 24 Jan 2017 12:23:19 -0500 Subject: [PATCH 12/20] Bug fixes for Case changes, etc. --- .../autopsy/actions/OpenLogFolderAction.java | 21 ++++++++++++------- .../actions/OpenOutputFolderAction.java | 2 ++ .../ShowIngestProgressSnapshotAction.java | 4 +++- .../sleuthkit/autopsy/casemodule/Case.java | 3 +-- .../autopsy/casemodule/CaseCloseAction.java | 2 ++ 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java index a127425651..7197c9a7aa 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java @@ -36,8 +36,11 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** * The action associated with the Help/Open Log Folder menu item. It opens a - * file explorer window for the log subdirectory for the currently open case, or - * the log subdirectory of the user directory if there is no current case. + * file explorer window for either the log subdirectory for the currently open + * case, or the log subdirectory of the user directory, if there is no current + * case. + * + * This action should only be invoked in the event dispatch thread (EDT). */ @ActionRegistration(displayName = "#CTL_OpenLogFolder", iconInMenu = true) @ActionReference(path = "Menu/Help", position = 1750) @@ -51,11 +54,15 @@ public final class OpenLogFolderAction implements ActionListener { File logDir; if (Case.isCaseOpen()) { try { + /* + * Open the log directory for the case. + */ Case currentCase = Case.getCurrentCase(); logDir = new File(currentCase.getLogDirectoryPath()); } catch (IllegalStateException ex) { /* - * The case + * There is no open case, open the application level log + * directory. */ logDir = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "var" + File.separator + "log"); } @@ -64,14 +71,14 @@ public final class OpenLogFolderAction implements ActionListener { } try { - if (logDir.exists() == false) { - logger.log(Level.SEVERE, String.format("The log subdirectory %s does not exist", logDir)); + if (logDir.exists()) { + Desktop.getDesktop().open(logDir); + } else { + logger.log(Level.SEVERE, String.format("The log directory %s does not exist", logDir)); NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "OpenLogFolder.error1", logDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(notifyDescriptor); - } else { - Desktop.getDesktop().open(logDir); } } catch (IOException ex) { logger.log(Level.SEVERE, String.format("Could not open log directory %s", logDir), ex); diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index 93b5b7527e..4294170070 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -40,6 +40,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; * case. If the case is a single-user case, this is the case directory. If the * case is a multi-user case, this is a subdirectory of the case directory * specific to the host machine. + * + * This action should only be invoked in the event dispatch thread (EDT). */ @ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = true) @ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849) diff --git a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java index 68c86f2a44..20bdd9753a 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java @@ -32,6 +32,8 @@ import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; /** * The action associated with the Help/Get Ingest Progress Snapshot menu item. * It opens a the Ingest Progress Snapshot dialog. + * + * This action should only be invoked in the event dispatch thread (EDT). */ @ActionID(category = "Help", id = "org.sleuthkit.autopsy.actions.ShowIngestProgressSnapshotAction") @ActionRegistration(displayName = "#CTL_ShowIngestProgressSnapshotAction", lazy = false) @@ -44,7 +46,7 @@ public final class ShowIngestProgressSnapshotAction extends CallableSystemAction @Override public void performAction() { - IngestProgressSnapshotDialog dialog = new IngestProgressSnapshotDialog(); + new IngestProgressSnapshotDialog(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 486e751f83..50e3b21d90 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -811,12 +811,11 @@ public class Case { * thrown. */ logger.log(Level.INFO, "Closed case with metadata file path {0}", currentCase.getCaseMetadata().getFilePath()); //NON-NLS - Case closedCase = currentCase; + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), currentCase, null)); currentCase = null; if (RuntimeProperties.runningWithGUI()) { updateGUIForCaseClosed(); } - eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null)); progressIndicator.finish(Bundle.Case_progressMessage_finshing()); if (RuntimeProperties.runningWithGUI()) { SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java index 5c69c40091..475ef1fcc8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java @@ -46,6 +46,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager; * The action associated with the Case/Close Case menu item and the Close Case * toolbar button. It closes the current case and pops up the start up window * that allows a user to open another case. + * + * This action should only be invoked in the event dispatch thread (EDT). */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.CaseCloseAction") @ActionRegistration(displayName = "#CTL_CaseCloseAct", lazy = false) From ec108b0b06c98f262d2973d05806c75eb4d2640f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 24 Jan 2017 13:54:12 -0500 Subject: [PATCH 13/20] Bug fixes for Case changes, etc. --- .../sleuthkit/autopsy/actions/ExitAction.java | 20 +++---- .../autopsy/actions/IngestRunningCheck.java | 54 +++++++++++++++++++ .../autopsy/actions/OpenLogFolderAction.java | 2 +- .../actions/OpenOutputFolderAction.java | 10 +++- .../autopsy/casemodule/AddImageAction.java | 12 +++-- .../sleuthkit/autopsy/casemodule/Case.java | 7 ++- 6 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java index ea8b3c8857..d4e1e7e379 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -40,14 +40,16 @@ final public class ExitAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - new Thread(() -> { - try { - Case.closeCurrentCase(); - } catch (CaseActionException ex) { - Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case on exit", ex); //NON-NLS - } finally { - LifecycleManager.getDefault().exit(); - } - }).start(); + if (IngestRunningCheck.checkAndConfirmProceed()) { + new Thread(() -> { + try { + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case on exit", ex); //NON-NLS + } finally { + LifecycleManager.getDefault().exit(); + } + }).start(); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java b/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java new file mode 100644 index 0000000000..64f319f61f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java @@ -0,0 +1,54 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.actions; + +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.ingest.IngestManager; + +/** + * A helper for actions that checks to see if ingest is running. If it is, + * prompts the user to confirm they want to proceed with whatever operation was + * initiated (e.g., closing the case). + */ +public class IngestRunningCheck { + + /** + * Checks to see if ingest is running. If it is, prompts the user to confirm + * they want to proceed with whatever operation was initiated (e.g., closing + * the case). + * + * @return True to proceed, false otherwise. + */ + @Messages({ + "IngestRunningCheck.confirmationDialog.title=Ingest is Running", + "IngestRunningCheck.confirmationDialog.message=Ingest is running, are you sure you want to proceed?" + + }) + public static boolean checkAndConfirmProceed() { + if (IngestManager.getInstance().isIngestRunning()) { + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + Bundle.IngestRunningCheck_confirmationDialog_message(), + Bundle.IngestRunningCheck_confirmationDialog_title(), + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object response = DialogDisplayer.getDefault().notify(descriptor); + return (DialogDescriptor.YES_OPTION == response); + } else { + return true; + } + } + + /** + * Private contructor to prevent instantiation of a utility class. + */ + private IngestRunningCheck() { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java index 7197c9a7aa..62a23b46b0 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java @@ -48,7 +48,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; public final class OpenLogFolderAction implements ActionListener { private static final Logger logger = Logger.getLogger(OpenLogFolderAction.class.getName()); - + @Override public void actionPerformed(ActionEvent e) { File logDir; diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index 4294170070..451bb2b1c3 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -43,13 +43,21 @@ import org.sleuthkit.autopsy.coreutils.Logger; * * This action should only be invoked in the event dispatch thread (EDT). */ -@ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = true) +@ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = false) @ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help") public final class OpenOutputFolderAction extends CallableSystemAction { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(OpenOutputFolderAction.class.getName()); + + public OpenOutputFolderAction() { + /* + * Initially disabled. The Case class enables this action when a case is + * opened and disables it when a case is closed. + */ + this.setEnabled(false); + } @Override public void performAction() { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java index 76d678dec3..b5c91d449b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java @@ -52,6 +52,8 @@ import org.sleuthkit.datamodel.Image; /** * An action that invokes the Add Data Source wizard. + * + * This action should only be invoked in the event dispatch thread (EDT). */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.AddImageAction") @ActionRegistration(displayName = "#CTL_AddImage", lazy = false) @@ -103,7 +105,11 @@ public final class AddImageAction extends CallableSystemAction implements Presen } }); - this.setEnabled(false); // disable this action class + /* + * Disable this action until a case is opened. Currently, the Case class + * enables the action. + */ + this.setEnabled(false); } @Override @@ -118,7 +124,7 @@ public final class AddImageAction extends CallableSystemAction implements Presen } } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); iterator = new AddImageWizardIterator(this); wizardDescriptor = new WizardDescriptor(iterator); wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "AddImageAction.wizard.title")); @@ -131,7 +137,7 @@ public final class AddImageAction extends CallableSystemAction implements Presen dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); Dimension d = dialog.getSize(); dialog.setSize(SIZE); - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); dialog.setVisible(true); dialog.toFront(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 50e3b21d90..5392e202cf 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -58,6 +58,7 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.actions.CallableSystemAction; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.actions.OpenOutputFolderAction; import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException; import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent; import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent; @@ -1279,7 +1280,8 @@ public class Case { CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); - + CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); + /* * Add the case to the recent cases tracker that supplies a list * of recent cases to the recent cases menu item and the @@ -1325,7 +1327,8 @@ public class Case { CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); - + CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); + /* * Clear the notifications in the notfier component in the lower * right hand corner of the main application window. From 8fc797a16ac0b8b2ced3c501ede9256965a534f1 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 24 Jan 2017 15:45:26 -0500 Subject: [PATCH 14/20] Bug fixes for Case changes, etc. --- .../sleuthkit/autopsy/actions/ExitAction.java | 8 +- .../autopsy/actions/IngestRunningCheck.java | 17 ++- .../actions/OpenOutputFolderAction.java | 8 -- .../autopsy/casemodule/AddImageAction.java | 67 +++++------ .../autopsy/casemodule/Bundle.properties | 20 ++-- .../autopsy/casemodule/Bundle_ja.properties | 18 +-- .../sleuthkit/autopsy/casemodule/Case.java | 3 +- .../autopsy/casemodule/CaseCloseAction.java | 68 ++++------- .../autopsy/casemodule/CaseDeleteAction.java | 2 + .../casemodule/CaseInformationPanel.java | 2 +- .../autopsy/casemodule/CaseNewAction.java | 2 + .../autopsy/casemodule/CaseOpenAction.java | 108 ++++++++---------- ...tiesForm.form => CasePropertiesPanel.form} | 20 ++-- ...tiesForm.java => CasePropertiesPanel.java} | 26 ++--- .../casemodule/NewCaseWizardAction.java | 26 ++--- .../autopsy/casemodule/RecentItems.java | 61 ++++------ .../autopsy/corecomponents/Installer.java | 15 ++- .../autopsy/ingest/RunIngestAction.java | 8 ++ 18 files changed, 211 insertions(+), 268 deletions(-) rename Core/src/org/sleuthkit/autopsy/casemodule/{CasePropertiesForm.form => CasePropertiesPanel.form} (92%) rename Core/src/org/sleuthkit/autopsy/casemodule/{CasePropertiesForm.java => CasePropertiesPanel.java} (94%) diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java index d4e1e7e379..a8d0979a97 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -25,6 +25,7 @@ import org.openide.LifecycleManager; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -38,9 +39,14 @@ import org.sleuthkit.autopsy.coreutils.Logger; @ActionID(id = "org.sleuthkit.autopsy.casemodule.ExitAction", category = "Case") final public class ExitAction implements ActionListener { + @NbBundle.Messages({ + "ExitAction.confirmationDialog.title=Ingest is Running", + "ExitAction.confirmationDialog.message=Ingest is running, are you sure you want to exit?" + + }) @Override public void actionPerformed(ActionEvent e) { - if (IngestRunningCheck.checkAndConfirmProceed()) { + if (IngestRunningCheck.checkAndConfirmProceed(Bundle.ExitAction_confirmationDialog_title(), Bundle.ExitAction_confirmationDialog_message())) { new Thread(() -> { try { Case.closeCurrentCase(); diff --git a/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java b/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java index 64f319f61f..e284ee33a1 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java +++ b/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java @@ -8,7 +8,6 @@ package org.sleuthkit.autopsy.actions; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; -import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.ingest.IngestManager; /** @@ -23,18 +22,18 @@ public class IngestRunningCheck { * they want to proceed with whatever operation was initiated (e.g., closing * the case). * + * @param optionsDlgTitle The title for the options dialog used to confirm + * the iser's intentions. + * @param optionsDlgMessage The message for the options dialog used to + * confirm the iser's intentions. + * * @return True to proceed, false otherwise. */ - @Messages({ - "IngestRunningCheck.confirmationDialog.title=Ingest is Running", - "IngestRunningCheck.confirmationDialog.message=Ingest is running, are you sure you want to proceed?" - - }) - public static boolean checkAndConfirmProceed() { + public static boolean checkAndConfirmProceed(String optionsDlgTitle, String optionsDlgMessage) { if (IngestManager.getInstance().isIngestRunning()) { NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - Bundle.IngestRunningCheck_confirmationDialog_message(), - Bundle.IngestRunningCheck_confirmationDialog_title(), + optionsDlgMessage, + optionsDlgTitle, NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index 451bb2b1c3..c3477859e6 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -51,14 +51,6 @@ public final class OpenOutputFolderAction extends CallableSystemAction { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(OpenOutputFolderAction.class.getName()); - public OpenOutputFolderAction() { - /* - * Initially disabled. The Case class enables this action when a case is - * opened and disables it when a case is closed. - */ - this.setEnabled(false); - } - @Override public void performAction() { File outputDir; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java index b5c91d449b..1ae20d1163 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,6 @@ import java.util.logging.Level; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; -import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -46,8 +45,8 @@ import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.openide.util.lookup.ServiceProvider; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.actions.IngestRunningCheck; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.Image; /** @@ -57,13 +56,13 @@ import org.sleuthkit.datamodel.Image; */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.AddImageAction") @ActionRegistration(displayName = "#CTL_AddImage", lazy = false) -@ActionReferences(value = { - @ActionReference(path = "Toolbars/Case", position = 100)}) +@ActionReferences(value = {@ActionReference(path = "Toolbars/Case", position = 100)}) @ServiceProvider(service = AddImageAction.class) public final class AddImageAction extends CallableSystemAction implements Presenter.Toolbar { private static final long serialVersionUID = 1L; - static final Dimension SIZE = new Dimension(875, 550); + private static final Dimension SIZE = new Dimension(875, 550); + private final ChangeSupport cleanupSupport = new ChangeSupport(this); // Keys into the WizardDescriptor properties that pass information between stages of the wizard // : @@ -114,36 +113,30 @@ public final class AddImageAction extends CallableSystemAction implements Presen @Override public void actionPerformed(ActionEvent e) { - if (IngestManager.getInstance().isIngestRunning()) { - final String msg = NbBundle.getMessage(this.getClass(), "AddImageAction.ingestConfig.ongoingIngest.msg"); - if (JOptionPane.showConfirmDialog(null, msg, - NbBundle.getMessage(this.getClass(), - "AddImageAction.ingestConfig.ongoingIngest.title"), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.NO_OPTION) { - return; + String optionsDlgTitle = NbBundle.getMessage(this.getClass(), "AddImageAction.ingestConfig.ongoingIngest.title"); + String optionsDlgMessage = NbBundle.getMessage(this.getClass(), "AddImageAction.ingestConfig.ongoingIngest.msg"); + if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + iterator = new AddImageWizardIterator(this); + wizardDescriptor = new WizardDescriptor(iterator); + wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "AddImageAction.wizard.title")); + wizardDescriptor.putProperty(NAME, e); + wizardDescriptor.setTitleFormat(new MessageFormat("{0}")); + + if (dialog != null) { + dialog.setVisible(false); // hide the old one } + dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); + Dimension d = dialog.getSize(); + dialog.setSize(SIZE); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + dialog.setVisible(true); + dialog.toFront(); + + // Do any cleanup that needs to happen (potentially: stopping the + //add-image process, reverting an image) + runCleanupTasks(); } - - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - iterator = new AddImageWizardIterator(this); - wizardDescriptor = new WizardDescriptor(iterator); - wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "AddImageAction.wizard.title")); - wizardDescriptor.putProperty(NAME, e); - wizardDescriptor.setTitleFormat(new MessageFormat("{0}")); - - if (dialog != null) { - dialog.setVisible(false); // hide the old one - } - dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); - Dimension d = dialog.getSize(); - dialog.setSize(SIZE); - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - dialog.setVisible(true); - dialog.toFront(); - - // Do any cleanup that needs to happen (potentially: stopping the - //add-image process, reverting an image) - runCleanupTasks(); } /** @@ -233,8 +226,8 @@ public final class AddImageAction extends CallableSystemAction implements Presen public void requestFocusButton(String buttonText) { // get all buttons on this wizard panel Object[] wizardButtons = wizardDescriptor.getOptions(); - for (int i = 0; i < wizardButtons.length; i++) { - JButton tempButton = (JButton) wizardButtons[i]; + for (Object wizardButton : wizardButtons) { + JButton tempButton = (JButton) wizardButton; if (tempButton.getText().equals(buttonText)) { tempButton.setDefaultCapable(true); tempButton.requestFocus(); @@ -251,8 +244,6 @@ public final class AddImageAction extends CallableSystemAction implements Presen cleanupSupport.fireChange(); } - ChangeSupport cleanupSupport = new ChangeSupport(this); - /** * Instances of this class implement the cleanup() method to run cleanup * code when the wizard exits. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index f7a21711f5..c4a51ac904 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -15,12 +15,6 @@ NewCaseVisualPanel1.caseNameTextField.text_1= NewCaseVisualPanel1.jLabel2.text_1=Case data will be stored in the following directory: NewCaseVisualPanel1.caseParentDirTextField.text= NewCaseVisualPanel1.caseDirTextField.text_1= -CasePropertiesForm.caseDirLabel.text=Case Directory: -CasePropertiesForm.crDateLabel.text=Created Date: -CasePropertiesForm.caseNameLabel.text=Case Name: -CasePropertiesForm.caseNameTextField.text= -CasePropertiesForm.updateCaseNameButton.text=Update Name -CasePropertiesForm.deleteCaseButton.text=Delete Case CueBannerPanel.autopsyLogo.text= CueBannerPanel.createNewLabel.text=Create New Case CueBannerPanel.openLabel.text=Open Existing Case @@ -31,8 +25,6 @@ CueBannerPanel.openCaseButton.text= CueBannerPanel.openRecentButton.text= OpenRecentCasePanel.cancelButton.text=Cancel OpenRecentCasePanel.jLabel1.text=Recent Cases -CasePropertiesForm.caseNumberLabel.text=Case Number: -CasePropertiesForm.examinerLabel.text=Examiner: NewCaseVisualPanel2.caseNumberTextField.text= NewCaseVisualPanel2.examinerLabel.text=Examiner: NewCaseVisualPanel2.caseNumberLabel.text=Case Number: @@ -218,8 +210,6 @@ NewCaseVisualPanel1.caseParentDirWarningLabel.text= NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user NewCaseVisualPanel1.caseTypeLabel.text=Case Type: -CasePropertiesForm.lbDbType.text=Case Type: -CasePropertiesForm.lbDbName.text=Database Name: SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user! SingleUserCaseConverter.NonUniqueDatabaseName=Database name not unique. @@ -234,3 +224,13 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close +CasePropertiesPanel.updateCaseNameButton.text=Update Name +CasePropertiesPanel.caseNameTextField.text= +CasePropertiesPanel.caseDirLabel.text=Case Directory: +CasePropertiesPanel.crDateLabel.text=Created Date: +CasePropertiesPanel.caseNameLabel.text=Case Name: +CasePropertiesPanel.lbDbName.text=Database Name: +CasePropertiesPanel.lbDbType.text=Case Type: +CasePropertiesPanel.examinerLabel.text=Examiner: +CasePropertiesPanel.caseNumberLabel.text=Case Number: +CasePropertiesPanel.deleteCaseButton.text=Delete Case diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 7f2500d454..fdb39ad133 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -11,19 +11,12 @@ NewCaseVisualPanel1.caseNameLabel.text_1=\u30b1\u30fc\u30b9\u540d\uff1a NewCaseVisualPanel1.caseDirLabel.text=\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\uff1a NewCaseVisualPanel1.caseDirBrowseButton.text=\u95b2\u89a7 NewCaseVisualPanel1.jLabel2.text_1=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u306f\u6b21\u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306b\u4fdd\u5b58\u3055\u308c\u307e\u3059\uff1a -CasePropertiesForm.caseDirLabel.text=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\uff1a -CasePropertiesForm.crDateLabel.text=\u4f5c\u6210\u65e5\uff1a -CasePropertiesForm.caseNameLabel.text=\u30b1\u30fc\u30b9\u540d\uff1a -CasePropertiesForm.updateCaseNameButton.text=\u66f4\u65b0 -CasePropertiesForm.deleteCaseButton.text=\u30b1\u30fc\u30b9\u3092\u524a\u9664 CueBannerPanel.createNewLabel.text=\u65b0\u898f\u30b1\u30fc\u30b9\u3092\u4f5c\u6210 CueBannerPanel.openLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f CueBannerPanel.closeButton.text=\u9589\u3058\u308b CueBannerPanel.openRecentLabel.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f OpenRecentCasePanel.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb OpenRecentCasePanel.jLabel1.text=\u6700\u8fd1\u958b\u3044\u305f\u30d5\u30a1\u30a4\u30eb -CasePropertiesForm.caseNumberLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a -CasePropertiesForm.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uff1a NewCaseVisualPanel2.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uff1a NewCaseVisualPanel2.caseNumberLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a NewCaseVisualPanel2.optionalLabel.text=\u30aa\u30d7\u30b7\u30e7\u30ca\u30eb\uff1a\u30b1\u30fc\u30b9\u756a\u53f7\u304a\u3088\u3073\u8abf\u67fb\u62c5\u5f53\u8005\u3092\u8a2d\u5b9a @@ -196,8 +189,6 @@ CollaborationMonitor.addingDataSourceStatus.msg={0}\u304c\u30c7\u30fc\u30bf\u30d CollaborationMonitor.analyzingDataSourceStatus.msg={0}\u304c{1}\u3092\u89e3\u6790\u4e2d NewCaseVisualPanel1.multiUserCaseRadioButton.text=\u8907\u6570\u30e6\u30fc\u30b6\u30fc NewCaseVisualPanel1.singleUserCaseRadioButton.text=\u5358\u6570\u30e6\u30fc\u30b6\u30fc -CasePropertiesForm.lbDbType.text=\u30b1\u30fc\u30b9\u30bf\u30a4\u30d7\uff1a -CasePropertiesForm.lbDbName.text=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\uff1a SingleUserCaseConverter.BadDatabaseFileName=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u307e\u305b\u3093\uff01 SingleUserCaseConverter.AlreadyMultiUser=\u30b1\u30fc\u30b9\u306f\u65e2\u306b\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u3067\u3059\uff01 SingleUserCaseConverter.NonUniqueDatabaseName=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\u304c\u30e6\u30cb\u30fc\u30af\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 @@ -209,3 +200,12 @@ Case_caseType_multiUser=\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9 Case_caseType_singleUser=\u5358\u6570\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9 CasePropertiesForm.imagesTable.columnModel.title0=\u30d1\u30b9 CasePropertiesForm.imagesTable.columnModel.title1=\u524a\u9664 +CasePropertiesPanel.updateCaseNameButton.text=\u66f4\u65b0 +CasePropertiesPanel.caseDirLabel.text=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\uff1a +CasePropertiesPanel.crDateLabel.text=\u4f5c\u6210\u65e5\uff1a +CasePropertiesPanel.caseNameLabel.text=\u30b1\u30fc\u30b9\u540d\uff1a +CasePropertiesPanel.lbDbName.text=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\uff1a +CasePropertiesPanel.lbDbType.text=\u30b1\u30fc\u30b9\u30bf\u30a4\u30d7\uff1a +CasePropertiesPanel.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uff1a +CasePropertiesPanel.caseNumberLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a +CasePropertiesPanel.deleteCaseButton.text=\u30b1\u30fc\u30b9\u3092\u524a\u9664 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 5392e202cf..26429c1c5a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -95,6 +95,7 @@ import org.sleuthkit.autopsy.framework.ModalDialogProgressIndicator; import org.sleuthkit.autopsy.framework.ProgressIndicator; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.RunIngestAction; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -1280,7 +1281,7 @@ public class Case { CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); - CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); + CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); /* * Add the case to the recent cases tracker that supplies a list diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java index 475ef1fcc8..15f9cb38a4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java @@ -27,9 +27,6 @@ import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.SwingWorker; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; @@ -39,8 +36,8 @@ import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.actions.IngestRunningCheck; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; /** * The action associated with the Case/Close Case menu item and the Close Case @@ -51,7 +48,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager; */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.CaseCloseAction") @ActionRegistration(displayName = "#CTL_CaseCloseAct", lazy = false) -@ActionReferences(value = {@ActionReference(path = "Toolbars/Case", position = 104)}) +@ActionReferences(value = { + @ActionReference(path = "Toolbars/Case", position = 104)}) public final class CaseCloseAction extends CallableSystemAction implements Presenter.Toolbar { private static final long serialVersionUID = 1L; @@ -76,46 +74,30 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese */ @Override public void actionPerformed(ActionEvent e) { - /* - * If ingest is running, give the user the option to abort changing - * cases. - */ - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, - NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object response = DialogDisplayer.getDefault().notify(descriptor); - if (DialogDescriptor.NO_OPTION == response) { - return; - } - } + String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); + String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); + if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { - /* - * Close the case. - */ - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - new SwingWorker() { - - @Override - protected Void doInBackground() throws Exception { - Case.closeCurrentCase(); - return null; - } - - @Override - protected void done() { - try { - get(); - } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, "Error closing the current case", ex); + @Override + protected Void doInBackground() throws Exception { + Case.closeCurrentCase(); + return null; } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - StartupWindowProvider.getInstance().open(); - } - }.execute(); + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Error closing the current case", ex); + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + StartupWindowProvider.getInstance().open(); + } + }.execute(); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java index c510d5e5b2..8a9f7bc17c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java @@ -36,6 +36,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** * The action associated with the Delete button of the Case Properties panel. It * deletes the current case. + * + * This action should only be invoked in the event dispatch thread (EDT). */ final class CaseDeleteAction extends CallableSystemAction { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index 15370e5d45..cc81d70a30 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -54,7 +54,7 @@ class CaseInformationPanel extends javax.swing.JPanel { // put the image paths information into hashmap Map imgPaths = Case.getImagePaths(currentCase.getSleuthkitCase()); - CasePropertiesForm cpf = new CasePropertiesForm(currentCase, crDate, caseDir, imgPaths); + CasePropertiesPanel cpf = new CasePropertiesPanel(currentCase, crDate, caseDir, imgPaths); cpf.setSize(cpf.getPreferredSize()); this.tabbedPane.addTab(Bundle.CaseInformationPanel_caseDetails_header(), cpf); this.tabbedPane.addTab(Bundle.CaseInformationPanel_ingestJobInfo_header(), new IngestJobInfoPanel()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java index 2621277e44..6e3acaceec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java @@ -29,6 +29,8 @@ import org.openide.util.lookup.ServiceProvider; * The action associated with the Case/New Case menu item and the Create New * Case button of the start up window that allows a user to open a case. It * invokes the New Case wizard. + * + * This action should only be invoked in the event dispatch thread (EDT). */ @ServiceProvider(service = CaseNewActionInterface.class) public final class CaseNewAction extends CallableSystemAction implements CaseNewActionInterface { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index afd6a2cb9b..a5064606f2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -29,23 +29,22 @@ import javax.swing.JOptionPane; import javax.swing.SwingWorker; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.lookup.ServiceProvider; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.actions.IngestRunningCheck; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Version; -import org.sleuthkit.autopsy.ingest.IngestManager; /** * The action associated with the Case/Open Case menu item via the layer.xml * file, a toolbar button, and the Create New Case button of the start up window * that allows a user to open a case. It opens an existing case. + * + * This action should only be invoked in the event dispatch thread (EDT). */ @ServiceProvider(service = CaseOpenAction.class) public final class CaseOpenAction extends CallableSystemAction implements ActionListener { @@ -82,67 +81,54 @@ public final class CaseOpenAction extends CallableSystemAction implements Action */ @Override public void actionPerformed(ActionEvent e) { - /* - * If ingest is running, give the user the option to abort changing - * cases. - */ - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, - NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object response = DialogDisplayer.getDefault().notify(descriptor); - if (DialogDescriptor.NO_OPTION == response) { - return; - } - } - - /** - * Pop up a file chooser to allow the user to select a case metadata - * file (.aut file). - */ - int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); - if (retval == JFileChooser.APPROVE_OPTION) { - /* - * Close the startup window, if it is open. + String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); + String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); + if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { + /** + * Pop up a file chooser to allow the user to select a case metadata + * file (.aut file). */ - StartupWindowProvider.getInstance().close(); + int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); + if (retval == JFileChooser.APPROVE_OPTION) { + /* + * Close the startup window, if it is open. + */ + StartupWindowProvider.getInstance().close(); - /* - * Try to open the case associated with the case metadata file the - * user selected. - */ - final String path = fileChooser.getSelectedFile().getPath(); - String dirPath = fileChooser.getSelectedFile().getParent(); - ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator))); - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - new SwingWorker() { + /* + * Try to open the case associated with the case metadata file + * the user selected. + */ + final String path = fileChooser.getSelectedFile().getPath(); + String dirPath = fileChooser.getSelectedFile().getParent(); + ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator))); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { - @Override - protected Void doInBackground() throws Exception { - Case.openAsCurrentCase(path); - return null; - } - - @Override - protected void done() { - try { - get(); - } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - StartupWindowProvider.getInstance().open(); + @Override + protected Void doInBackground() throws Exception { + Case.openAsCurrentCase(path); + return null; } - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - }.execute(); + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().open(); + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }.execute(); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.form similarity index 92% rename from Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form rename to Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.form index 9b61cf90da..1d46bebfbb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.form @@ -161,7 +161,7 @@ - + @@ -173,7 +173,7 @@ - + @@ -185,7 +185,7 @@ - + @@ -197,7 +197,7 @@ - + @@ -209,7 +209,7 @@ - + @@ -224,7 +224,7 @@ - + @@ -239,7 +239,7 @@ - + @@ -251,7 +251,7 @@ - + @@ -263,7 +263,7 @@ - + @@ -275,7 +275,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java similarity index 94% rename from Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java rename to Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java index 6bdee89046..753033fe51 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java @@ -35,13 +35,13 @@ import org.sleuthkit.autopsy.coreutils.Logger; * A panel that allows the user to view various properties of the current case * and change the display name of the case. */ -class CasePropertiesForm extends javax.swing.JPanel { +class CasePropertiesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private Case current = null; private static JPanel caller; // panel for error - CasePropertiesForm(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException { + CasePropertiesPanel(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException { initComponents(); caseNameTextField.setText(currentCase.getDisplayName()); String caseNumber = currentCase.getNumber(); @@ -118,19 +118,19 @@ class CasePropertiesForm extends javax.swing.JPanel { jScrollPane1.setViewportView(jTextArea1); caseNameLabel.setFont(caseNameLabel.getFont().deriveFont(caseNameLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - caseNameLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNameLabel.text")); // NOI18N + caseNameLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseNameLabel.text")); // NOI18N crDateLabel.setFont(crDateLabel.getFont().deriveFont(crDateLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - crDateLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.crDateLabel.text")); // NOI18N + crDateLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.crDateLabel.text")); // NOI18N caseDirLabel.setFont(caseDirLabel.getFont().deriveFont(caseDirLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - caseDirLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseDirLabel.text")); // NOI18N + caseDirLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseDirLabel.text")); // NOI18N caseNameTextField.setFont(caseNameTextField.getFont().deriveFont(caseNameTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - caseNameTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNameTextField.text")); // NOI18N + caseNameTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseNameTextField.text")); // NOI18N updateCaseNameButton.setFont(updateCaseNameButton.getFont().deriveFont(updateCaseNameButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - updateCaseNameButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.updateCaseNameButton.text")); // NOI18N + updateCaseNameButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.updateCaseNameButton.text")); // NOI18N updateCaseNameButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { updateCaseNameButtonActionPerformed(evt); @@ -138,7 +138,7 @@ class CasePropertiesForm extends javax.swing.JPanel { }); deleteCaseButton.setFont(deleteCaseButton.getFont().deriveFont(deleteCaseButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - deleteCaseButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.deleteCaseButton.text")); // NOI18N + deleteCaseButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.deleteCaseButton.text")); // NOI18N deleteCaseButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { deleteCaseButtonActionPerformed(evt); @@ -146,16 +146,16 @@ class CasePropertiesForm extends javax.swing.JPanel { }); caseNumberLabel.setFont(caseNumberLabel.getFont().deriveFont(caseNumberLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - caseNumberLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNumberLabel.text")); // NOI18N + caseNumberLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseNumberLabel.text")); // NOI18N examinerLabel.setFont(examinerLabel.getFont().deriveFont(examinerLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - examinerLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.examinerLabel.text")); // NOI18N + examinerLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.examinerLabel.text")); // NOI18N lbDbType.setFont(lbDbType.getFont().deriveFont(lbDbType.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - lbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbType.text")); // NOI18N + lbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.lbDbType.text")); // NOI18N lbDbName.setFont(lbDbName.getFont().deriveFont(lbDbName.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - lbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbName.text")); // NOI18N + lbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.lbDbName.text")); // NOI18N caseDirField.setMinimumSize(new java.awt.Dimension(25, 14)); @@ -307,7 +307,7 @@ class CasePropertiesForm extends javax.swing.JPanel { try { current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath); } catch (CaseActionException ex) { - Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS + Logger.getLogger(CasePropertiesPanel.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 3f48161330..5c7ad8aeca 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -28,24 +28,24 @@ import java.util.logging.Level; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.SwingWorker; -import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.WizardDescriptor; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.SystemAction; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.actions.IngestRunningCheck; import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; /** * The action associated with the Case/New Case menu item, t toolbar button, and * the button in the start up window that allows users to open cases action. It * runs first the New Case wizard, then the Add Data Source wizard. + * + * This action should only be invoked in the event dispatch thread (EDT). */ final class NewCaseWizardAction extends CallableSystemAction { @@ -55,23 +55,11 @@ final class NewCaseWizardAction extends CallableSystemAction { @Override public void performAction() { - /* - * If ingest is running, give the user the option to abort changing - * cases. - */ - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, - NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object response = DialogDisplayer.getDefault().notify(descriptor); - if (DialogDescriptor.NO_OPTION == response) { - return; - } + String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); + String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); + if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { + runNewCaseWizard(); } - runNewCaseWizard(); } private void runNewCaseWizard() { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index 1067ae1022..d46fce31cb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -23,22 +23,20 @@ import java.awt.event.ActionListener; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.actions.IngestRunningCheck; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action listener for a specific case, associated with a Recent Cases menu * item for the case by a DynamicMenuContent content JMenuItem. + * + * This action should only be invoked in the event dispatch thread (EDT). */ class RecentItems implements ActionListener { private static final Logger logger = Logger.getLogger(RecentItems.class.getName()); - private final String caseName; private final String caseMetaDataFilePath; /** @@ -50,7 +48,6 @@ class RecentItems implements ActionListener { * @param caseMetaDataFilePath The path to the case metadata file. */ RecentItems(String caseName, String caseMetaDataFilePath) { - this.caseName = caseName; this.caseMetaDataFilePath = caseMetaDataFilePath; } @@ -61,40 +58,24 @@ class RecentItems implements ActionListener { */ @Override public void actionPerformed(ActionEvent e) { - /* - * If ingest is running, give the user the option to abort changing - * cases. - */ - if (IngestManager.getInstance().isIngestRunning()) { - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"), - NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"), - NotifyDescriptor.YES_NO_OPTION, - NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object response = DialogDisplayer.getDefault().notify(descriptor); - if (DialogDescriptor.NO_OPTION == response) { - return; - } + String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); + String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); + if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { + new Thread(() -> { + try { + Case.openAsCurrentCase(caseMetaDataFilePath); + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), + NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().open(); + }); + } + }).start(); } - - /* - * Open the case. - */ - new Thread(() -> { - try { - Case.openAsCurrentCase(caseMetaDataFilePath); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); - StartupWindowProvider.getInstance().open(); - }); - } - }).start(); } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java index 3bf74e42e8..814e4bf654 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; import javax.swing.BorderFactory; +import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.UnsupportedLookAndFeelException; @@ -32,7 +33,9 @@ import org.netbeans.spi.sendopts.OptionProcessor; import org.netbeans.swing.tabcontrol.plaf.DefaultTabbedContainerUI; import org.openide.modules.ModuleInstall; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.actions.IngestRunningCheck; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; @@ -101,11 +104,13 @@ public class Installer extends ModuleInstall { @Override public void close() { - try { - Case.closeCurrentCase(); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, "Error closing current case", ex); //NON-NLS - } + new Thread(() -> { + try { + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, "Error closing current case", ex); //NON-NLS + } + }).start(); } private void setLookAndFeel() { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java index 644b896bdd..49875a3997 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java @@ -30,6 +30,11 @@ import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.sleuthkit.autopsy.casemodule.Case; +/** + * The action associated with assorted Run Ingest Modules menu items. + * + * This action should only be invoked in the event dispatch thread (EDT). + */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.ingest.RunIngestAction") @ActionRegistration(displayName = "#CTL_RunIngestAction", lazy = false) @Messages("CTL_RunIngestAction=Run Ingest") @@ -45,6 +50,9 @@ public final class RunIngestAction extends CallableSystemAction implements Prese return action; } + private RunIngestAction() { + } + @Override public void performAction() { getMenuPresenter(); From 18b301394a12fca1b452be3d7db73617688e87bb Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 24 Jan 2017 15:59:10 -0500 Subject: [PATCH 15/20] Bug fix --- .../sleuthkit/autopsy/keywordsearch/IndexUpgrader.java | 9 ++++++++- .../autopsy/keywordsearch/SolrSearchService.java | 9 ++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java index ead8609d32..b1bcfb837a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java @@ -44,30 +44,37 @@ class IndexUpgrader { JAVA_PATH = PlatformUtil.getJavaPath(); } - void performIndexUpgrade(String newIndexDir, Index indexToUpgrade, String tempResultsDir) throws AutopsyService.AutopsyServiceException { + Index performIndexUpgrade(String newIndexDir, Index indexToUpgrade, String tempResultsDir) throws AutopsyService.AutopsyServiceException { // ELTODO Check for cancellation at whatever points are feasible // Run the upgrade tools on the contents (core) in ModuleOutput/keywordsearch/data/solrX_schema_Y/index File tmpDir = Paths.get(tempResultsDir, "IndexUpgrade").toFile(); //NON-NLS tmpDir.mkdirs(); + Index upgradedIndex; double currentSolrVersion = NumberUtils.toDouble(indexToUpgrade.getSolrVersion()); try { // upgrade from Solr 4 to 5 currentSolrVersion = upgradeSolrIndexVersion4to5(currentSolrVersion, newIndexDir, tempResultsDir); // upgrade from Solr 5 to 6 currentSolrVersion = upgradeSolrIndexVersion5to6(currentSolrVersion, newIndexDir, tempResultsDir); + + // create upgraded index object + upgradedIndex = new Index(newIndexDir, Double.toString(currentSolrVersion), indexToUpgrade.getSchemaVersion()); + upgradedIndex.setNewIndex(true); } catch (Exception ex) { // catch-all firewall for exceptions thrown by Solr upgrade tools throw new AutopsyService.AutopsyServiceException("Exception while running Solr index upgrade in " + newIndexDir, ex); //NON-NLS } finally { if (currentSolrVersion != NumberUtils.toDouble(IndexFinder.getCurrentSolrVersion())) { // upgrade did not complete, delete the new index directories + upgradedIndex = null; if (!new File(newIndexDir).delete()) { logger.log(Level.SEVERE, "Unable to delete folder {0}", newIndexDir); //NON-NLS } } } + return upgradedIndex; } /** diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 96c2f86ac2..f09b8c3727 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -216,11 +216,10 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService // upgrade the existing index to the latest supported Solr version IndexUpgrader indexUpgrader = new IndexUpgrader(); - indexUpgrader.performIndexUpgrade(newIndexDir, indexToUpgrade, context.getCase().getTempDirectory()); - - // set the upgraded index as the index to be used for this case - currentVersionIndex = new Index(newIndexDir, IndexFinder.getCurrentSolrVersion(), indexToUpgrade.getSchemaVersion()); - currentVersionIndex.setNewIndex(true); + currentVersionIndex = indexUpgrader.performIndexUpgrade(newIndexDir, indexToUpgrade, context.getCase().getTempDirectory()); + if (currentVersionIndex == null) { + throw new AutopsyServiceException("Unable to upgrade index to the latest version of Solr"); + } } } } From ef1d492e3adbb3bf064cd477ab78233cec700d81 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 24 Jan 2017 16:03:10 -0500 Subject: [PATCH 16/20] Code review comments --- .../autopsy/keywordsearch/SolrSearchService.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index f09b8c3727..086a224304 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -228,10 +228,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService try { KeywordSearch.getServer().openCoreForCase(context.getCase(), currentVersionIndex); } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Failed to open or create core for %s", context.getCase().getCaseDirectory()), ex); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { - MessageNotifyUtil.Notify.error(NbBundle.getMessage(KeywordSearch.class, "KeywordSearch.openCore.notification.msg"), ex.getMessage()); - } + throw new AutopsyServiceException("Failed to open or create core for " + context.getCase().getCaseDirectory(), ex); } } @@ -258,10 +255,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService Thread.sleep(2000); KeywordSearch.getServer().closeCore(); } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { - MessageNotifyUtil.Notify.error(NbBundle.getMessage(KeywordSearch.class, "KeywordSearch.closeCore.notification.msg"), ex.getMessage()); - } + throw new AutopsyServiceException("Failed to close core for " + context.getCase().getCaseDirectory(), ex); } } From f7ea9f8e4d51becdaae08c7318895a0018272e5f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 24 Jan 2017 18:39:51 -0500 Subject: [PATCH 17/20] Case metadata bug fix --- .../org/sleuthkit/autopsy/casemodule/CaseMetadata.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 5cc9a206b6..3238846a6f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -445,6 +445,16 @@ public final class CaseMetadata { this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, true); } + /* + * Fix up the case database name due to a bug that for a time caused + * the absolute paths of single-user case databases to be stored. + */ + Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName); + if (possibleAbsoluteCaseDbPath.getNameCount() > 1) { + Path caseDirectoryPath = Paths.get(getCaseDirectory()); + this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString(); + } + /* * Update the file to the current schema, if necessary. */ From 3ada14c54d587b99926cddb33267a5ba5f4c657b Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 24 Jan 2017 18:40:14 -0500 Subject: [PATCH 18/20] Enable search features by schema version --- .../DropdownSingleTermSearchPanel.java | 7 +- .../keywordsearch/DropdownToolbar.java | 99 +++++++++++-------- .../KeywordSearchIngestModule.java | 16 +++ .../autopsy/keywordsearch/Server.java | 17 ++++ 4 files changed, 97 insertions(+), 42 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index a5024fd7b0..512d62238a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -82,7 +82,7 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { @Override public void focusLost(FocusEvent e) { - if (keywordTextField.getText().equals("")) { + if (keywordTextField.getText().isEmpty()) { clearSearchBox(); } } @@ -124,6 +124,11 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { keywordTextField.setText(""); } + void setRegexSearchEnabled(boolean enabled) { + exactRadioButton.setSelected(true); + regexRadioButton.setEnabled(enabled); + } + /** * Gets a single keyword list consisting of a single keyword encapsulating * the input term(s)/phrase/substring/regex. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java index 90835907ff..2fa8f652e5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2003-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,12 +24,12 @@ import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.coreutils.Logger; /** * A panel that provides a toolbar button for the dropdown keyword list search @@ -39,7 +39,7 @@ import org.sleuthkit.autopsy.core.RuntimeProperties; class DropdownToolbar extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(DropdownToolbar.class.getName()); + private static final Logger logger = Logger.getLogger(DropdownToolbar.class.getName()); private static DropdownToolbar instance; private SearchSettingsChangeListener searchSettingsChangeListener; private boolean active = false; @@ -82,7 +82,7 @@ class DropdownToolbar extends javax.swing.JPanel { listsPanel.addSearchButtonActionListener((ActionEvent e) -> { listsMenu.setVisible(false); }); - + // Adding border of six to account for menu border listsMenu.setSize(listsPanel.getPreferredSize().width + 6, listsPanel.getPreferredSize().height + 6); listsMenu.add(listsPanel); @@ -150,8 +150,60 @@ class DropdownToolbar extends javax.swing.JPanel { } searchMenu.show(searchDropButton, searchDropButton.getWidth() - searchMenu.getWidth(), searchDropButton.getHeight() - 1); } - - + + private class SearchSettingsChangeListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + String changed = evt.getPropertyName(); + if (changed.equals(Case.Events.CURRENT_CASE.toString())) { + dropPanel.clearSearchBox(); + if (RuntimeProperties.runningWithGUI() || null == evt.getNewValue()) { + Server server = KeywordSearch.getServer(); + if (server.coreIsOpen()) { + try { + Index indexInfo = server.getIndexInfo(); + listsButton.setEnabled(IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion())); + searchDropButton.setEnabled(true); + dropPanel.setRegexSearchEnabled(IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion())); + active = true; + } catch (KeywordSearchModuleException ex) { + searchDropButton.setEnabled(false); + listsButton.setEnabled(false); + active = false; + } + } else { + searchDropButton.setEnabled(false); + listsButton.setEnabled(false); + active = false; + } + } else { + searchDropButton.setEnabled(false); + listsButton.setEnabled(false); + active = false; + } + } else if (changed.equals(Server.CORE_EVT)) { + final Server.CORE_EVT_STATES state = (Server.CORE_EVT_STATES) evt.getNewValue(); + switch (state) { + case STARTED: + try { + final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles(); + KeywordSearch.fireNumIndexedFilesChange(null, numIndexedFiles); + } catch (NoOpenCoreException ex) { + logger.log(Level.SEVERE, "Error executing Solr query, {0}", ex); //NON-NLS + } catch (KeywordSearchModuleException se) { + logger.log(Level.SEVERE, "Error executing Solr query, {0}", se.getMessage()); //NON-NLS + } + break; + case STOPPED: + break; + default: + } + } + } + + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -248,39 +300,4 @@ class DropdownToolbar extends javax.swing.JPanel { private javax.swing.JPopupMenu searchMenu; // End of variables declaration//GEN-END:variables - private class SearchSettingsChangeListener implements PropertyChangeListener { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - String changed = evt.getPropertyName(); - if (changed.equals(Case.Events.CURRENT_CASE.toString())) { - dropPanel.clearSearchBox(); - setFields(null != evt.getNewValue() && RuntimeProperties.runningWithGUI()); - } else if (changed.equals(Server.CORE_EVT)) { - final Server.CORE_EVT_STATES state = (Server.CORE_EVT_STATES) evt.getNewValue(); - switch (state) { - case STARTED: - try { - final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles(); - KeywordSearch.fireNumIndexedFilesChange(null, numIndexedFiles); - } catch (NoOpenCoreException ex) { - LOGGER.log(Level.SEVERE, "Error executing Solr query, {0}", ex); //NON-NLS - } catch (KeywordSearchModuleException se) { - LOGGER.log(Level.SEVERE, "Error executing Solr query, {0}", se.getMessage()); //NON-NLS - } - break; - case STOPPED: - break; - default: - } - } - } - - private void setFields(boolean enabled) { - searchDropButton.setEnabled(enabled); - listsButton.setEnabled(enabled); - active = enabled; - } - } - } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 4a0c3902c8..d5d0afad1e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; @@ -114,6 +115,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { /** * Records the ingest status for a given file for a given ingest job. Used * for final statistics at the end of the job. + * * @param ingestJobId id of ingest job * @param fileId id of file * @param status ingest status of the file @@ -140,6 +142,10 @@ public final class KeywordSearchIngestModule implements FileIngestModule { * retrieves settings, keyword lists to run on * */ + @Messages({ + "KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get schema version for text index.", + "# {0} - schema version number", "KeywordSearchIngestModule.startupMessage.indexSchemaNotSupported=Adding text no longer supported for schema version {0} of the text index." + }) @Override public void startUp(IngestJobContext context) throws IngestModuleException { initialized = false; @@ -151,11 +157,21 @@ public final class KeywordSearchIngestModule implements FileIngestModule { throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startUp_noOpenCore_msg()); } + try { + Index indexInfo = server.getIndexInfo(); + if (!IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion())) { + throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startupMessage_indexSchemaNotSupported(indexInfo.getSchemaVersion())); + } + } catch (KeywordSearchModuleException ex) { + throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startupMessage_failedToGetIndexSchema(), ex); + } + try { fileTypeDetector = new FileTypeDetector(); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); } + ingester = Ingester.getDefault(); this.context = context; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 79c91b82ab..10f1c0f72d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -696,6 +696,19 @@ public class Server { } } + Index getIndexInfo() throws KeywordSearchModuleException { + currentCoreLock.readLock().lock(); + try { + if (null != currentCore) { + return currentCore.getIndexInfo(); + } else { + throw new KeywordSearchModuleException("Cannot get text index info, no core is open"); + } + } finally { + currentCoreLock.readLock().unlock(); + } + } + void closeCore() throws KeywordSearchModuleException { currentCoreLock.writeLock().lock(); try { @@ -1320,6 +1333,10 @@ public class Server { return name; } + private Index getIndexInfo() { + return this.textIndex; + } + private QueryResponse query(SolrQuery sq) throws SolrServerException, IOException { return solrCore.query(sq); } From adbc85f7448c93b95c322b07537fe25a774eece5 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 25 Jan 2017 12:28:05 +0100 Subject: [PATCH 19/20] fix npe when a hit is in the file name and so the "parent" index entry is returned, which does not include a chunksize. Allways accept these hits. --- .../src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java | 4 ++-- .../src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index 28859549d3..6c65f075ef 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -223,8 +223,8 @@ class LuceneQuery implements KeywordSearchQuery { final Integer chunkSize = (Integer) resultDoc.getFieldValue(Server.Schema.CHUNK_SIZE.toString()); final String content_str = resultDoc.get(Server.Schema.CONTENT_STR.toString()).toString(); - Integer firstOccurence = content_str.indexOf(strippedQueryString); - if (firstOccurence < chunkSize) { + int firstOccurence = content_str.indexOf(strippedQueryString); + if (chunkSize != null && firstOccurence < chunkSize) { matches.add(createKeywordtHit(highlightResponse, docId)); } } catch (TskException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 3aae742571..aa11182b86 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -243,7 +243,7 @@ final class RegexQuery implements KeywordSearchQuery { while (hitMatcher.find(offset)) { StringBuilder snippet = new StringBuilder(); - if (hitMatcher.start() >= chunkSize) { + if (chunkSize != null && hitMatcher.start() >= chunkSize) { break; } From 8fcdee830b477a70f3f574a243c7af45a624f761 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 25 Jan 2017 16:36:31 +0100 Subject: [PATCH 20/20] fix LuceneQuery for old index schemas --- .../autopsy/keywordsearch/LuceneQuery.java | 16 +++++++++++++--- .../autopsy/keywordsearch/RegexQuery.java | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index 6c65f075ef..9e99394a34 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -23,8 +23,10 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.logging.Level; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.response.QueryResponse; @@ -221,11 +223,19 @@ class LuceneQuery implements KeywordSearchQuery { * will get picked up in the next one. */ final String docId = resultDoc.getFieldValue(Server.Schema.ID.toString()).toString(); final Integer chunkSize = (Integer) resultDoc.getFieldValue(Server.Schema.CHUNK_SIZE.toString()); - final String content_str = resultDoc.get(Server.Schema.CONTENT_STR.toString()).toString(); + String content_str = Objects.toString(resultDoc.get(Server.Schema.CONTENT_STR.toString()), null); - int firstOccurence = content_str.indexOf(strippedQueryString); - if (chunkSize != null && firstOccurence < chunkSize) { + double indexSchemaVersion = NumberUtils.toDouble(KeywordSearch.getServer().getIndexInfo().getSchemaVersion()); + if (indexSchemaVersion < 2.0) { + //old schema versions don't support chunk_size or the content_str fields, so just accept hits matches.add(createKeywordtHit(highlightResponse, docId)); + } else { + //for new schemas, check that the hit is before the chunk/window boundary. + int firstOccurence = StringUtils.indexOf(content_str, strippedQueryString); + //there is no chunksize field for "parent" entries in the index + if (chunkSize != null && firstOccurence < chunkSize) { + matches.add(createKeywordtHit(highlightResponse, docId)); + } } } catch (TskException ex) { return matches; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index aa11182b86..0c5ac166f2 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -243,6 +243,7 @@ final class RegexQuery implements KeywordSearchQuery { while (hitMatcher.find(offset)) { StringBuilder snippet = new StringBuilder(); + //"parent" entries in the index don't have chunk size, so just accept those hits if (chunkSize != null && hitMatcher.start() >= chunkSize) { break; }