From b4aca79862a84e657149a6e2918991acd0c95cb0 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Feb 2019 16:05:25 -0500 Subject: [PATCH 01/32] Make case deletion more verbose --- .../casemodule/Bundle.properties-MERGED | 45 +- .../sleuthkit/autopsy/casemodule/Case.java | 679 ++++++++++++------ .../autopsy/casemodule/CaseMetadata.java | 4 + .../multiusercases/CaseNodeData.java | 47 ++ .../Bundle.properties-MERGED | 5 + .../autoingest/AutoIngestControlPanel.form | 27 +- .../autoingest/AutoIngestControlPanel.java | 79 +- .../autoingest/AutoIngestJob.java | 2 +- .../autoingest/AutoIngestJobLogger.java | 4 +- .../autoingest/AutoIngestManager.java | 121 +--- .../AutoIngestMetricsCollector.java | 2 +- .../autoingest/AutoIngestMonitor.java | 2 +- .../experimental/autoingest/Bundle.properties | 1 - .../autoingest/Bundle.properties-MERGED | 40 +- .../autoingest/CaseDeletionTask.java | 275 +++++++ .../keywordsearch/Bundle.properties-MERGED | 62 +- .../recentactivity/Bundle.properties-MERGED | 100 ++- .../Bundle.properties-MERGED | 11 +- 18 files changed, 955 insertions(+), 551 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED create mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index dc659aac05..a5ca101dcb 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -11,7 +11,7 @@ Case.deleteCaseFailureMessageBox.message=Error deleting case: {0} Case.deleteCaseFailureMessageBox.title=Failed to Delete Case Case.exceptionMessage.cancelledByUser=Cancelled by user. Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first. -Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service. +Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user. Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case. # {0} - exception message @@ -34,10 +34,9 @@ Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case datab Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}. Case.exceptionMessage.emptyCaseDir=Must specify a case directory path. Case.exceptionMessage.emptyCaseName=Must specify a case name. -Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details +Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details. # {0} - exception message Case.exceptionMessage.execExceptionWrapperMessage={0} -Case.exceptionMessage.failedToDeleteCoordinationServiceNodes=Failed to delete the coordination service nodes for the case. # {0} - exception message Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}. Case.exceptionMessage.metadataUpdateError=Failed to update case metadata @@ -55,18 +54,46 @@ Case.progressMessage.clearingTempDirectory=Clearing case temp directory... Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources... Case.progressMessage.closingCaseDatabase=Closing case database... Case.progressMessage.closingCaseLevelServices=Closing case-level services... +Case.progressMessage.connectingToCoordSvc=Connecting to coordination service... Case.progressMessage.creatingCaseDatabase=Creating case database... Case.progressMessage.creatingCaseDirectory=Creating case directory... Case.progressMessage.creatingCaseNodeData=Creating coordination service node data... +Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log coordination service node... Case.progressMessage.deletingCaseDatabase=Deleting case database... Case.progressMessage.deletingCaseDirectory=Deleting case directory... -Case.progressMessage.deletingCoordinationServiceNodes=Deleting coordination service nodes... +Case.progressMessage.deletingDirectroyCoordinationServiceNode=Deleting case ersource coordination service node... +Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node... Case.progressMessage.deletingTextIndex=Deleting text index... +# {0} - exception message +Case.progressMessage.errorConnectingToCoordSvc=An occurred connecting to the coordination service (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorDeletingCaseDb=An occurred deleting the case database (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorDeletingCaseDir=An occurred deleting the case directory (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorDeletingCaseDirLockNode=An occurred deleting the case directory lock node (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorDeletingJobLogLockNode=An occurred deleting the case auto ingest log lock node (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorDeletingResourcesLockNode=An occurred deleting the case resources lock node (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorDeletingTextIndex=An occurred deleting the text index for the case (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorLockingCase=An error occurred exclusively locking the case for deletion (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}. +# {0} - exception message +Case.progressMessage.errorSavingDeletedItemsFlags=An occurred saving the deleted items flags in the coordination service database (see log for details): {0}. +Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case... +Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data. Case.progressMessage.openingApplicationServiceResources=Opening application service case resources... Case.progressMessage.openingCaseDatabase=Opening case database... Case.progressMessage.openingCaseLevelServices=Opening case-level services... Case.progressMessage.preparing=Preparing... Case.progressMessage.preparingToOpenCaseResources=Preparing to open case resources.
This may take time if another user is upgrading the case. +Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu... Case.progressMessage.savingCaseMetadata=Saving case metadata to file... Case.progressMessage.settingUpNetworkCommunications=Setting up network communications... Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications... @@ -159,13 +186,6 @@ LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders Menu/Case/OpenRecentCase=Open Recent Case CTL_CaseDeleteAction=Delete Case -MultiUserCaseNode.column.createTime=Create Time -MultiUserCaseNode.column.name=Name -MultiUserCaseNode.column.path=Path -MultiUserCasesBrowserPanel.waitNode.message=Please Wait... -OpenCaseAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. -OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. -OpenCaseAutoIngestLogAction.menuItemText=Open Auto Ingest Log File OpenIDE-Module-Name=Case NewCaseVisualPanel1.caseNameLabel.text_1=Case Name: NewCaseVisualPanel1.caseDirLabel.text=Base Directory: @@ -419,5 +439,6 @@ CaseDetailsPanel.lbDbType.text=Case Type: CaseDetailsPanel.caseNameLabel.text=Case Name: OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name OpenMultiUserCasePanel.cancelButton.text=Cancel -OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case OpenMultiUserCasePanel.openSingleUserCaseButton.text=Open Single-User Case... +OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case +OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 521566d294..7f021111f9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -35,6 +35,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -58,6 +59,7 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; 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; @@ -119,7 +121,6 @@ import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException; -import org.sleuthkit.autopsy.coreutils.StopWatch; /** * An Autopsy case. Currently, only one case at a time may be open. @@ -139,6 +140,8 @@ public class Case { private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS private static final String CASE_ACTION_THREAD_NAME = "%s-case-action"; private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources"; + private static final String RESOURCES_LOCK_SUFFIX = "_resources"; //NON-NLS + private static final String AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; private static final Logger logger = Logger.getLogger(Case.class.getName()); private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); private static final Object caseActionSerializationLock = new Object(); @@ -687,6 +690,9 @@ public class Case { * and may be a wrapper for a lower-level * exception. */ + @Messages({ + "Case.progressIndicatorTitle.deletingCase=Deleting Case" + }) public static void deleteCurrentCase() throws CaseActionException { synchronized (caseActionSerializationLock) { if (null == currentCase) { @@ -694,119 +700,8 @@ public class Case { } CaseMetadata metadata = currentCase.getMetadata(); 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. - */ - @Messages({ - "Case.progressIndicatorTitle.deletingCase=Deleting Case", - "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.", - "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", - "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service.", - "Case.exceptionMessage.failedToDeleteCoordinationServiceNodes=Failed to delete the coordination service nodes for the case." - }) - public static void deleteCase(CaseMetadata metadata) throws CaseActionException { - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - synchronized (caseActionSerializationLock) { - if (null != currentCase) { - throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()); - } - } - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to acquire caseActionSerializationLock (Java monitor in Case class) for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - - /* - * Set up either a GUI progress indicator without a cancel button (can't - * cancel deleting a case) or a logging progress indicator. - */ - ProgressIndicator progressIndicator; - if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); - } else { - progressIndicator = new LoggingProgressIndicator(); - } - progressIndicator.start(Bundle.Case_progressMessage_preparing()); - try { - if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - deleteCase(metadata, progressIndicator); - } else { - /* - * First, acquire an exclusive case directory lock. The case - * cannot be deleted if another node has it open. - */ - progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser()); - stopWatch.reset(); - stopWatch.start(); - try (CoordinationService.Lock dirLock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to acquire case directory coordination service lock for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - if (dirLock != null) { - deleteCase(metadata, progressIndicator); - } else { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); - } - } catch (CoordinationServiceException ex) { - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to fail to acquire case directory coordination service lock for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - throw new CaseActionException(Bundle.Case_exceptionMessage_failedToDeleteCoordinationServiceNodes(), ex); - } - try { - deleteCoordinationServiceNodes(metadata, progressIndicator); - } catch (CoordinationServiceException ex) { - throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex); - } - } - } finally { - progressIndicator.finish(); - } - } - - /** - * Deletes the coordination nodes for a multi-user case. - * - * @param metadata The metadata for the case to delete. - * @param progressIndicator The progress indicator for the deletion - * operation. - * - * @throws CoordinationServiceException If there is a problem getting the - * coordination service. - */ - @Messages({ - "Case.progressMessage.deletingCoordinationServiceNodes=Deleting coordination service nodes..." - }) - static void deleteCoordinationServiceNodes(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CoordinationServiceException { - progressIndicator.progress(Bundle.Case_progressMessage_deletingCoordinationServiceNodes()); - CoordinationService coordinationService; - coordinationService = CoordinationService.getInstance(); - String resourcesLockNodePath = metadata.getCaseDirectory() + "_resources"; - try { - coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); - } catch (CoordinationServiceException ex) { - /* - * Log but do not notify the user. - */ - logger.log(Level.SEVERE, String.format("Failed to delete resources lock coordination service node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - } - String caseDirectoryLockNodePath = metadata.getCaseDirectory(); - try { - coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath); - } catch (CoordinationServiceException ex) { - /* - * Log but do not notify the user. - */ - logger.log(Level.SEVERE, String.format("Failed to delete case directory lock coordination service node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + ProgressIndicator progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); + deleteCase(metadata, progressIndicator); } } @@ -988,101 +883,394 @@ public class Case { } /** - * - * Deletes the case directory of a deleted case and removes the case form - * the Recent Cases menu. + * Deletes a case. The case to be deletd must not be the "current case." * * @param metadata The case metadata. * @param progressIndicator A progress indicator. * - * @throws UserPreferencesException if there is a problem getting the case - * databse connection info for a multi-user - * case. - * @throws ClassNotFoundException if there is a problem loading the JDBC - * driver for PostgreSQL for a multi-user - * case. - * @throws SQLException If there is a problem + * @throws CaseActionException If there were one or more errors deleting the + * case. */ @Messages({ - "Case.progressMessage.deletingTextIndex=Deleting text index...", - "Case.progressMessage.deletingCaseDatabase=Deleting case database...", - "Case.progressMessage.deletingCaseDirectory=Deleting case directory...", - "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details" + "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first." }) - private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { - StopWatch stopWatch = new StopWatch(); - boolean errorsOccurred = false; - if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { - /* - * Delete the case database from the database server. - */ - stopWatch.start(); - try { - progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); - CaseDbConnectionInfo db; - 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); - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to delete case database for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - } - } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { - logger.log(Level.SEVERE, String.format("Failed to delete case database %s for %s (%s) in %s", metadata.getCaseDatabaseName(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - errorsOccurred = true; - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to fail delete case database for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); + public static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + synchronized (caseActionSerializationLock) { + if (null != currentCase) { + throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()); } } - /* - * Delete the text index. - */ + progressIndicator.start(Bundle.Case_progressMessage_preparing()); + try { + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { + deleteSingleUserCase(metadata, progressIndicator); + } else { + deleteMultiUserCase(metadata, progressIndicator); + } + } finally { + progressIndicator.finish(); + } + } + + /** + * Deletes a single-user case. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there were one or more errors deleting the + * case. + */ + @Messages({ + "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details.", + "# {0} - exception message", "Case.progressMessage.errorDeletingTextIndex=An occurred deleting the text index for the case (see log for details): {0}.", + "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDir=An occurred deleting the case directory (see log for details): {0}." + }) + private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + boolean errorsOccurred = false; + try { + deleteTextIndex(metadata, progressIndicator); + } catch (KeywordSearchServiceException ex) { + errorsOccurred = true; + logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingTextIndex(ex.getMessage())); + } + + try { + deleteCaseDirectory(metadata, progressIndicator); + } catch (CaseActionException ex) { + errorsOccurred = true; + logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDir(ex.getMessage())); + } + + deleteFromRecentCases(metadata, progressIndicator); + + if (errorsOccurred) { + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + } + + /** + * Deletes a multi-user case. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there were one or more errors deleting the + * case. + */ + @Messages({ + "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", + "# {0} - exception message", "Case.progressMessage.errorConnectingToCoordSvc=An occurred connecting to the coordination service (see log for details): {0}.", + "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", + "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user.", + "# {0} - exception message", "Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}.", + "# {0} - exception message", "Case.progressMessage.errorLockingCase=An error occurred exclusively locking the case for deletion (see log for details): {0}.", + "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", + "# {0} - exception message", "Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}.", + "Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data.", + "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDb=An occurred deleting the case database (see log for details): {0}.", + "# {0} - exception message", "Case.progressMessage.errorSavingDeletedItemsFlags=An occurred saving the deleted items flags in the coordination service database (see log for details): {0}." + }) + private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + CaseNodeData caseNodeData; + boolean errorsOccurred = false; + progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc()); + CoordinationService coordinationService; + try { + coordinationService = CoordinationService.getInstance(); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorConnectingToCoordSvc(ex.getMessage())); + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + + progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser()); + try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseName())) { + + if (nameLock == null) { + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because the case name was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); + progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); + throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); + } + + try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { + + if (dirLock == null) { + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because the case was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); + progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); + throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); + } + + progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData()); + try { + byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); + if (nodeBytes != null && nodeBytes.length > 0) { + caseNodeData = new CaseNodeData(nodeBytes); + } else { + logger.log(Level.SEVERE, String.format("Missing coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); + progressIndicator.progress(Bundle.Case_progressMessage_missingCoordSvcNodeData()); + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorGettingCoordSvcNodeData(ex.getMessage())); + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + + try { + deleteCaseDatabase(metadata, progressIndicator, caseNodeData); + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + errorsOccurred = true; + logger.log(Level.SEVERE, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDb(ex.getMessage())); + } + + try { + deleteTextIndex(metadata, progressIndicator, caseNodeData); + } catch (KeywordSearchServiceException ex) { + errorsOccurred = true; + logger.log(Level.SEVERE, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingTextIndex(ex.getMessage())); + } + + try { + deleteCaseDirectory(metadata, progressIndicator, caseNodeData); + } catch (CaseActionException ex) { + errorsOccurred = true; + logger.log(Level.SEVERE, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDir(ex.getMessage())); + } + + deleteFromRecentCases(metadata, progressIndicator); + + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorLockingCase(ex.getMessage())); + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case name for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorLockingCaseName(ex.getMessage())); + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + + deleteCaseResourcesCoordinationServiceNode(metadata, progressIndicator, coordinationService); + deleteCaseAutoIngestLogCoordinationServiceNode(metadata, progressIndicator, coordinationService); + + if (!errorsOccurred) { + deleteCaseDirectoryCoordinationServiceNode(metadata, progressIndicator, coordinationService); + } else { + /* + * Save the deleted item flags for the partially deleted case so + * that system adminstrators can try to fix whatever wnet wrong and + * then retry deleting the case. + */ + try { + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), caseNodeData.toArray()); + } catch (IOException | CoordinationServiceException | InterruptedException ex) { + logger.log(Level.SEVERE, String.format("Failed to write the node dat with the deleted items flags for the partially deleted case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorSavingDeletedItemsFlags(ex.getMessage())); + } + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + } + + /** + * Attempts to delete the case database for a multi-user case. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * @param caseNodeData The coordination service node data for the case. + * + * @throws UserPreferencesException If there is an error getting the + * database server connection info. + * @throws ClassNotFoundException If there is an error gettting the + * required JDBC driver. + * @throws SQLException If there is an error executing the SQL + * to drop the database from the database + * server. + */ + @Messages({ + "Case.progressMessage.deletingCaseDatabase=Deleting case database...",}) + private static void deleteCaseDatabase(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws UserPreferencesException, ClassNotFoundException, SQLException { + if (caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { + return; + } + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); + CaseDbConnectionInfo db; + 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); + } + caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.CASE_DB); + } + + /** + * Attempts to delete the text index for a case. + * + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * + * @throws KeywordSearchServiceException If there is an error deleting the + * text index. + */ + @Messages({ + "Case.progressMessage.deletingTextIndex=Deleting text index..." + }) + private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException { progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex()); for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) { - try { - stopWatch.reset(); - stopWatch.start(); - searchService.deleteTextIndex(metadata); - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to delete text index for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - } catch (KeywordSearchServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - errorsOccurred = true; - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to fail to delete text index for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - } + searchService.deleteTextIndex(metadata); } + } - /* - * Delete the case directory. - */ + /** + * Attempts to delete the text index for a multi-user case. + * + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * @param caseNodeData The coordination service node data for the case + * if it is a multi-user case; for a single-user + * case pass null. + * + * @throws KeywordSearchServiceException If there is an error deleting the + * text index. + */ + private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws KeywordSearchServiceException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) { + deleteTextIndex(metadata, progressIndicator); + caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.TEXT_INDEX); + } + } + + /** + * Attempts to delete the case directory for a case. + * + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is an error deleting the case + * directory. + */ + @Messages({ + "Case.progressMessage.deletingCaseDirectory=Deleting case directory..." + }) + private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); - stopWatch.reset(); - stopWatch.start(); + // RJCTODO: Use robocopy on Windows, possibly just for longer paths if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { - logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - errorsOccurred = true; - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to fail to delete case directory for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - } else { - stopWatch.stop(); - logger.log(Level.INFO, String.format("Used %d s to delete case directory for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); + throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); } + } - /* - * If running in a GUI, remove the case from the Recent Cases menu - */ + /** + * Attempts to delete the case directory for a case. + * + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * @param caseNodeData The coordination service node data for the case. + * + * @throws CaseActionException If there is an error deleting the case + * directory. + */ + private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws CaseActionException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) { + deleteCaseDirectory(metadata, progressIndicator); + caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.CASE_DIR); + } + } + + /** + * Attempts to remove a case from the recent cases menu if the main + * application window is present. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..." + }) + private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) { if (RuntimeProperties.runningWithGUI()) { + progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases()); SwingUtilities.invokeLater(() -> { RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString()); }); } + } - if (errorsOccurred) { - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + /** + * Deletes the case resources coordination nodes for a multi-user case. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...", + "# {0} - exception message", "Case.progressMessage.errorDeletingResourcesLockNode=An occurred deleting the case resources lock node (see log for details): {0}." + }) + static void deleteCaseResourcesCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { + progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode()); + String resourcesLockNodePath = metadata.getCaseDirectory() + RESOURCES_LOCK_SUFFIX; + try { + coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingResourcesLockNode(ex.getMessage())); + } + } + + /** + * Deletes the case auto ingest log lock coordination service node for a + * multi-user case, if it exists. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log coordination service node...", + "# {0} - exception message", "Case.progressMessage.errorDeletingJobLogLockNode=An occurred deleting the case auto ingest log lock node (see log for details): {0}." + }) + static void deleteCaseAutoIngestLogCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { + progressIndicator.progress(Bundle.Case_progressMessage_deletingAutoIngestLogCoordSvcNode()); + Path logFilePath = Paths.get(metadata.getCaseDirectory(), AUTO_INGEST_LOG_FILE_NAME); + try { + /* + * Does nothing if the node does not exist. + */ + coordinationService.deleteNode(CategoryNode.CASES, logFilePath.toString()); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error deleting the case auto ingest log lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingJobLogLockNode(ex.getMessage())); + } + } + + /** + * Deletes the case (directory lock) coordination service node for a + * multi-user case. + * + * @param metadata The metadata for the case to delete. + * @param progressIndicator The progress indicator for the deletion + * operation. + */ + @Messages({ + "Case.progressMessage.deletingDirectroyCoordinationServiceNode=Deleting case ersource coordination service node...", + "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDirLockNode=An occurred deleting the case directory lock node (see log for details): {0}." + }) + static void deleteCaseDirectoryCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { + String caseDirectoryLockNodePath = metadata.getCaseDirectory(); + try { + coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error deleting the case directory lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDirLockNode(ex.getMessage())); } } @@ -1101,7 +1289,7 @@ public class Case { }) private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException { try { - String resourcesNodeName = caseDir + "_resources"; + String resourcesNodeName = caseDir + RESOURCES_LOCK_SUFFIX; Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); return lock; } catch (InterruptedException ex) { @@ -1133,11 +1321,14 @@ public class Case { */ SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase(); String backupDbPath = caseDb.getBackupDatabasePath(); + if (null != backupDbPath) { JOptionPane.showMessageDialog( mainFrame, - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), + NbBundle.getMessage(Case.class, + "Case.open.msgDlg.updated.msg", backupDbPath), + NbBundle.getMessage(Case.class, + "Case.open.msgDlg.updated.title"), JOptionPane.INFORMATION_MESSAGE); } @@ -1151,11 +1342,14 @@ public class Case { long obj_id = entry.getKey(); String path = entry.getValue(); boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); + if (!fileExists) { int response = JOptionPane.showConfirmDialog( mainFrame, - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), + NbBundle.getMessage(Case.class, + "Case.checkImgExist.confDlg.doesntExist.msg", path), + NbBundle.getMessage(Case.class, + "Case.checkImgExist.confDlg.doesntExist.title"), JOptionPane.YES_NO_OPTION); if (response == JOptionPane.YES_OPTION) { MissingImageDialog.makeDialog(obj_id, caseDb); @@ -1169,15 +1363,32 @@ public class Case { /* * Enable the case-specific actions. */ - CallableSystemAction.get(AddImageAction.class).setEnabled(true); - CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); - CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true); - CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true); - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); - CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); - CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true); - CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true); - CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); + CallableSystemAction.get(AddImageAction.class + ).setEnabled(true); + CallableSystemAction + .get(CaseCloseAction.class + ).setEnabled(true); + CallableSystemAction + .get(CaseDetailsAction.class + ).setEnabled(true); + CallableSystemAction + .get(DataSourceSummaryAction.class + ).setEnabled(true); + CallableSystemAction + .get(CaseDeleteAction.class + ).setEnabled(true); + CallableSystemAction + .get(OpenTimelineAction.class + ).setEnabled(true); + CallableSystemAction + .get(OpenCommVisualizationToolAction.class + ).setEnabled(true); + CallableSystemAction + .get(CommonAttributeSearchAction.class + ).setEnabled(true); + CallableSystemAction + .get(OpenOutputFolderAction.class + ).setEnabled(false); /* * Add the case to the recent cases tracker that supplies a list @@ -1223,15 +1434,33 @@ public class Case { /* * Disable the case-specific menu items. */ - CallableSystemAction.get(AddImageAction.class).setEnabled(false); - CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); - CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false); - CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false); - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); - CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); - CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false); - CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); - CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false); + CallableSystemAction + .get(AddImageAction.class + ).setEnabled(false); + CallableSystemAction + .get(CaseCloseAction.class + ).setEnabled(false); + CallableSystemAction + .get(CaseDetailsAction.class + ).setEnabled(false); + CallableSystemAction + .get(DataSourceSummaryAction.class + ).setEnabled(false); + CallableSystemAction + .get(CaseDeleteAction.class + ).setEnabled(false); + CallableSystemAction + .get(OpenTimelineAction.class + ).setEnabled(false); + CallableSystemAction + .get(OpenCommVisualizationToolAction.class + ).setEnabled(false); + CallableSystemAction + .get(OpenOutputFolderAction.class + ).setEnabled(false); + CallableSystemAction + .get(CommonAttributeSearchAction.class + ).setEnabled(false); /* * Clear the notifications in the notfier component in the lower @@ -2339,7 +2568,9 @@ public class Case { * possible to ensure that each service task completes before the next * one starts by awaiting termination of the executor service. */ - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { + + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class + )) { /* * Create a progress indicator for the task and start the task. If * running with a GUI, the progress indicator will be a dialog box @@ -2598,7 +2829,8 @@ public class Case { * Each service gets its own independently cancellable task, and thus * its own task progress indicator. */ - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class + )) { ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { progressIndicator = new ModalDialogProgressIndicator( @@ -3062,4 +3294,31 @@ public class Case { deleteReports(reports); } + /** + * Deletes a case. + * + * @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. + * @deprecated Use deleteCase(CaseMetadata, ProgressIndicator) instead. + * instead. + */ + @Deprecated + public static void deleteCase(CaseMetadata metadata) throws CaseActionException { + /* + * Set up either a GUI progress indicator without a cancel button (can't + * cancel deleting a case) or a logging progress indicator. + */ + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + deleteCase(metadata, progressIndicator); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 9b71d51b06..5ddb93c291 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -190,6 +190,10 @@ public final class CaseMetadata { readFromFile(); } + public CaseMetadata(String metadataFilePath) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + /** * Gets the full path to the case metadata file. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java index 3869e364d0..0840fa480c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java @@ -238,6 +238,27 @@ public final class CaseNodeData { this.displayName = displayName; } + /** + * Checks whether a deleted item flag is set for the case represented by + * this node data. + * + * @param flag The flag to check. + * + * @return + */ + public boolean isDeletedFlagSet(DeletedFlags flag) { + return (this.deletedItemFlags & flag.getValue()) == flag.getValue(); + } + + /** + * Sets a deleted item flag for the case represented by this node data. + * + * @param flag The flag to set. + */ + public void setDeletedFlag(DeletedFlags flag) { + this.deletedItemFlags |= flag.getValue(); + } + /** * Gets the node data as a byte array that can be sent to the coordination * service. @@ -262,6 +283,31 @@ public final class CaseNodeData { return byteStream.toByteArray(); } + public enum DeletedFlags { + + TEXT_INDEX(1), + CASE_DB(2), + CASE_DIR(4), + DATA_SOURCES(8), + JOB_MANIFEST_NODES(16); + + private final short value; + + private DeletedFlags(int value) { + this.value = (short) value; + } + + /** + * Gets the value of the flag. + * + * @return The value as a short. + */ + private short getValue() { + return value; + } + + } + public final static class InvalidDataException extends Exception { private static final long serialVersionUID = 1L; @@ -274,4 +320,5 @@ public final class CaseNodeData { super(message, cause); } } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED new file mode 100755 index 0000000000..7ca1d937a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED @@ -0,0 +1,5 @@ +MultiUserCaseBrowserCustomizer.column.createTime=Create Time +MultiUserCaseBrowserCustomizer.column.directory=Directory +MultiUserCaseBrowserCustomizer.column.displayName=Name +MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time +MultiUserCasesBrowserPanel.waitNode.message=Please Wait... diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form index 1e1cc73c43..db37699f2d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form @@ -62,7 +62,6 @@ - @@ -134,9 +133,7 @@ - - - + @@ -255,28 +252,6 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index db8e25a592..068a856a20 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -63,7 +63,6 @@ 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.experimental.autoingest.AutoIngestManager.CaseDeletionResult; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.JobsSnapshot; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer; @@ -621,7 +620,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { } int row = completedTable.getSelectedRow(); boolean enabled = row >= 0 && row < completedTable.getRowCount(); - bnDeleteCase.setEnabled(enabled); bnShowCaseLog.setEnabled(enabled); bnReprocessJob.setEnabled(enabled); }); @@ -632,7 +630,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { */ private void initButtons() { bnOptions.setEnabled(true); - bnDeleteCase.setEnabled(false); enablePrioritizeButtons(false); enableDeprioritizeButtons(false); bnShowCaseLog.setEnabled(false); @@ -1229,7 +1226,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { completedScrollPane = new javax.swing.JScrollPane(); completedTable = new javax.swing.JTable(); bnCancelJob = new javax.swing.JButton(); - bnDeleteCase = new javax.swing.JButton(); lbPending = new javax.swing.JLabel(); lbRunning = new javax.swing.JLabel(); lbCompleted = new javax.swing.JLabel(); @@ -1317,17 +1313,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { } }); - org.openide.awt.Mnemonics.setLocalizedText(bnDeleteCase, org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.bnDeleteCase.text")); // NOI18N - bnDeleteCase.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.bnDeleteCase.toolTipText")); // NOI18N - bnDeleteCase.setMaximumSize(new java.awt.Dimension(162, 23)); - bnDeleteCase.setMinimumSize(new java.awt.Dimension(162, 23)); - bnDeleteCase.setPreferredSize(new java.awt.Dimension(162, 23)); - bnDeleteCase.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - bnDeleteCaseActionPerformed(evt); - } - }); - lbPending.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(lbPending, org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.lbPending.text")); // NOI18N @@ -1551,7 +1536,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { .addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(bnDeleteCase, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(bnShowCaseLog, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(bnReprocessJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addGroup(layout.createSequentialGroup() @@ -1565,7 +1549,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) ); - layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnDeleteCase, bnShowProgress}); + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnShowProgress}); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnClusterMetrics, bnExit, bnOpenLogDir, bnOptions, bnPause, bnRefresh}); @@ -1611,9 +1595,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { .addGroup(layout.createSequentialGroup() .addGap(68, 68, 68) .addComponent(bnReprocessJob, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(bnDeleteCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGap(35, 35, 35) .addComponent(bnShowCaseLog, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -1631,7 +1613,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { .addContainerGap()) ); - layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnClusterMetrics, bnDeleteCase, bnExit, bnOpenLogDir, bnOptions, bnPrioritizeCase, bnPrioritizeJob, bnRefresh, bnShowProgress}); + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnClusterMetrics, bnExit, bnOpenLogDir, bnOptions, bnPrioritizeCase, bnPrioritizeJob, bnRefresh, bnShowProgress}); }// //GEN-END:initComponents @@ -1649,60 +1631,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_bnRefreshActionPerformed - /** - * Handles a click on the delete case button. If an entry is selected that - * can be deleted, pops up a confirmation dialog. Upon confirmation, asks - * AutoIngestManager to delete the entry and asks for an updated view. - * - * @param evt The button click event. - */ - @Messages({ - "AutoIngestControlPanel.DeletionFailed=Deletion failed for job" - }) - private void bnDeleteCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDeleteCaseActionPerformed - if (completedTable.getModel().getRowCount() < 0 || completedTable.getSelectedRow() < 0) { - return; - } - - String caseName = (String) completedTable.getModel().getValueAt(completedTable.convertRowIndexToModel(completedTable.getSelectedRow()), JobsTableModelColumns.CASE.ordinal()); - Object[] options = { - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.Delete"), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DoNotDelete") - }; - Object[] msgContent = {org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DeleteAreYouSure") + "\"" + caseName + "\"?"}; - int reply = JOptionPane.showOptionDialog(this, - msgContent, - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmDeletionHeader"), - JOptionPane.DEFAULT_OPTION, - JOptionPane.WARNING_MESSAGE, - null, - options, - options[JOptionPane.NO_OPTION]); - if (reply == JOptionPane.YES_OPTION) { - bnDeleteCase.setEnabled(false); - bnShowCaseLog.setEnabled(false); - if (completedTable.getModel().getRowCount() > 0 && completedTable.getSelectedRow() >= 0) { - Path caseDirectoryPath = (Path) completedTable.getModel().getValueAt(completedTable.convertRowIndexToModel(completedTable.getSelectedRow()), JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal()); - completedTable.clearSelection(); - this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - CaseDeletionResult result = manager.deleteCase(caseName, caseDirectoryPath); - refreshTables(); - this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - if (CaseDeletionResult.FAILED == result) { - JOptionPane.showMessageDialog(this, - String.format("Could not delete case %s. It may be in use.", caseName), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), - JOptionPane.INFORMATION_MESSAGE); - } else if (CaseDeletionResult.PARTIALLY_DELETED == result) { - JOptionPane.showMessageDialog(this, - String.format("Could not fully delete case %s. See system log for details.", caseName), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), - JOptionPane.INFORMATION_MESSAGE); - } - } - } - }//GEN-LAST:event_bnDeleteCaseActionPerformed - /** * Handles a click on the cancel auto ingest job button. Cancels the * selected job. @@ -1976,7 +1904,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { private javax.swing.JButton bnCancelJob; private javax.swing.JButton bnCancelModule; private javax.swing.JButton bnClusterMetrics; - private javax.swing.JButton bnDeleteCase; private javax.swing.JButton bnDeprioritizeCase; private javax.swing.JButton bnDeprioritizeJob; private javax.swing.JButton bnExit; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 4083d448f7..c993daa34e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -651,7 +651,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn PENDING, PROCESSING, COMPLETED, - DELETED + DELETED // No longer used, retained for legacy jobs only. } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java index dfff014556..6523c983f6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java @@ -47,9 +47,9 @@ import org.sleuthkit.autopsy.coreutils.NetworkUtils; * of the error. */ @Immutable -final class AutoIngestJobLogger { +final class AutoIngestJobLogger { private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; + - private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; private static final int LOCK_TIME_OUT = 15; private static final TimeUnit LOCK_TIME_OUT_UNIT = TimeUnit.MINUTES; private static final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss"; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 55332cc03c..41e8218aa7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -924,123 +924,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - /** - * Deletes a case. This includes deleting the case directory, the text - * index, and the case database. This does not include the directories - * containing the data sources and their manifests. - * - * @param caseName The name of the case. - * @param caseDirectoryPath The path to the case directory. - * - * @return A result code indicating success, partial success, or failure. - */ - CaseDeletionResult deleteCase(String caseName, Path caseDirectoryPath) { - if (state != State.RUNNING) { - return CaseDeletionResult.FAILED; - } - - CaseDeletionResult result = CaseDeletionResult.FULLY_DELETED; - List manifestFileLocks = new ArrayList<>(); - 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) { - sysLogger.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. - */ - InputDirScanner scanner = new InputDirScanner(); - scanner.scan(); - Set manifestPaths = casesToManifests.get(caseName); - if (null == manifestPaths) { - sysLogger.log(Level.SEVERE, String.format("No manifest paths found for case %s at %s", caseName, caseDirectoryPath)); - return CaseDeletionResult.FAILED; - } - - /* - * 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 { - Lock lock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString()); - if (null != lock) { - manifestFileLocks.add(lock); - } else { - return CaseDeletionResult.FAILED; - } - } catch (CoordinationServiceException ex) { - sysLogger.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; - } - } - - try { - /* - * Physically delete the case. - */ - Case.deleteCase(metaData); - } catch (CaseActionException ex) { - sysLogger.log(Level.SEVERE, String.format("Failed to physically delete case %s at %s", caseName, caseDirectoryPath), ex); - return CaseDeletionResult.FAILED; - } - - /* - * Mark each job (manifest file) as deleted - */ - for (Path manifestPath : manifestPaths) { - try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - AutoIngestJob deletedJob = new AutoIngestJob(nodeData); - deletedJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.DELETED); - this.updateCoordinationServiceManifestNode(deletedJob); - } catch (AutoIngestJobNodeData.InvalidDataException | AutoIngestJobException ex) { - sysLogger.log(Level.WARNING, String.format("Invalid auto ingest job node data for %s", manifestPath), ex); - return CaseDeletionResult.PARTIALLY_DELETED; - } catch (InterruptedException | CoordinationServiceException ex) { - sysLogger.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; - } - } - - /* - * Remove the jobs for the case from the pending jobs queue and - * completed jobs list. - */ - removeJobs(manifestPaths, pendingJobs); - removeJobs(manifestPaths, completedJobs); - casesToManifests.remove(caseName); - } - - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, getSystemUserNameProperty())); - setChanged(); - notifyObservers(Event.CASE_DELETED); - return result; - - } finally { - /* - * Always release the manifest locks, regardless of the outcome. - */ - for (Lock lock : manifestFileLocks) { - try { - lock.release(); - } catch (CoordinationServiceException ex) { - sysLogger.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. * @@ -1339,7 +1222,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen case COMPLETED: addCompletedJob(manifest, nodeData); break; - case DELETED: + case DELETED: // No longer used, retained for legacy jobs only. /* * Ignore jobs marked as "deleted." */ @@ -2411,7 +2294,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen /* * Acquire and hold a case name lock so that only one node at as * time can scan the output directory at a time. This prevents - * making duplicate cases for the saem auto ingest case. + * making duplicate cases for the same auto ingest case. */ try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, 30, TimeUnit.MINUTES)) { if (null != caseLock) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java index 7b07a15aec..36a376e3a0 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java @@ -72,7 +72,7 @@ final class AutoIngestMetricsCollector { switch (processingStatus) { case PENDING: case PROCESSING: - case DELETED: + case DELETED: // No longer used, retained for legacy jobs only. /* * These are not jobs we care about for metrics, so * we will ignore them. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 8f7a9c0696..16fc961c4b 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -360,7 +360,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen case COMPLETED: newJobsSnapshot.addOrReplaceCompletedJob(job); break; - case DELETED: + case DELETED: // No longer used, retained for legacy jobs only. break; default: LOGGER.log(Level.SEVERE, "Unknown AutoIngestJobData.ProcessingStatus"); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index 86eb72c325..42e2ecac4e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -236,7 +236,6 @@ AutoIngestControlPanel.bnShowProgress.text=Ingest Progress AutoIngestControlPanel.bnCancelJob.text=&Cancel Job AutoIngestControlPanel.bnCancelModule.text=Cancel &Module AutoIngestControlPanel.bnReprocessJob.text=Reprocess Job -AutoIngestControlPanel.bnDeleteCase.text=&Delete Case AutoIngestControlPanel.bnShowCaseLog.text=Show Case &Log AutoIngestControlPanel.bnPause.text=Pause AutoIngestControlPanel.bnRefresh.text=&Refresh diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index d2d665c8d4..9f9e5dbf7c 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -59,7 +59,6 @@ AutoIngestControlPanel.Cancelling=Cancelling... AutoIngestControlPanel.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already AutoIngestControlPanel.ConfigLocked=The shared configuration directory is locked because upload from another node is in progress. \nIf this is an error, you can unlock the directory and then retry the upload. AutoIngestControlPanel.ConfigLockedTitle=Configuration directory locked -AutoIngestControlPanel.DeletionFailed=Deletion failed for job AutoIngestControlPanel.EnableConfigurationSettings=Enable shared configuration from the options panel before uploading AutoIngestControlPanel.errorMessage.caseDeprioritization=An error occurred when deprioritizing the case. Some or all jobs may not have been deprioritized. AutoIngestControlPanel.errorMessage.casePrioritization=An error occurred when prioritizing the case. Some or all jobs may not have been prioritized. @@ -119,7 +118,7 @@ AutoIngestDashboard.JobsTableModel.ColumnHeader.StartedTime=Stage Started AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Job Completed AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage=Stage AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status -AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path +AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath=Manifest File Path AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime=Time in Stage AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case AutoIngestDashboard.JobsTableModel.ColumnHeader.Job=Job @@ -130,6 +129,7 @@ AutoIngestDashboard.tbServicesStatusMessage.Message.Up=up AutoIngestDashboard.tbServicesStatusMessage.Message.Down=down AutoIngestDashboard.tbServicesStatusMessage.Message.Unknown=unknown AutoIngestDashboardTopComponent.exceptionMessage.failedToCreateDashboard=Failed to create Auto Ingest Dashboard. +AutoIngestJobsDeletionTask.error.failedToGetJobNodes=Failed to get auto ingest job node data from coordination service. AutoIngestJobsNode.caseName.text=Case Name AutoIngestJobsNode.dataSource.text=Data Source AutoIngestJobsNode.hostName.text=Host Name @@ -144,6 +144,21 @@ AutoIngestJobsNode.status.text=Status AutoIngestJobsPanel.waitNode.text=Please Wait... AutoIngestMetricsDialog.initReportText=Select a date above and click the 'Generate Metrics Report' button to generate\na metrics report. AutoIngestMetricsDialog.title.text=Auto Ingest Metrics +CaseDeletionTask.error.failedToExclusivelyLockCase=Failed to exclusively lock the case, it may be in use. +CaseDeletionTask.error.failedToLockCase=A coordination service error occurred while trying to lock the case for deletion: {0}. +CaseDeletionTask.progress.connectingToCoordSvc=Connecting to coordination service (ZooKeeper)... +# {0} - case name +CaseDeletionTask.progress.deletingCase=Deleting {0}... +# {0} - exception message +CaseDeletionTask.progress.errorConnectingToCoordSvc=Failed to connect to coordination service (see log for details): {0} +# {0} - exception message +CaseDeletionTask.progress.errorDeletingOutput=The following error occurred deleting the case output (see log for details): {0} +# {0} - exception message +CaseDeletionTask.progress.errorLockingCase=A coordination service error occurred while trying to lock the case (see log for details): {0}. +CaseDeletionTask.progress.failedToLockCase=Failed to exclusively lock the case, it may be in use, did not delete. +CaseDeletionTask.progress.gettingJobNodeData=Getting coordination service node data for the auto ingest jobs... +CaseDeletionTask.progress.lockingCase=Acquiring exclusive lock for case... +CaseDeletionTask.progress.lockingJobs=Acquiring exclusive locks on all of the auto ingest job directories... ConfirmationDialog.DoNotDelete=Do not delete ConfirmationDialog.Delete=Permanently delete ConfirmationDialog.DeleteAreYouSure=The entire case will be removed. Are you sure you want to delete case @@ -168,14 +183,22 @@ CTL_AinStatusDashboardTopComponent=Auto Ingest Nodes CTL_AutoIngestDashboardAction=Auto Ingest Jobs CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs +CTL_CasesDashboardAction=Multi-User Cases Dashboard +CTL_CasesDashboardTopComponent=Cases DataSourceOnCDriveError.noOpenCase.errMsg=Warning: Exception while getting open case. DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on "C:" drive +DeleteCaseInputDirectoriesAction.menuItemText=Delete Input Directories +DeleteCasesAction.menuItemText=Delete Case and Jobs +DeleteCasesForReprocessingAction.menuItemText=Delete for Reprocessing GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz) -OpenIDE-Module-Long-Description=\ - This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. \ - You can enable this module to use the new features. \ - The features should be stable, but their exact behavior and API are subject to change. \n\n\ - We make no guarantee that the API of this module will not change, so developers should be careful when relying on it. +HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases +OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. +OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. +OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File +# {0} - caseErrorMessage +OpenCaseAction.errorMsg=Failed to open case: {0} +OpenCaseAction.menuItemText=Open +OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change. \n\nWe 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. DisplayLogDialog.cannotOpenLog=Unable to open the selected case log file @@ -284,6 +307,7 @@ PrioritizationAction.prioritizeJobAction.error=Failed to prioritize job "%s". PrioritizationAction.prioritizeJobAction.title=Prioritize Job PrioritizedIconCellRenderer.notPrioritized.tooltiptext=This job has not been prioritized. PrioritizedIconCellRenderer.prioritized.tooltiptext=This job has been prioritized. The most recently prioritized job should be processed next. +ShowCaseDeletionStatusAction.menuItemText=Show Deletion Status SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping SingleUserCaseImporter.WillImport=Will import: SingleUserCaseImporter.None=None @@ -381,7 +405,6 @@ AutoIngestControlPanel.bnShowProgress.text=Ingest Progress AutoIngestControlPanel.bnCancelJob.text=&Cancel Job AutoIngestControlPanel.bnCancelModule.text=Cancel &Module AutoIngestControlPanel.bnReprocessJob.text=Reprocess Job -AutoIngestControlPanel.bnDeleteCase.text=&Delete Case AutoIngestControlPanel.bnShowCaseLog.text=Show Case &Log AutoIngestControlPanel.bnPause.text=Pause AutoIngestControlPanel.bnRefresh.text=&Refresh @@ -407,3 +430,4 @@ AinStatusDashboard.refreshButton.text=&Refresh AinStatusDashboard.clusterMetricsButton.text=Auto Ingest &Metrics AinStatusDashboard.nodeStatusTableTitle.text=Auto Ingest Nodes AinStatusDashboard.healthMonitorButton.text=Health Monitor +CasesDashboardTopComponent.refreshButton.text=Refresh diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java new file mode 100755 index 0000000000..eb970d30f9 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java @@ -0,0 +1,275 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.progress.ProgressIndicator; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException; + +/** + * A task that deletes one or more cases, entirely or in part, depending on the + * use case. + */ +final class CaseDeletionTask implements Runnable { + + private static final Logger logger = Logger.getLogger(CaseDeletionTask.class.getName()); + private final List caseNodeDataList; + private final CaseDeletionTaskType deletionTaskType; + private final ProgressIndicator progress; + private CoordinationService coordinationService; + + /** + * An enum specifying the work a case deletion task is to do, depending on + * the use case. + */ + enum CaseDeletionTaskType { + + /** + * To delete the auto ingest job input directories only, while leaving + * behind the auto ingest job coordination service nodes and leaving the + * cases otherwise intact, specify this deletion type. The use case is + * freeing space while retaining the option to restore the input + * directories, effectively restoring the cases. + */ + INPUT_ONLY, + /** + * To delete the auto ingest job coordination service nodes and the + * cases, while leaving behind the auto ingest job input directories, + * specify this deletion type. The use case is when it is desirable to + * reprocess the cases with a clean slate without having to restore the + * input directories. + */ + OUTPUT_ONLY, + /** + * To delete the auto ingest job input directories and coordination + * service nodes and all of the output for the cases, specify this + * deletion type. + */ + FULL; + + } + + /** + * Constructs a task that deletes one or more cases, entirely or in part, + * depending on the use case. + * + * @param caseNodeDataList The coordination service node data for the cases + * to delete. + * @param deletionTaskType The extent of the deletion to attempt for each + * case. + * @param progress A progress indicator. + */ + CaseDeletionTask(List caseNodeDataList, CaseDeletionTaskType deletionTaskType, ProgressIndicator progress) { + this.caseNodeDataList = caseNodeDataList; + this.deletionTaskType = deletionTaskType; + this.progress = progress; + } + + @Override + @NbBundle.Messages({ + "CaseDeletionTask.progress.connectingToCoordSvc=Connecting to coordination service (ZooKeeper)...", + "# {0} - exception message", "CaseDeletionTask.progress.errorConnectingToCoordSvc=Failed to connect to coordination service (see log for details): {0}", + "# {0} - case name", "CaseDeletionTask.progress.deletingCase=Deleting {0}...", + "CaseDeletionTask.progress.lockingCase=Acquiring exclusive lock for case...", + "# {0} - exception message", "CaseDeletionTask.progress.errorLockingCase=A coordination service error occurred while trying to lock the case (see log for details): {0}.", + "CaseDeletionTask.progress.failedToLockCase=Failed to exclusively lock the case, it may be in use, did not delete.", + "CaseDeletionTask.progress.gettingJobNodeData=Getting coordination service node data for the auto ingest jobs...", + "CaseDeletionTask.progress.lockingJobs=Acquiring exclusive locks on all of the auto ingest job directories...", + "# {0} - exception message", "CaseDeletionTask.progress.errorDeletingOutput=The following error occurred deleting the case output (see log for details): {0}",}) + public void run() { + progress.start(Bundle.CaseDeletionTask_progress_connectingToCoordSvc()); + try { + /* + * Connect to the coordination service. + */ + try { + coordinationService = CoordinationService.getInstance(); + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.SEVERE, "Failed to connect to the coordination service", ex); + progress.progress(Bundle.CaseDeletionTask_progress_errorConnectingToCoordSvc(ex.getMessage())); + return; + } + + // RJCTODO: Get the auto ingest job nodes and bucket them by case + + for (CaseNodeData caseNodeData : caseNodeDataList) { + progress.progress(Bundle.CaseDeletionTask_progress_deletingCase(caseNodeData.getDisplayName())); + progress.progress(Bundle.CaseDeletionTask_progress_lockingCase()); + + /* + * Get an exclusive lock on the case. + * + * RJCTODO: Should the case name lock also be obtained and + * deleted? + */ + try (CoordinationService.Lock caseLock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseNodeData.getDirectory().toString())) { + if (caseLock == null) { + logger.log(Level.WARNING, String.format("Failed to get exclusive lock on %s, skipping", caseNodeData.getDisplayName())); + progress.progress(Bundle.CaseDeletionTask_progress_failedToLockCase()); + continue; + } + + /* + * Get all of the auto ingest job coordination service nodes for the case. + */ + progress.progress(Bundle.CaseDeletionTask_progress_gettingJobNodeData()); + List jobNodeDataList = getJobNodeData(caseNodeData.getName()); + + /* + * Lock all of the auto ingest job directories for the case. + */ + progress.progress(Bundle.CaseDeletionTask_progress_lockingJobs()); + Map jobLocks = getJobLocks(jobNodeDataList); + + for (AutoIngestJobNodeData jobNodeData : jobNodeDataList) { + CoordinationService.Lock jobLock = jobLocks.remove(jobNodeData.getManifestFilePath()); + + if (deletionTaskType == CaseDeletionTaskType.INPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { + deleteAutoIngestNodeDirectory(jobNodeData); + } + + jobLock.release(); + + if (deletionTaskType == CaseDeletionTaskType.OUTPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { + coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, jobNodeData.getManifestFilePath().toString()); + } + + } + + if (deletionTaskType == CaseDeletionTaskType.OUTPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { + // RJCTODO: Progress message + String metadataFilePath = null; + File caseDirectory = caseNodeData.getDirectory().toFile(); + File[] filesInDirectory = caseDirectory.listFiles(); + if (filesInDirectory != null) { + for (File file : filesInDirectory) { + if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { + metadataFilePath = file.getPath(); + } + } + } + + if (metadataFilePath == null) { + continue; // RJCTODO: Or blow away the directory? + } + + try { + Case.deleteCase(new CaseMetadata(Paths.get(metadataFilePath)), progress); + } catch (CaseMetadata.CaseMetadataException | CaseActionException ex) { + // RJCTODO: + } + + } + + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error attempting to acquire exclusive case directory lock for %s", caseNodeData.getName()), ex); + progress.progress(Bundle.CaseDeletionTask_progress_errorLockingCase(ex.getMessage())); + } + } + + } finally { + progress.finish(); + } + } + + /** + * RJCTODO + * + * @param caseName + * + * @return + * + * @throws CoordinationServiceException If there is a + */ + @NbBundle.Messages({ + "AutoIngestJobsDeletionTask.error.failedToGetJobNodes=Failed to get auto ingest job node data from coordination service." + }) + private List getJobNodeData(String caseName) throws CoordinationServiceException { + final List nodes; + nodes = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); + final List jobNodeData = new ArrayList<>(); + for (String nodeName : nodes) { + try { + byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); + if (nodeBytes == null || nodeBytes.length <= 0) { + // Empty node data, indicate + // RJCTODO: Delete empty node, indicate success or failure + //coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, nodeName); + continue; + } + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes); + if (caseName.equals(nodeData.getCaseName())) { + jobNodeData.add(nodeData); + } + } catch (CoordinationService.CoordinationServiceException | InterruptedException | InvalidDataException ex) { + // RJCTODO: Failed to get node data for a node, indicate + logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex); + } + } + return jobNodeData; + } + + /** + * RJCTODO + * @param jobNodeData + * @return + */ + private Map getJobLocks(List jobNodeData) { + Map jobLocks = new HashMap<>(); + for (AutoIngestJobNodeData nodeData : jobNodeData) { + try { + CoordinationService.Lock lock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, nodeData.getManifestFilePath().toString()); + if (null != lock) { + jobLocks.put(nodeData.getManifestFilePath(), lock); + } else { + // RJCTODO: release the locks already obtained and + // throw an exception indicating all locks cannot be obtained. + } + } catch (CoordinationService.CoordinationServiceException ex) { + // RJCTODO: Release the locks already obtained and rethrow + } + } + return jobLocks; + } + + private void deleteAutoIngestNodeDirectory(AutoIngestJobNodeData nodeData) { + // RJCTODO: Need to strip of file name + if (FileUtil.deleteDir(new File(nodeData.getManifestFilePath().toString()))) { + // RJCTODO: + } + } + +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index e384766d55..52513be1e0 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -34,11 +34,7 @@ KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get sc KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found. KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=\ - Keyword Search ingest module.\n\n\ - The module indexes files found in the disk image at ingest time. \n\ - It then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\ - The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time. \nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search @@ -103,7 +99,7 @@ KeywordSearchConfigurationPanel.customizeComponents.genTabToolTip=General config KeywordSearchConfigurationPanel1.customizeComponents.title=Delete a Keyword List KeywordSearchConfigurationPanel1.customizeComponents.body=This will delete the keyword list globally (for all Cases). Do you want to proceed with the deletion? KeywordSearchConfigurationPanel1.customizeComponents.keywordListEmptyErr=Keyword List is empty and cannot be saved -KeywordSearch.newKwListTitle=New keyword list name\: +KeywordSearch.newKwListTitle=New keyword list name: KeywordSearch.openCore.notification.msg=Could not open keyword search index KeywordSearch.closeCore.notification.msg=Error closing keyword search index KeywordSearchConfigurationPanel1.customizeComponents.noOwDefaultMsg=Cannot overwrite default list @@ -124,7 +120,7 @@ KeywordSearchEditListPanel.exportButtonActionPerformed.fileFilterLabel=Keyword L KeywordSearchEditListPanel.exportButtonActionPerformed.fileExistPrompt=File {0} exists, overwrite? KeywordSearchEditListPanel.exportButtonActionPerformed.kwListExportedMsg=Keyword lists exported KeywordSearchEditListPanel.kwColName=Keyword -KeywordSearchEditListPanel.addKeyword.message=Add a new word to the keyword search list\: +KeywordSearchEditListPanel.addKeyword.message=Add a new word to the keyword search list: KeywordSearchEditListPanel.addKeyword.title=New Keyword KeywordSearchFilterNode.getFileActions.openExternViewActLbl=Open in External Viewer KeywordSearchFilterNode.getFileActions.searchSameMd5=Search for files with the same MD5 hash @@ -132,11 +128,11 @@ KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl=View in New Window KeywordSearchIngestModule.init.noKwInLstMsg=No keywords in keyword list. KeywordSearchIngestModule.init.onlyIdxKwSkipMsg=Only indexing will be done and keyword search will be skipped (you can still add keyword lists using the Keyword Lists - Add to Ingest). KeywordSearchIngestModule.doInBackGround.displayName=Periodic Keyword Search -KeywordSearchIngestModule.doInBackGround.finalizeMsg= - Finalizing -KeywordSearchIngestModule.doInBackGround.pendingMsg= (Pending) +KeywordSearchIngestModule.doInBackGround.finalizeMsg=- Finalizing +KeywordSearchIngestModule.doInBackGround.pendingMsg=(Pending) RawText.FileText=File Text RawText.ResultText=Result Text -SearchRunner.doInBackGround.cancelMsg= (Cancelling...) +SearchRunner.doInBackGround.cancelMsg=(Cancelling...) KeywordSearchIngestModule.postIndexSummary.knowFileHeaderLbl=Files with known types KeywordSearchIngestModule.postIndexSummary.fileGenStringsHead=Files with general strings extracted KeywordSearchIngestModule.postIndexSummary.mdOnlyLbl=Metadata only was indexed @@ -152,8 +148,8 @@ KeywordSearchListsViewerPanel.initIngest.addIngestTitle=Add to Ingest KeywordSearchListsViewerPanel.initIngest.addIngestMsg=You can select additional keyword lists
and enqueue them to the ongoing ingest.
The selected lists will be searched next time the file index is rebuilt. KeywordSearchListsViewerPanel.initIngest.searchIngestTitle=Search KeywordSearchListsViewerPanel.initIngest.addIdxSearchMsg=Search indexed files for keywords in selected lists -KeywordSearchListsViewerPanel.initIngest.ongoingIngestMsg=Files Indexed\: {0} (ingest is ongoing) -KeywordSearchListsViewerPanel.initIngest.fileIndexCtMsg=Files Indexed\: {0} +KeywordSearchListsViewerPanel.initIngest.ongoingIngestMsg=Files Indexed: {0} (ingest is ongoing) +KeywordSearchListsViewerPanel.initIngest.fileIndexCtMsg=Files Indexed: {0} KeywordSearch.selectedColLbl=Selected KeywordSearch.nameColLbl=Name KeywordSearch.typeColLbl=Keyword Type @@ -182,8 +178,8 @@ DropdownSearchPanel.selectAllMenuItem.text=Select All DropdownSearchPanel.pasteMenuItem.text=Paste DropdownSearchPanel.copyMenuItem.text=Copy AbstractFileStringContentStream.getSize.exception.msg=Cannot tell how many chars in converted string, until entire string is converted -AbstractFileStringContentStream.getSrcInfo.text=File\:{0} -ByteContentStream.getSrcInfo.text=File\:{0} +AbstractFileStringContentStream.getSrcInfo.text=File:{0} +ByteContentStream.getSrcInfo.text=File:{0} ExtractedContentViewer.nextPage.exception.msg=No next page. ExtractedContentViewer.previousPage.exception.msg=No previous page. ExtractedContentViewer.hasNextItem.exception.msg=Not supported, not a searchable source. @@ -191,31 +187,31 @@ ExtractedContentViewer.hasPreviousItem.exception.msg=Not supported, not a search ExtractedContentViewer.nextItem.exception.msg=Not supported, not a searchable source. ExtractedContentViewer.previousItem.exception.msg=Not supported, not a searchable source. ExtractedContentViewer.currentItem.exception.msg=Not supported, not a searchable source. -Ingester.ingest.exception.unknownImgId.msg=Skipping indexing the file, unknown image id, for file\: {0} -Ingester.ingest.exception.cantReadStream.msg=Could not read content stream\: {0} -Ingester.ingest.exception.err.msg=Error ingesting document\: {0} -Ingester.ingestExtract.exception.solrTimeout.msg=Solr index request time out for id\: {0}, name\: {1} -Ingester.ingestExtract.exception.probPostToSolr.msg=Problem posting content to Solr, id\: {0}, name\: {1} +Ingester.ingest.exception.unknownImgId.msg=Skipping indexing the file, unknown image id, for file: {0} +Ingester.ingest.exception.cantReadStream.msg=Could not read content stream: {0} +Ingester.ingest.exception.err.msg=Error ingesting document: {0} +Ingester.ingestExtract.exception.solrTimeout.msg=Solr index request time out for id: {0}, name: {1} +Ingester.ingestExtract.exception.probPostToSolr.msg=Problem posting content to Solr, id: {0}, name: {1} Ingester.UpReqestTask.run.exception.sorlNotAvail.msg=No Solr core available, cannot index the content Ingester.UpRequestTask.run.exception.probReadFile.msg=Problem reading file. Ingester.UpRequestTask.run.exception.solrProb.msg=Problem with Solr -Ingester.UpRequestTask.run.exception.probPostToSolr.msg=Problem posting file contents to Solr. SolrException error code\: {0} -Ingester.FscContentStream.getSrcInfo=File\:{0} +Ingester.UpRequestTask.run.exception.probPostToSolr.msg=Problem posting file contents to Solr. SolrException error code: {0} +Ingester.FscContentStream.getSrcInfo=File:{0} Ingester.FscContentStream.getReader=Not supported yet. -Ingester.NullContentStream.getSrcInfo.text=File\:{0} +Ingester.NullContentStream.getSrcInfo.text=File:{0} Ingester.NullContentStream.getReader=Not supported yet. KeywordSearch.moduleErr=Module Error KeywordSearch.fireNumIdxFileChg.moduleErr.msg=A module caused an error listening to KeywordSearch updates. See log to determine which module. Some data could be incomplete. KeywordSearchListsEncase.save.exception.msg=Not supported yet. KeywordSearchListsEncase.save2.exception.msg=Not supported yet. -KeywordSearchListsEncase.encaseMetaType.exception.msg=Unsupported EncaseMetaType\: {0} +KeywordSearchListsEncase.encaseMetaType.exception.msg=Unsupported EncaseMetaType: {0} KeywordSearchListsManagementPanel.getColName.text=Name KeywordSearchListsManagementPanel.setValueAt.exception.msg=Editing of cells is not supported KeywordSearchOptionsPanelController.moduleErr=Module Error KeywordSearchOptionsPanelController.moduleErr.msg1=A module caused an error listening to KeywordSearchOptionsPanelController updates. See log to determine which module. Some data could be incomplete. KeywordSearchOptionsPanelController.moduleErr.msg2=A module caused an error listening to KeywordSearchOptionsPanelController updates. See log to determine which module. Some data could be incomplete. KeywordSearchQueryManager.pathText.text=Keyword search -KeywordSearchResultFactory.progress.saving=Saving results\: {0} +KeywordSearchResultFactory.progress.saving=Saving results: {0} KeywordSearchSettings.moduleName.text=KeywordSearch KeywordSearchSettings.properties_options.text={0}_Options KeywordSearchSettings.propertiesNSRL.text={0}_NSRL @@ -228,23 +224,23 @@ Server.start.exception.cantStartSolr.msg=Could not start Solr server process Server.start.exception.cantStartSolr.msg2=Could not start Solr server process Server.isRunning.exception.errCheckSolrRunning.msg=Error checking if Solr server is running Server.isRunning.exception.errCheckSolrRunning.msg2=Error checking if Solr server is running -Server.openCore.exception.alreadyOpen.msg=Already an open Core\! Explicitely close Core first. +Server.openCore.exception.alreadyOpen.msg=Already an open Core! Explicitely close Core first. Server.queryNumIdxFiles.exception.msg=Error querying number of indexed files, Server.queryNumIdxChunks.exception.msg=Error querying number of indexed chunks, Server.queryNumIdxDocs.exception.msg=Error querying number of indexed documents, Server.queryIsIdxd.exception.msg=Error checking if content is indexed, Server.queryNumFileChunks.exception.msg=Error getting number of file chunks, -Server.query.exception.msg=Error running query\: {0} -Server.query2.exception.msg=Error running query\: {0} -Server.queryTerms.exception.msg=Error running terms query\: {0} -Server.connect.exception.msg=Failed to connect to Solr server\: {0} +Server.query.exception.msg=Error running query: {0} +Server.query2.exception.msg=Error running query: {0} +Server.queryTerms.exception.msg=Error running terms query: {0} +Server.connect.exception.msg=Failed to connect to Solr server: {0} Server.openCore.exception.msg=Keyword search service not yet running Server.openCore.exception.cantOpen.msg=Could not create or open index Server.openCore.exception.noIndexDir.msg=Index directory could not be created or is missing Server.request.exception.exception.msg=Could not issue Solr request Server.commit.exception.msg=Could not commit index -Server.addDoc.exception.msg=Could not add document to index via update handler\: {0} -Server.addDoc.exception.msg2=Could not add document to index via update handler\: {0} +Server.addDoc.exception.msg=Could not add document to index via update handler: {0} +Server.addDoc.exception.msg2=Could not add document to index via update handler: {0} Server.close.exception.msg=Cannot close Core Server.close.exception.msg2=Cannot close Core Server.solrServerNoPortException.msg=Indexing server could not bind to port {0}, port is not available, consider change the default {1} port. @@ -285,8 +281,8 @@ KeywordSearchIngestModule.previewThLbl=Preview KeywordSearchIngestModule.fileThLbl=File KeywordSearchIngestModule.listThLbl=List KeywordSearchIngestModule.regExThLbl=Reg Ex -AbstractFileTikaTextExtract.index.tikaParseTimeout.text=Exception\: Tika parse timeout for content\: {0}, {1} -AbstractFileTikaTextExtract.index.exception.tikaParse.msg=Exception\: Unexpected exception from Tika parse task execution for file\: {0}, {1} +AbstractFileTikaTextExtract.index.tikaParseTimeout.text=Exception: Tika parse timeout for content: {0}, {1} +AbstractFileTikaTextExtract.index.exception.tikaParse.msg=Exception: Unexpected exception from Tika parse task execution for file: {0}, {1} KeywordSearchEditListPanel.exportButtonAction.featureName.text=Keyword List Export KeywordSearchGlobalListSettingsPanel.component.featureName.text=Save Keyword List KeywordSearchListsAbstract.moduleErr=Module Error diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 8ca26a2a9d..b290d7abe4 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -37,99 +37,95 @@ ExtractOs.windowsVolume.label=OS Drive (Windows) ExtractOs.yellowDogLinuxOs.label=Linux (Yellow Dog) ExtractOs.yellowDogLinuxVolume.label=OS Drive (Linux Yellow Dog) OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=\ - Recent Activity ingest module.\n\n\ - The module extracts useful information about the recent user activity on the disk image being ingested, such as\:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\ - The module currently supports Windows only disk images. \n\ - The plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images. \nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files. Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files. -Chrome.getHistory.errMsg.errAnalyzingFile={0}\: Error while trying to analyze file\:{1} +Chrome.getHistory.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1} Chrome.parentModuleName=Recent Activity Chrome.getBookmark.errMsg.errGettingFiles=Error when trying to get Chrome Bookmark files. -Chrome.getBookmark.errMsg.errAnalyzingFile={0}\: Error while trying to analyze file\:{1} -Chrome.getBookmark.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\: {1} -Chrome.getBookmark.errMsg.errAnalyzingFile3={0}\: Error while trying to analyze file\: {1} -Chrome.getBookmark.errMsg.errAnalyzingFile4={0}\: Error while trying to analyze file\:{1} +Chrome.getBookmark.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1} +Chrome.getBookmark.errMsg.errAnalyzeFile={0}: Error while trying to analyze file: {1} +Chrome.getBookmark.errMsg.errAnalyzingFile3={0}: Error while trying to analyze file: {1} +Chrome.getBookmark.errMsg.errAnalyzingFile4={0}: Error while trying to analyze file:{1} Chrome.getCookie.errMsg.errGettingFiles=Error when trying to get Chrome history files. -Chrome.getCookie.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\:{1} +Chrome.getCookie.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{1} Chrome.getDownload.errMsg.errGettingFiles=Error when trying to get Chrome history files. -Chrome.getDownload.errMsg.errAnalyzeFiles1={0}\: Error while trying to analyze file\:{1} +Chrome.getDownload.errMsg.errAnalyzeFiles1={0}: Error while trying to analyze file:{1} Chrome.getLogin.errMsg.errGettingFiles=Error when trying to get Chrome history files. -Chrome.getLogin.errMsg.errAnalyzingFiles={0}\: Error while trying to analyze file\:{1} +Chrome.getLogin.errMsg.errAnalyzingFiles={0}: Error while trying to analyze file:{1} Chrome.getAutofill.errMsg.errGettingFiles=Error when trying to get Chrome Web Data files. -Chrome.getAutofill.errMsg.errAnalyzingFiles={0}\: Error while trying to analyze file\:{1} -Extract.dbConn.errMsg.failedToQueryDb={0}\: Failed to query database. +Chrome.getAutofill.errMsg.errAnalyzingFiles={0}: Error while trying to analyze file:{1} +Extract.dbConn.errMsg.failedToQueryDb={0}: Failed to query database. ExtractIE.moduleName.text=Internet Explorer -ExtractIE.getBookmark.errMsg.errGettingBookmarks={0}\: Error getting Internet Explorer Bookmarks. +ExtractIE.getBookmark.errMsg.errGettingBookmarks={0}: Error getting Internet Explorer Bookmarks. ExtractIE.parentModuleName.noSpace=RecentActivity ExtractIE.parentModuleName=Recent Activity -ExtractIE.getURLFromIEBmkFile.errMsg={0}\: Error parsing IE bookmark File {1} -ExtractIE.getURLFromIEBmkFile.errMsg2={0}\: Error parsing IE bookmark File {1} -ExtractIE.getCookie.errMsg.errGettingFile={0}\: Error getting Internet Explorer cookie files. -ExtractIE.getCookie.errMsg.errReadingIECookie={0}\: Error reading Internet Explorer cookie {1} -ExtractIE.getHistory.errMsg.unableToGetHist={0}\: Unable to get IE History\: pasco not found -ExtractIE.getHistory.errMsg.errGettingHistFiles={0}\: Error getting Internet Explorer history files +ExtractIE.getURLFromIEBmkFile.errMsg={0}: Error parsing IE bookmark File {1} +ExtractIE.getURLFromIEBmkFile.errMsg2={0}: Error parsing IE bookmark File {1} +ExtractIE.getCookie.errMsg.errGettingFile={0}: Error getting Internet Explorer cookie files. +ExtractIE.getCookie.errMsg.errReadingIECookie={0}: Error reading Internet Explorer cookie {1} +ExtractIE.getHistory.errMsg.unableToGetHist={0}: Unable to get IE History: pasco not found +ExtractIE.getHistory.errMsg.errGettingHistFiles={0}: Error getting Internet Explorer history files ExtractIE.getHistory.errMsg.noHistFiles=No InternetExplorer history files found. -ExtractIE.getHistory.errMsg.errWriteFile={0}\: Error while trying to write file\:{1} -ExtractIE.getHistory.errMsg.errProcHist={0}\: Error processing Internet Explorer history. -ExtractIE.parsePascoOutput.errMsg.notFound={0}\: Pasco output not found\: {1} -ExtractIE.parsePascoOutput.errMsg.errParsing={0}\: Error parsing IE history entry {1} -ExtractIE.parsePascoOutput.errMsg.errParsingEntry={0}\: Error parsing Internet Explorer History entry. +ExtractIE.getHistory.errMsg.errWriteFile={0}: Error while trying to write file:{1} +ExtractIE.getHistory.errMsg.errProcHist={0}: Error processing Internet Explorer history. +ExtractIE.parsePascoOutput.errMsg.notFound={0}: Pasco output not found: {1} +ExtractIE.parsePascoOutput.errMsg.errParsing={0}: Error parsing IE history entry {1} +ExtractIE.parsePascoOutput.errMsg.errParsingEntry={0}: Error parsing Internet Explorer History entry. ExtractRegistry.moduleName.text=Registry -ExtractRegistry.findRegFiles.errMsg.errReadingFile=Error fetching registry file\: {0} -ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp={0}\: Error analyzing registry file {1} -ExtractRegistry.analyzeRegFiles.failedParsingResults={0}\: Failed parsing registry file results {1} +ExtractRegistry.findRegFiles.errMsg.errReadingFile=Error fetching registry file: {0} +ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp={0}: Error analyzing registry file {1} +ExtractRegistry.analyzeRegFiles.failedParsingResults={0}: Failed parsing registry file results {1} ExtractRegistry.parentModuleName.noSpace=RecentActivity ExtractRegistry.programName=RegRipper -ExtractRegistry.analyzeRegFiles.errMsg.errReadingRegFile={0}\: Error reading registry file - {1} -ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile={0}\: Failed to analyze registry file -ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile2={0}\: Failed to analyze registry file -ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile3={0}\: Failed to analyze registry file -ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile4={0}\: Failed to analyze registry file +ExtractRegistry.analyzeRegFiles.errMsg.errReadingRegFile={0}: Error reading registry file - {1} +ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile={0}: Failed to analyze registry file +ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile2={0}: Failed to analyze registry file +ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile3={0}: Failed to analyze registry file +ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile4={0}: Failed to analyze registry file Firefox.moduleName=FireFox Firefox.getHistory.errMsg.errFetchingFiles=Error fetching internet history files for Firefox. Firefox.getHistory.errMsg.noFilesFound=No FireFox history files found. -Firefox.getHistory.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\:{1} +Firefox.getHistory.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{1} Firefox.getFormsAutofill.errMsg.errFetchingFiles=Error fetching form history file for Firefox. Firefox.getFormsAutofill.errMsg.noFilesFound=No FireFox form history files found. -Firefox.getFormsAutofill.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\:{1} +Firefox.getFormsAutofill.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{1} Firefox.getAutofillProfiles.errMsg.errFetchingFiles=Error fetching Autofill Profiles file for Firefox. Firefox.getAutofillProfiles.errMsg.noFilesFound=No FireFox Autofill Profiles files found. -Firefox.getAutofillProfiles.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\:{1} +Firefox.getAutofillProfiles.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{1} Firefox.parentModuleName.noSpace=RecentActivity Firefox.parentModuleName=Recent Activity Firefox.getBookmark.errMsg.errFetchFiles=Error fetching bookmark files for Firefox. -Firefox.getBookmark.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\:{1} +Firefox.getBookmark.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{1} Firefox.getCookie.errMsg.errFetchFile=Error fetching cookies files for Firefox. -Firefox.getCookie.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\:{1} +Firefox.getCookie.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{1} Firefox.getDlPre24.errMsg.errFetchFiles=Error fetching 'downloads' files for Firefox. -Firefox.getDlPre24.errMsg.errAnalyzeFiles={0}\: Error while trying to analyze file\:{1} -Firefox.getDlPre24.errMsg.errParsingArtifacts={0}\: Error parsing {1} Firefox web history artifacts. +Firefox.getDlPre24.errMsg.errAnalyzeFiles={0}: Error while trying to analyze file:{1} +Firefox.getDlPre24.errMsg.errParsingArtifacts={0}: Error parsing {1} Firefox web history artifacts. Firefox.getDlV24.errMsg.errFetchFiles=Error fetching 'downloads' files for Firefox. -Firefox.getDlV24.errMsg.errAnalyzeFile={0}\: Error while trying to analyze file\:{1} -Firefox.getDlV24.errMsg.errParsingArtifacts={0}\: Error parsing {1} Firefox web download artifacts. +Firefox.getDlV24.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{1} +Firefox.getDlV24.errMsg.errParsingArtifacts={0}: Error parsing {1} Firefox web download artifacts. RAImageIngestModule.process.started=Started {0} RAImageIngestModule.process.errModFailed={0} failed - see log for details
RAImageIngestModule.process.errModErrs={0} had errors -- see log -RAImageIngestModule.process.errMsg.errsEncountered=

Errors encountered during analysis\:

    +RAImageIngestModule.process.errMsg.errsEncountered=

    Errors encountered during analysis:

      RAImageIngestModule.process.errMsgSub.oneErr=1 error found RAImageIngestModule.process.errMsgSub.nErrs={0} errors found RAImageIngestModule.process.ingestMsg.finished=Finished {0} - {1} RAImageIngestModule.process.errMsg.noErrs=

      No errors encountered.

      RAImageIngestModule.process.errMsgSub.noErrs=No errors reported -RAImageIngestModule.process.histMsg.title=

      Browser Data on {0}\:

        +RAImageIngestModule.process.histMsg.title=

        Browser Data on {0}:

          RAImageIngestModule.process.histMsg.found=\ Found. RAImageIngestModule.process.histMsg.notFnd=\ Not Found. RAImageIngestModule.process.ingestMsg.results={0} - Browser Results RAImageIngestModule.complete.errMsg.failed={0} failed to complete - see log for details
          RAImageIngestModule.getName=Recent Activity RAImageIngestModule.getDesc=Extracts recent user activity, such as Web browsing, recently used documents and installed programs. -RecentDocumentsByLnk.getRecDoc.errMsg.errGetLnkFiles={0}\: Error getting lnk Files. -RecentDocumentsByLnk.getRecDoc.errParsingFile={0}\: Error parsing Recent File {1} +RecentDocumentsByLnk.getRecDoc.errMsg.errGetLnkFiles={0}: Error getting lnk Files. +RecentDocumentsByLnk.getRecDoc.errParsingFile={0}: Error parsing Recent File {1} RecentDocumentsByLnk.parentModuleName.noSpace=RecentActivity RecentDocumentsByLnk.parentModuleName=Recent Activity RegRipperFullNotFound=Full version RegRipper executable not found. @@ -139,11 +135,7 @@ SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -SearchEngineURLQueryAnalyzer.toString=Name\: {0}\n\ - Domain Substring\: {1}\n\ - count\: {2}\n\ - Split Tokens\: \n\ - {3} +SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\ncount: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity -UsbDeviceIdMapper.parseAndLookup.text=Product\: {0} +UsbDeviceIdMapper.parseAndLookup.text=Product: {0} diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle.properties-MERGED b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle.properties-MERGED index a699992c1a..16646b8e90 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle.properties-MERGED +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle.properties-MERGED @@ -1,19 +1,16 @@ MboxParser.handleAttch.noOpenCase.errMsg=Exception while getting open case. OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=\ - Email Parser ingest module.\n\n\ - The module extracts MBOX and PST e-mail files and posts the results to the blackboard. \n\ - It knows about the Thunderbird folder structure for MBOX files. +OpenIDE-Module-Long-Description=Email Parser ingest module.\n\nThe module extracts MBOX and PST e-mail files and posts the results to the blackboard. \nIt knows about the Thunderbird folder structure for MBOX files. OpenIDE-Module-Name=Email Parser OpenIDE-Module-Short-Description=Parses MBOX and PST files MboxParser.parse.errMsg.failedToReadFile=Failed to read mbox file from disk. MboxParser.parse.errMsg.couldntFindCharset=Could not find appropriate charset encoder. MboxParser.parse.errMsg.failedToParseNMsgs=Failed to extract {0} email messages. -MboxParser.handleAttch.errMsg.failedToCreateOnDisk=Failed to extract MBOX attachment to disk\: {0} -MboxParser.handleAttch.failedWriteToDisk=Failed to extract attachment to disk\: {0} +MboxParser.handleAttch.errMsg.failedToCreateOnDisk=Failed to extract MBOX attachment to disk: {0} +MboxParser.handleAttch.failedWriteToDisk=Failed to extract attachment to disk: {0} PstParser.noOpenCase.errMsg=Exception while getting open case. PstParser.parse.errMsg.failedToParseNMsgs=Failed to extract {0} email messages. -PstParser.extractAttch.errMsg.failedToExtractToDisk=Failed to extract PST attachment to disk\: {0} +PstParser.extractAttch.errMsg.failedToExtractToDisk=Failed to extract PST attachment to disk: {0} ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search. ThunderbirdMboxFileIngestModule.moduleName=Email Parser ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case. From 6cfd243c079697b01c01277f01c4f78689b2310a Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Feb 2019 16:32:07 -0500 Subject: [PATCH 02/32] Finish merge conflict reoslution for Bundle.properties.MERGED files --- .../experimental/autoingest/Bundle.properties-MERGED | 6 ------ .../autopsy/keywordsearch/Bundle.properties-MERGED | 12 ------------ .../autopsy/recentactivity/Bundle.properties-MERGED | 8 -------- 3 files changed, 26 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index c2de4a110a..395b8ad4cb 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -144,8 +144,6 @@ AutoIngestJobsNode.status.text=Status AutoIngestJobsPanel.waitNode.text=Please Wait... AutoIngestMetricsDialog.initReportText=Select a date above and click the 'Generate Metrics Report' button to generate\na metrics report. AutoIngestMetricsDialog.title.text=Auto Ingest Metrics -CaseDeletionTask.error.failedToExclusivelyLockCase=Failed to exclusively lock the case, it may be in use. -CaseDeletionTask.error.failedToLockCase=A coordination service error occurred while trying to lock the case for deletion: {0}. CaseDeletionTask.progress.connectingToCoordSvc=Connecting to coordination service (ZooKeeper)... # {0} - case name CaseDeletionTask.progress.deletingCase=Deleting {0}... @@ -191,7 +189,6 @@ DeleteCaseInputDirectoriesAction.menuItemText=Delete Input Directories DeleteCasesAction.menuItemText=Delete Case and Jobs DeleteCasesForReprocessingAction.menuItemText=Delete for Reprocessing GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz) -<<<<<<< HEAD HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. @@ -199,10 +196,7 @@ OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File # {0} - caseErrorMessage OpenCaseAction.errorMsg=Failed to open case: {0} OpenCaseAction.menuItemText=Open -OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change. \n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it. -======= OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change.\n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it. ->>>>>>> upstream/develop 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. DisplayLogDialog.cannotOpenLog=Unable to open the selected case log file diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index c59cd59368..f4febc1d7c 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -34,11 +34,7 @@ KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get sc KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found. KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -<<<<<<< HEAD -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time. \nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. -======= OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\n\The module indexes files found in the disk image at ingest time.\n\It then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. ->>>>>>> upstream/develop OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search @@ -132,11 +128,7 @@ KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl=View in New Window KeywordSearchIngestModule.init.noKwInLstMsg=No keywords in keyword list. KeywordSearchIngestModule.init.onlyIdxKwSkipMsg=Only indexing will be done and keyword search will be skipped (you can still add keyword lists using the Keyword Lists - Add to Ingest). KeywordSearchIngestModule.doInBackGround.displayName=Periodic Keyword Search -<<<<<<< HEAD -KeywordSearchIngestModule.doInBackGround.finalizeMsg=- Finalizing -======= KeywordSearchIngestModule.doInBackGround.finalizeMsg=Finalizing ->>>>>>> upstream/develop KeywordSearchIngestModule.doInBackGround.pendingMsg=(Pending) RawText.FileText=File Text RawText.ResultText=Result Text @@ -232,11 +224,7 @@ Server.start.exception.cantStartSolr.msg=Could not start Solr server process Server.start.exception.cantStartSolr.msg2=Could not start Solr server process Server.isRunning.exception.errCheckSolrRunning.msg=Error checking if Solr server is running Server.isRunning.exception.errCheckSolrRunning.msg2=Error checking if Solr server is running -<<<<<<< HEAD -Server.openCore.exception.alreadyOpen.msg=Already an open Core! Explicitely close Core first. -======= Server.openCore.exception.alreadyOpen.msg=There is an already open Solr core. Explicitly close the core first. ->>>>>>> upstream/develop Server.queryNumIdxFiles.exception.msg=Error querying number of indexed files, Server.queryNumIdxChunks.exception.msg=Error querying number of indexed chunks, Server.queryNumIdxDocs.exception.msg=Error querying number of indexed documents, diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 2a9e6e0c15..62bacbc9fc 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -37,11 +37,7 @@ ExtractOs.windowsVolume.label=OS Drive (Windows) ExtractOs.yellowDogLinuxOs.label=Linux (Yellow Dog) ExtractOs.yellowDogLinuxVolume.label=OS Drive (Linux Yellow Dog) OpenIDE-Module-Display-Category=Ingest Module -<<<<<<< HEAD -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images. \nThe plugin is also fully functional when deployed on Windows version of Autopsy. -======= OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. ->>>>>>> upstream/develop OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome @@ -139,11 +135,7 @@ SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -<<<<<<< HEAD -SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\ncount: {2}\nSplit Tokens: \n{3} -======= SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\n\count: {2}\nSplit Tokens: \n{3} ->>>>>>> upstream/develop SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity UsbDeviceIdMapper.parseAndLookup.text=Product: {0} From 97c479e5baf7a991ab9dc883ac5ef249458d41d9 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 1 Mar 2019 08:30:00 -0500 Subject: [PATCH 03/32] Interim commit for improved multi-user case deletion --- Core/nbproject/project.xml | 1 + .../autopsy/actions/Bundle.properties-MERGED | 4 +- .../casemodule/Bundle.properties-MERGED | 81 ++--- .../sleuthkit/autopsy/casemodule/Case.java | 58 ++-- .../autopsy/casemodule/CaseMetadata.java | 4 - .../services/Bundle.properties-MERGED | 2 +- .../Bundle.properties-MERGED | 5 +- .../contentviewers/Bundle.properties-MERGED | 8 +- .../autopsy/core/Bundle.properties-MERGED | 8 +- .../corecomponents/Bundle.properties-MERGED | 10 +- .../coreutils/Bundle.properties-MERGED | 18 +- .../datamodel/Bundle.properties-MERGED | 14 +- .../directorytree/Bundle.properties-MERGED | 10 +- .../filesearch/Bundle.properties-MERGED | 12 +- .../autopsy/ingest/Bundle.properties-MERGED | 14 +- .../Bundle.properties-MERGED | 6 +- .../Bundle.properties-MERGED | 27 +- .../modules/exif/Bundle.properties-MERGED | 4 +- .../fileextmismatch/Bundle.properties-MERGED | 18 +- .../hashdatabase/Bundle.properties-MERGED | 30 +- .../interestingitems/Bundle.properties-MERGED | 4 +- .../photoreccarver/Bundle.properties-MERGED | 12 +- .../autopsy/report/Bundle.properties-MERGED | 42 +-- .../autopsy/timeline/Bundle.properties-MERGED | 2 +- .../timeline/ui/Bundle.properties-MERGED | 6 +- .../autoingest/AinStatusPanel.java | 1 - .../autoingest/AutoIngestJob.java | 1 + .../autoingest/AutoIngestJobsNode.java | 8 + .../autoingest/Bundle.properties-MERGED | 43 ++- .../autoingest/CaseDeletionTask.java | 318 ++++++++++++------ .../keywordsearch/Bundle.properties-MERGED | 2 +- .../recentactivity/Bundle.properties-MERGED | 4 +- 32 files changed, 446 insertions(+), 331 deletions(-) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 61eee79f24..20225c61ef 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -311,6 +311,7 @@ net.sf.sevenzipjbinding.simple.impl org.sleuthkit.autopsy.actions org.sleuthkit.autopsy.appservices + org.sleuthkit.autopsy.autoingest org.sleuthkit.autopsy.casemodule org.sleuthkit.autopsy.casemodule.events org.sleuthkit.autopsy.casemodule.multiusercases diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED index 78a0433157..506786c42d 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED @@ -69,7 +69,7 @@ GetTagNameDialog.createTag=Create Tag GetTagNameDialog.cancelName=Cancel GetTagNameDialog.mustSupplyTtagName.msg=Must supply a tag name to continue. GetTagNameDialog.tagNameErr=Tag Name -GetTagNameDialog.illegalChars.msg=The tag name contains illegal characters.\nCannot contain any of the following symbols\: \\ \: * ? " < > | , ; +GetTagNameDialog.illegalChars.msg=The tag name contains illegal characters.\nCannot contain any of the following symbols: \\ : * ? " < > | , ; GetTagNameDialog.illegalCharsErr=Illegal Characters GetTagNameDialog.unableToAddTagNameToCase.msg=Unable to add the {0} tag name to the case. GetTagNameDialog.taggingErr=Tagging Error @@ -80,7 +80,7 @@ OpenLogFolder.error1=Log File Not Found: {0} OpenLogFolder.CouldNotOpenLogFolder=Could not open log folder CTL_OpenLogFolder=Open Log Folder CTL_OpenOutputFolder=Open Output Folder -OpenOutputFolder.error1=Output Folder Not Found\: {0} +OpenOutputFolder.error1=Output Folder Not Found: {0} OpenOutputFolder.noCaseOpen=No open case, therefore no current output folder available. OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder # {0} - old tag name diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index a5ca101dcb..47e8d1f9d7 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -58,26 +58,26 @@ Case.progressMessage.connectingToCoordSvc=Connecting to coordination service... Case.progressMessage.creatingCaseDatabase=Creating case database... Case.progressMessage.creatingCaseDirectory=Creating case directory... Case.progressMessage.creatingCaseNodeData=Creating coordination service node data... -Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log coordination service node... +Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log lock coordination service node... Case.progressMessage.deletingCaseDatabase=Deleting case database... Case.progressMessage.deletingCaseDirectory=Deleting case directory... -Case.progressMessage.deletingDirectroyCoordinationServiceNode=Deleting case ersource coordination service node... -Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node... +Case.progressMessage.deletingDirectoryCoordinationServiceNode=Deleting case directory lock coordination service node... +Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resourceslock coordination service node... Case.progressMessage.deletingTextIndex=Deleting text index... # {0} - exception message -Case.progressMessage.errorConnectingToCoordSvc=An occurred connecting to the coordination service (see log for details): {0}. +Case.progressMessage.errorConnectingToCoordSvc=An error occurred connecting to the coordination service (see log for details): {0}. # {0} - exception message -Case.progressMessage.errorDeletingCaseDb=An occurred deleting the case database (see log for details): {0}. +Case.progressMessage.errorDeletingCaseDb=An error occurred deleting the case database (see log for details): {0}. # {0} - exception message -Case.progressMessage.errorDeletingCaseDir=An occurred deleting the case directory (see log for details): {0}. +Case.progressMessage.errorDeletingCaseDir=An error occurred deleting the case directory (see log for details): {0}. # {0} - exception message -Case.progressMessage.errorDeletingCaseDirLockNode=An occurred deleting the case directory lock node (see log for details): {0}. +Case.progressMessage.errorDeletingCaseDirLockNode=An error occurred deleting the case directory lock node (see log for details): {0}. # {0} - exception message -Case.progressMessage.errorDeletingJobLogLockNode=An occurred deleting the case auto ingest log lock node (see log for details): {0}. +Case.progressMessage.errorDeletingJobLogLockNode=An error occurred deleting the case auto ingest log lock node (see log for details): {0}. # {0} - exception message -Case.progressMessage.errorDeletingResourcesLockNode=An occurred deleting the case resources lock node (see log for details): {0}. +Case.progressMessage.errorDeletingResourcesLockNode=An error occurred deleting the case resources lock node (see log for details): {0}. # {0} - exception message -Case.progressMessage.errorDeletingTextIndex=An occurred deleting the text index for the case (see log for details): {0}. +Case.progressMessage.errorDeletingTextIndex=An error occurred deleting the text index for the case (see log for details): {0}. # {0} - exception message Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}. # {0} - exception message @@ -85,7 +85,7 @@ Case.progressMessage.errorLockingCase=An error occurred exclusively locking the # {0} - exception message Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}. # {0} - exception message -Case.progressMessage.errorSavingDeletedItemsFlags=An occurred saving the deleted items flags in the coordination service database (see log for details): {0}. +Case.progressMessage.errorSavingDeletedItemsFlags=An error occurred saving the deleted items flags in the coordination service database (see log for details): {0}. Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case... Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data. Case.progressMessage.openingApplicationServiceResources=Opening application service case resources... @@ -232,7 +232,7 @@ AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status AddImageAction.wizard.title=Add Data Source AddImageAction.ingestConfig.ongoingIngest.msg=Ingest is ongoing on another data source. Adding a new source now might slow down the current ingest.
          Do you want to proceed and add a new data source now? AddImageAction.ingestConfig.ongoingIngest.title=Ingest in progress -AddImageTask.run.progress.adding=Adding\: {0} +AddImageTask.run.progress.adding=Adding: {0} AddImageTask.interrupt.exception.msg=Error stopping add-image process. AddImageWizardAddingProgressPanel.isValid.focusNext=Next > AddImageWizardAddingProgressPanel.stateStarted.progressBarText=*This process may take some time for large data sources. @@ -246,41 +246,33 @@ AddImageWizardIngestConfigPanel.dsProcDone.noErrs.text=*Data Source added. AddImageWizardIngestConfigPanel.dsProcDone.errs.text=*Errors encountered in adding Data Source. 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.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the database with the following path has been made\:\n {0} +AddLocalFilesTask.localFileAdd.progress.text=Adding: {0}/{1} +Case.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open! +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.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\ -this case are missing. Would you like to search for them now?\n\ -Previously, the image was located at\:\n\ -{0}\n\ -Please note that you will still be able to browse directories and generate reports\n\ -if you choose No, but you will not be able to view file content or run the ingest process. +Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \nthis case are missing. Would you like to search for them now?\nPreviously, the image was located at:\n{0}\nPlease note that you will still be able to browse directories and generate reports\nif 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.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. Case.exception.errGetRootObj=Error getting root objects. -Case.createCaseDir.exception.existNotDir=Cannot create case directory, it already exists and is not a directory\: {0} -Case.createCaseDir.exception.existCantRW=Cannot create case directory, it already exists and cannot read/write\: {0} -Case.createCaseDir.exception.cantCreate=Cannot create case directory or it already exists\: {0} -Case.createCaseDir.exception.cantCreateCaseDir=Could not create case directory\: {0} -Case.createCaseDir.exception.cantCreateModDir=Could not create modules output directory\: {0} -Case.createCaseDir.exception.cantCreateReportsDir=Could not create reports output directory\: {0} +Case.createCaseDir.exception.existNotDir=Cannot create case directory, it already exists and is not a directory: {0} +Case.createCaseDir.exception.existCantRW=Cannot create case directory, it already exists and cannot read/write: {0} +Case.createCaseDir.exception.cantCreate=Cannot create case directory or it already exists: {0} +Case.createCaseDir.exception.cantCreateCaseDir=Could not create case directory: {0} +Case.createCaseDir.exception.cantCreateModDir=Could not create modules output directory: {0} +Case.createCaseDir.exception.cantCreateReportsDir=Could not create reports output directory: {0} Case.CollaborationSetup.FailNotify.ErrMsg=Failed to connect to any other nodes that may be collaborating on this case. Case.CollaborationSetup.FailNotify.Title=Connection Failure 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} -CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\ - Case Name\: {0}\n\ - Case Directory\: {1} -CaseDeleteAction.closeConfMsg.title=Warning\: Closing the Current Case -CaseDeleteAction.msgDlg.fileInUse.msg=The delete action cannot be fully completed because the folder or file in it is open by another program.\n\n\ -Close the folder and file and try again or you can delete the case manually. -CaseDeleteAction.msgDlg.fileInUse.title=Error\: Folder In Use +CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \nCase Name: {0}\nCase Directory: {1} +CaseDeleteAction.closeConfMsg.title=Warning: Closing the Current Case +CaseDeleteAction.msgDlg.fileInUse.msg=The delete action cannot be fully completed because the folder or file in it is open by another program.\n\nClose the folder and file and try again or you can delete the case manually. +CaseDeleteAction.msgDlg.fileInUse.title=Error: Folder In Use CaseDeleteAction.msgDlg.caseDelete.msg=Case {0} has been deleted. CaseOpenAction.autFilter.title={0} Case File ( {1}) CaseCreateAction.msgDlg.cantCreateCase.msg=Cannot create case @@ -309,16 +301,15 @@ NewCaseWizardAction.newCase.windowTitle.text=New Case Information NewCaseWizardAction.getName.text=New Case Wizard NewCaseWizardAction.databaseProblem1.text=Cannot open database. Cancelling case creation. NewCaseWizardAction.databaseProblem2.text=Error -NewCaseWizardPanel1.validate.errMsg.invalidSymbols=The Case Name cannot contain any of the following symbols\: \\ / \: * ? " < > | +NewCaseWizardPanel1.validate.errMsg.invalidSymbols=The Case Name cannot contain any of the following symbols: \\ / : * ? " < > | NewCaseWizardPanel1.validate.errMsg.dirExists=Case directory ''{0}'' already exists. -NewCaseWizardPanel1.validate.confMsg.createDir.msg=The base directory "{0}" does not exist. \n\n\ - Do you want to create that directory? +NewCaseWizardPanel1.validate.confMsg.createDir.msg=The base directory "{0}" does not exist. \n\nDo you want to create that directory? NewCaseWizardPanel1.validate.confMsg.createDir.title=Create directory -NewCaseWizardPanel1.validate.errMsg.cantCreateParDir.msg=Error\: Could not create case parent directory {0} +NewCaseWizardPanel1.validate.errMsg.cantCreateParDir.msg=Error: Could not create case parent directory {0} NewCaseWizardPanel1.validate.errMsg.prevCreateBaseDir.msg=Prevented from creating base directory {0} -NewCaseWizardPanel1.validate.errMsg.cantCreateDir=Error\: Could not create directory. -NewCaseWizardPanel1.validate.errMsg.invalidBaseDir.msg=ERROR\: The Base Directory that you entered is not valid.\nPlease enter a valid Base Directory. -NewCaseWizardPanel1.createDir.errMsg.cantCreateDir.msg=ERROR\: Could not create the case directory. \nPlease enter a valid Case Name and Directory. +NewCaseWizardPanel1.validate.errMsg.cantCreateDir=Error: Could not create directory. +NewCaseWizardPanel1.validate.errMsg.invalidBaseDir.msg=ERROR: The Base Directory that you entered is not valid.\nPlease enter a valid Base Directory. +NewCaseWizardPanel1.createDir.errMsg.cantCreateDir.msg=ERROR: Could not create the case directory. \nPlease enter a valid Case Name and Directory. NewCaseWizardPanel2.validate.errCreateCase.msg=Error creating case OpenRecentCasePanel.colName.caseName=Case Name OpenRecentCasePanel.colName.path=Path @@ -348,15 +339,15 @@ StartupWindow.title.text=Welcome UpdateRecentCases.menuItem.clearRecentCases.text=Clear Recent Cases UpdateRecentCases.menuItem.empty=-Empty- AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=Cancel -NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on \"C:\" drive -NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on \"C:\" drive. Case folder is created on the target system +NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on "C:" drive +NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on "C:" drive. Case folder is created on the target system NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text=Warning: Path to case folder is on the target system. Create case folder in mounted drive. CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1} MissingImageDialog.lbWarning.text= MissingImageDialog.lbWarning.toolTipText= NewCaseVisualPanel1.caseParentDirWarningLabel.text= -NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user +NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user\t\t NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user NewCaseVisualPanel1.caseTypeLabel.text=Case Type: SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! @@ -365,7 +356,7 @@ SingleUserCaseConverter.NonUniqueDatabaseName=Database name not unique. SingleUserCaseConverter.UnableToCopySourceImages=Unable to copy source images SingleUserCaseConverter.CanNotOpenDatabase=Unable to open database CloseCaseWhileIngesting.Warning=Ingest is running. Are you sure you want to close the case? -CloseCaseWhileIngesting.Warning.title=Warning\: This will close the current case +CloseCaseWhileIngesting.Warning.title=Warning: This will close the current case CasePropertiesForm.imagesTable.columnModel.title1=Remove CasePropertiesForm.imagesTable.columnModel.title0=Path IngestJobInfoPanel.jLabel1.text=Ingest Modules diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 7f021111f9..1e0ebcde91 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import com.google.common.annotations.Beta; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import java.awt.Frame; import java.awt.event.ActionEvent; @@ -99,6 +100,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.ThreadUtils; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.events.AutopsyEvent; @@ -701,7 +703,7 @@ public class Case { CaseMetadata metadata = currentCase.getMetadata(); closeCurrentCase(); ProgressIndicator progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); - deleteCase(metadata, progressIndicator); + deleteCase(metadata, true, progressIndicator); } } @@ -885,16 +887,19 @@ public class Case { /** * Deletes a case. The case to be deletd must not be the "current case." * - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. + * @param metadata The case metadata. + * @param deleteCaseLockNodes Whether or not to delete the coordination + * service lock nodes for the case. + * @param progressIndicator A progress indicator. * * @throws CaseActionException If there were one or more errors deleting the * case. */ + @Beta @Messages({ "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first." }) - public static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + public static void deleteCase(CaseMetadata metadata, boolean deleteCaseLockNodes, ProgressIndicator progressIndicator) throws CaseActionException { synchronized (caseActionSerializationLock) { if (null != currentCase) { throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()); @@ -924,8 +929,8 @@ public class Case { */ @Messages({ "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details.", - "# {0} - exception message", "Case.progressMessage.errorDeletingTextIndex=An occurred deleting the text index for the case (see log for details): {0}.", - "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDir=An occurred deleting the case directory (see log for details): {0}." + "# {0} - exception message", "Case.progressMessage.errorDeletingTextIndex=An error occurred deleting the text index for the case (see log for details): {0}.", + "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDir=An error occurred deleting the case directory (see log for details): {0}." }) private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { boolean errorsOccurred = false; @@ -963,7 +968,7 @@ public class Case { */ @Messages({ "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", - "# {0} - exception message", "Case.progressMessage.errorConnectingToCoordSvc=An occurred connecting to the coordination service (see log for details): {0}.", + "# {0} - exception message", "Case.progressMessage.errorConnectingToCoordSvc=An error occurred connecting to the coordination service (see log for details): {0}.", "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user.", "# {0} - exception message", "Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}.", @@ -971,8 +976,8 @@ public class Case { "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", "# {0} - exception message", "Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}.", "Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data.", - "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDb=An occurred deleting the case database (see log for details): {0}.", - "# {0} - exception message", "Case.progressMessage.errorSavingDeletedItemsFlags=An occurred saving the deleted items flags in the coordination service database (see log for details): {0}." + "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDb=An error occurred deleting the case database (see log for details): {0}.", + "# {0} - exception message", "Case.progressMessage.errorSavingDeletedItemsFlags=An error occurred saving the deleted items flags in the coordination service database (see log for details): {0}." }) private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { CaseNodeData caseNodeData; @@ -987,17 +992,27 @@ public class Case { throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } + /* + * Acquire an exclusive case name lock. This will prevent auto ingest + * nodes from attempting to search for the case directory before it is + * deleted. + */ progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser()); - try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseName())) { - + final String caseNameLockName = TimeStampUtils.removeTimeStamp(metadata.getCaseName()); + try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) { if (nameLock == null) { logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because the case name was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); } + /* + * Acquire an exclusive case directory lock. This ensures that no + * other node (host) currently has the case open and prevents + * another node (host) from trying to open the case as it is being + * deleted. + */ try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { - if (dirLock == null) { logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because the case was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); @@ -1060,6 +1075,7 @@ public class Case { deleteCaseResourcesCoordinationServiceNode(metadata, progressIndicator, coordinationService); deleteCaseAutoIngestLogCoordinationServiceNode(metadata, progressIndicator, coordinationService); + // RJCTODO: Delete case name lock node if (!errorsOccurred) { deleteCaseDirectoryCoordinationServiceNode(metadata, progressIndicator, coordinationService); @@ -1096,7 +1112,8 @@ public class Case { */ @Messages({ "Case.progressMessage.deletingCaseDatabase=Deleting case database...",}) - private static void deleteCaseDatabase(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws UserPreferencesException, ClassNotFoundException, SQLException { + private static void deleteCaseDatabase(CaseMetadata metadata, ProgressIndicator progressIndicator, + CaseNodeData caseNodeData) throws UserPreferencesException, ClassNotFoundException, SQLException { if (caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { return; } @@ -1213,8 +1230,8 @@ public class Case { * @param progressIndicator A progress indicator. */ @Messages({ - "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...", - "# {0} - exception message", "Case.progressMessage.errorDeletingResourcesLockNode=An occurred deleting the case resources lock node (see log for details): {0}." + "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resourceslock coordination service node...", + "# {0} - exception message", "Case.progressMessage.errorDeletingResourcesLockNode=An error occurred deleting the case resources lock node (see log for details): {0}." }) static void deleteCaseResourcesCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode()); @@ -1235,8 +1252,8 @@ public class Case { * @param progressIndicator A progress indicator. */ @Messages({ - "Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log coordination service node...", - "# {0} - exception message", "Case.progressMessage.errorDeletingJobLogLockNode=An occurred deleting the case auto ingest log lock node (see log for details): {0}." + "Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log lock coordination service node...", + "# {0} - exception message", "Case.progressMessage.errorDeletingJobLogLockNode=An error occurred deleting the case auto ingest log lock node (see log for details): {0}." }) static void deleteCaseAutoIngestLogCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { progressIndicator.progress(Bundle.Case_progressMessage_deletingAutoIngestLogCoordSvcNode()); @@ -1261,8 +1278,8 @@ public class Case { * operation. */ @Messages({ - "Case.progressMessage.deletingDirectroyCoordinationServiceNode=Deleting case ersource coordination service node...", - "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDirLockNode=An occurred deleting the case directory lock node (see log for details): {0}." + "Case.progressMessage.deletingDirectoryCoordinationServiceNode=Deleting case directory lock coordination service node...", + "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDirLockNode=An error occurred deleting the case directory lock node (see log for details): {0}." }) static void deleteCaseDirectoryCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { String caseDirectoryLockNodePath = metadata.getCaseDirectory(); @@ -1321,7 +1338,6 @@ public class Case { */ SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase(); String backupDbPath = caseDb.getBackupDatabasePath(); - if (null != backupDbPath) { JOptionPane.showMessageDialog( mainFrame, @@ -3318,7 +3334,7 @@ public class Case { } else { progressIndicator = new LoggingProgressIndicator(); } - deleteCase(metadata, progressIndicator); + deleteCase(metadata, true, progressIndicator); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 5ddb93c291..9b71d51b06 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -190,10 +190,6 @@ public final class CaseMetadata { readFromFile(); } - public CaseMetadata(String metadataFilePath) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - /** * Gets the full path to the case metadata file. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED index 3f175c430f..5e79318334 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED @@ -9,7 +9,7 @@ TagNameDialog.JOptionPane.tagDescriptionIllegalCharacters.message=Tag descriptio TagNameDialog.JOptionPane.tagDescriptionIllegalCharacters.title=Invalid character in tag description TagNameDialog.notableCheckbox.text=Tag indicates item is notable. TagNameDialog.title.text=New Tag -TagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ; +TagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols: \\ : * ? " < > | , ; TagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name TagNameDialog.JOptionPane.tagNameEmpty.message=The tag name cannot be empty TagNameDialog.JOptionPane.tagNameEmpty.title=Empty tag name diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties-MERGED index 007af703c5..75866a79d0 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties-MERGED @@ -5,10 +5,7 @@ CentralRepoCommentDialog.title.addEditCentralRepoComment=Add/Edit Central Reposi OpenIDE-Module-Name=Central Repository OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Short-Description=Correlation Engine Ingest Module -OpenIDE-Module-Long-Description=\ - Correlation Engine ingest module and central database. \n\n\ - The Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\ - Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest. +OpenIDE-Module-Long-Description=Correlation Engine ingest module and central database. \n\nThe Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\nStored attributes are used in future cases to correlate and analyzes files and artifacts during ingest. CentralRepoCommentDialog.commentLabel.text=Comment: CentralRepoCommentDialog.okButton.text=&OK CentralRepoCommentDialog.cancelButton.text=C&ancel diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index c2e99a53b7..74b500780f 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -30,7 +30,7 @@ GstVideoPanel.exception.problemPlayCaptFrame.msg=Problem with video file; proble GstVideoPanel.exception.problemStopCaptFrame.msg=Problem with video file; problem when attempting to stop while capturing a frame. GstVideoPanel.progress.buffering=Buffering... GstVideoPanel.progressLabel.bufferingErr=Error buffering file -GstVideoPanel.progress.infoLabel.updateErr=Error updating video progress\: {0} +GstVideoPanel.progress.infoLabel.updateErr=Error updating video progress: {0} GstVideoPanel.ExtractMedia.progress.buffering=Buffering {0} MediaFileViewer.AccessibleContext.accessibleDescription= MediaFileViewer.title=Media @@ -41,7 +41,7 @@ MediaViewImagePanel.externalViewerButton.text=Open in External Viewer MediaViewVideoPanel.pauseButton.text=\u25ba MediaViewVideoPanel.progressLabel.text=00:00 MediaViewVideoPanel.infoLabel.text=info -MediaViewImagePanel.imgFileTooLarge.msg=Could not load image file (too large)\: {0} +MediaViewImagePanel.imgFileTooLarge.msg=Could not load image file (too large): {0} MessageContentViewer.AtrachmentsPanel.title=Attachments MessageContentViewer.showImagesToggleButton.hide.text=Hide Images @@ -76,8 +76,8 @@ Metadata.tableRowTitle.type=Type Metadata.title=File Metadata Metadata.toolTip=Displays metadata about the file. Metadata.nodeText.nonFilePassedIn=Non-file passed in -Metadata.nodeText.text=From The Sleuth Kit istat Tool\: -Metadata.nodeText.exceptionNotice.text=Error getting file metadata\: +Metadata.nodeText.text=From The Sleuth Kit istat Tool: +Metadata.nodeText.exceptionNotice.text=Error getting file metadata: MessageContentViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF MessageContentViewer.headersScrollPane.TabConstraints.tabTitle=Headers MessageContentViewer.textbodyScrollPane.TabConstraints.tabTitle=Text diff --git a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED index 087eaec314..f7039ab4ce 100755 --- a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED @@ -3,13 +3,7 @@ Installer.closing.confirmationDialog.title=Ingest is Running # {0} - exception message Installer.closing.messageBox.caseCloseExceptionMessage=Error closing case: {0} OpenIDE-Module-Display-Category=Infrastructure -OpenIDE-Module-Long-Description=\ - This is the core Autopsy module.\n\n\ - The module contains the core components needed for the bare application to run; the RCP platform, windowing GUI, sleuthkit bindings, datamodel / storage, explorer, result viewers, content viewers, ingest framework, reporting, and core tools, such as the file search.\n\n\ - The framework included in the module contains APIs for developing modules for ingest, viewers and reporting. \ - The modules can be deployed as Plugins using the Autopsy plugin installer.\n\ - This module should not be uninstalled - without it, Autopsy will not run.\n\n\ - For more information, see http://www.sleuthkit.org/autopsy/ +OpenIDE-Module-Long-Description=This is the core Autopsy module.\n\nThe module contains the core components needed for the bare application to run; the RCP platform, windowing GUI, sleuthkit bindings, datamodel / storage, explorer, result viewers, content viewers, ingest framework, reporting, and core tools, such as the file search.\n\nThe framework included in the module contains APIs for developing modules for ingest, viewers and reporting. The modules can be deployed as Plugins using the Autopsy plugin installer.\nThis module should not be uninstalled - without it, Autopsy will not run.\n\nFor more information, see http://www.sleuthkit.org/autopsy/ OpenIDE-Module-Name=Autopsy-Core OpenIDE-Module-Short-Description=Autopsy Core Module org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index 4823c66440..999e1a22f7 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -63,9 +63,9 @@ DataContentViewerHex.pageLabel2.text=Page DataContentViewerString.pageLabel2.text=Page # Product Information panel -LBL_Description=
          \n Product Version: {0} ({9})
          Sleuth Kit Version: {7}
          Netbeans RCP Build: {8}
          Java: {1}; {2}
          System: {3}; {4}; {5}
          Userdir: {6}
          +LBL_Description=
          \n Product Version: {0} ({9})
          Sleuth Kit Version: {7}
          Netbeans RCP Build: {8}
          Java: {1}; {2}
          System: {3}; {4}; {5}
          Userdir: {6}
          Format_OperatingSystem_Value={0} version {1} running on {2} -LBL_Copyright=
          Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
          Copyright © 2003-2018.
          +LBL_Copyright=
          Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
          Copyright © 2003-2018.
          SortChooser.dialogTitle=Choose Sort Criteria ThumbnailViewChildren.progress.cancelling=(Cancelling) # {0} - file name @@ -102,7 +102,7 @@ DataResultViewerThumbnail.pageNextButton.text= DataResultViewerThumbnail.imagesLabel.text=Images: DataResultViewerThumbnail.imagesRangeLabel.text=- DataResultViewerThumbnail.pageNumLabel.text=- -DataResultViewerThumbnail.filePathLabel.text=\ \ \ +DataResultViewerThumbnail.filePathLabel.text=\ DataResultViewerThumbnail.goToPageLabel.text=Go to Page: DataResultViewerThumbnail.goToPageField.text= AdvancedConfigurationDialog.cancelButton.text=Cancel @@ -138,7 +138,7 @@ TableFilterNode.displayName.text=Name DataResultViewerThumbnail.comboBox.smallThumbnails=Small Thumbnails DataResultViewerThumbnail.comboBox.mediumThumbnails=Medium Thumbnails DataResultViewerThumbnail.comboBox.largeThumbnails=Large Thumbnails -DataResultViewerThumbnail.switchPage.done.errMsg=Error making thumbnails\: {0} +DataResultViewerThumbnail.switchPage.done.errMsg=Error making thumbnails: {0} AboutWindowPanel.actVerboseLogging.text=Activate verbose logging OptionsCategory_Name_Multi_User_Settings=Multi-User OptionsCategory_Keywords_Multi_User_Options=Multi-User Settings @@ -153,7 +153,7 @@ MultiUserSettingsPanel.validationErrMsg.invalidIndexingServerPort=Invalid Solr s MultiUserSettingsPanel.validationErrMsg.invalidMessgeServiceURI=Message service host and/or port not valid DataContentViewerHex.goToOffsetLabel.text=Jump to Offset DataContentViewerHex.goToOffsetTextField.text= -DataContentViewerHex.goToOffsetTextField.msgDlg=Invalid Offset\: {0} +DataContentViewerHex.goToOffsetTextField.msgDlg=Invalid Offset: {0} DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue=Cannot jump to the resultant offset MultiUserSettingsPanel.tbOops.text= MultiUserSettingsPanel.lbTestDatabase.text= diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED index 015d9ae7f7..702b726e08 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED @@ -9,23 +9,21 @@ JLNK.noPrefPath.text=No preferred path found PlatformUtil.nameUnknown=unknown PlatformUtil.verUnknown=unknown PlatformUtil.archUnknown=unknown -PlatformUtil.jrePath.jreDir.msg=Embedded jre directory found in\: {0} -PlatformUtil.jrePath.usingJavaPath.msg=Using java binary path\: {0} +PlatformUtil.jrePath.jreDir.msg=Embedded jre directory found in: {0} +PlatformUtil.jrePath.usingJavaPath.msg=Using java binary path: {0} PlatformUtil.getPID.sigarNotInit.msg=Cannot get PID, sigar not initialized PlatformUtil.getPID.gen.msg=Cannot get PID,{0} PlatformUtil.getJavaPID.sigarNotInit.msg=Cannot get PID of a java process, sigar not initialized -PlatformUtil.getJavaPID.gen.msg=Cannot get PID for query\: {0}, {1} +PlatformUtil.getJavaPID.gen.msg=Cannot get PID for query: {0}, {1} PlatformUtil.getJavaPIDs.sigarNotInit=Cannot get PIDs of a java process, sigar not initialized -PlatformUtil.getJavaPIDs.gen.msg=Cannot get PIDs for query\: {0}, {1} +PlatformUtil.getJavaPIDs.gen.msg=Cannot get PIDs for query: {0}, {1} PlatformUtil.killProcess.sigarNotInit.msg=Cannot kill process by pid, sigar not initialized. -PlatformUtil.killProcess.gen.msg=Cannot kill process\: {0}, {1} +PlatformUtil.killProcess.gen.msg=Cannot kill process: {0}, {1} PlatformUtil.getProcVmUsed.sigarNotInit.msg=Cannot get virt mem used, sigar not initialized. PlatformUtil.getProcVmUsed.gen.msg=Cannot get virt mem used, {0} -PlatformUtil.getJvmMemInfo.usageText=JVM heap usage\: {0}, JVM non-heap usage\: {1} -PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, free)\: {0}, {1}, {2} -PlatformUtil.getAllMemUsageInfo.usageText={0}\n\ -{1}\n\ -Process Virtual Memory\: {2} +PlatformUtil.getJvmMemInfo.usageText=JVM heap usage: {0}, JVM non-heap usage: {1} +PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, free): {0}, {1}, {2} +PlatformUtil.getAllMemUsageInfo.usageText={0}\n{1}\nProcess Virtual Memory: {2} # {0} - file name ReadImageTask.mesageText=Reading image: {0} StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index a714ccd674..a0c938a4ed 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -239,8 +239,8 @@ FileSize.createSheet.name.desc=no description FileSize.createSheet.filterType.name=Size Range FileSize.createSheet.filterType.displayName=Size Range FileSize.createSheet.filterType.desc=no description -FileSize.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0} -FileTypeChildren.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0} +FileSize.exception.notSupported.msg=Not supported for this type of Displayable Item: {0} +FileTypeChildren.exception.notSupported.msg=Not supported for this type of Displayable Item: {0} FileTypesByExtNode.createSheet.filterType.name=File Type FileTypesByExtNode.createSheet.filterType.displayName=File Type FileTypesByExtNode.createSheet.filterType.desc=no description @@ -258,10 +258,10 @@ ImageNode.getActions.viewInNewWin.text=View in New Window ImageNode.createSheet.name.name=Name ImageNode.createSheet.name.displayName=Name ImageNode.createSheet.name.desc=no description -Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null\! -Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""\! -Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed\!\n\nDetails\: {0} -Installer.tskLibErr.err=Fatal Error\! +Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null! +Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""! +Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed!\n\nDetails: {0} +Installer.tskLibErr.err=Fatal Error! InterestingHits.interestingItems.text=INTERESTING ITEMS InterestingHits.displayName.text=Interesting Items InterestingHits.createSheet.name.name=Name @@ -298,7 +298,7 @@ RecentFiles.aut3dayFilter.displayName.text=Final Day - 3 RecentFiles.aut4dayFilter.displayName.text=Final Day - 4 RecentFiles.aut5dayFilter.displayName.text=Final Day - 5 RecentFiles.aut6dayFilter.displayName.text=Final Day - 6 -RecentFilesFilterChildren.exception.defaultVisit.msg=Not supported for this type of Displayable Item\: {0} +RecentFilesFilterChildren.exception.defaultVisit.msg=Not supported for this type of Displayable Item: {0} RecentFilesFilterNode.createSheet.filterType.name=Filter Type RecentFilesFilterNode.createSheet.filterType.displayName=Filter Type RecentFilesFilterNode.createSheet.filterType.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index 5571878ccc..0a0e8a7be2 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -92,9 +92,9 @@ ExplorerNodeActionVisitor.action.extUnallocToSingleFiles=Extract Unallocated Spa ExplorerNodeActionVisitor.action.fileSystemDetails.title=File System Details ExplorerNodeActionVisitor.action.volumeDetails.title=Volume Details ExplorerNodeActionVisitor.action.extUnallocToSingleFile=Extract Unallocated Space to Single File -ExplorerNodeActionVisitor.volDetail.noVolMatchErr=Error\: No Volume Matches. -ExplorerNodeActionVisitor.imgDetail.noVolMatchesErr=Error\: No Volume Matches. -ExplorerNodeActionVisitor.exception.probGetParent.text=Problem getting parent from {0}\: {1} +ExplorerNodeActionVisitor.volDetail.noVolMatchErr=Error: No Volume Matches. +ExplorerNodeActionVisitor.imgDetail.noVolMatchesErr=Error: No Volume Matches. +ExplorerNodeActionVisitor.exception.probGetParent.text=Problem getting parent from {0}: {1} ExtractAction.title.extractFiles.text=Extract File(s) ExtractAction.extractFiles.cantCreateFolderErr.msg=Could not create selected folder. ExtractAction.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite? @@ -114,8 +114,8 @@ ExtractUnallocAction.processing.counter.msg=processing {0} of {1} MBs ExtractUnallocAction.done.notifyMsg.completedExtract.title=Completed extraction of unallocated space. ExtractUnallocAction.done.notifyMsg.completedExtract.msg=Files were extracted to {0} ExtractUnallocAction.done.errMsg.title=Error Extracting -ExtractUnallocAction.done.errMsg.msg=Error extracting unallocated space\: {0} -ExtractAction.done.notifyMsg.extractErr=Error extracting files\: {0} +ExtractUnallocAction.done.errMsg.msg=Error extracting unallocated space: {0} +ExtractAction.done.notifyMsg.extractErr=Error extracting files: {0} OptionsCategory_Name_ExternalViewer=External Viewer OptionsCategory_Keywords_ExternalViewer=ExternalViewer ExternalViewerGlobalSettingsPanel.exePathLabel.MIME.text=Program associated with this MIME type diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED index 468050cc52..7ab8ecbe04 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED @@ -14,7 +14,7 @@ KnownStatusSearchPanel.knownCheckBox.text=Known Status: KnownStatusSearchPanel.knownBadOptionCheckBox.text=Notable KnownStatusSearchPanel.knownOptionCheckBox.text=Known (NSRL or other) KnownStatusSearchPanel.unknownOptionCheckBox.text=Unknown -DateSearchFilter.noneSelectedMsg.text=At least one date type must be selected\! +DateSearchFilter.noneSelectedMsg.text=At least one date type must be selected! DateSearchPanel.dateCheckBox.text=Date: DateSearchPanel.jLabel4.text=Timezone: DateSearchPanel.jLabel3.text=*The date format is mm/dd/yyyy @@ -45,18 +45,18 @@ DateSearchPanel.copyMenuItem.text=Copy FileSearchAction.getName.text=File Search by Attributes FileSearchDialog.frame.title=File Search by Attributes FileSearchDialog.frame.msg=File Search by Attributes -FileSearchPanel.custComp.label.text=Search for files that match the following criteria\: +FileSearchPanel.custComp.label.text=Search for files that match the following criteria: FileSearchPanel.filterTitle.name=Name FileSearchPanel.filterTitle.metadata=Metadata FileSearchPanel.filterTitle.knownStatus=Known Status FileSearchPanel.search.results.title=File Search Results {0} -FileSearchPanel.search.results.pathText=Filename Search Results\: -FileSearchPanel.search.results.msg=File Search\: {0} matches found +FileSearchPanel.search.results.pathText=Filename Search Results: +FileSearchPanel.search.results.msg=File Search: {0} matches found FileSearchPanel.search.results.details=Large number of matches may impact performance on some operations FileSearchPanel.search.exception.noFilterSelected.msg=At least one filter must be selected. -FileSearchPanel.search.validationErr.msg=Validation Error\: {0} +FileSearchPanel.search.validationErr.msg=Validation Error: {0} FileSearchPanel.emptyWhereClause.text=Invalid options, nothing to show. -KnownStatusSearchFilter.noneSelectedMsg.text=At least one known status must be selected\! +KnownStatusSearchFilter.noneSelectedMsg.text=At least one known status must be selected! NameSearchFilter.emptyNameMsg.text=Must enter something for name search. SearchNode.getName.text=Search Result SizeSearchPanel.sizeCompareComboBox.equalTo=equal to diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED index 0651524249..6be3e48e71 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED @@ -41,11 +41,11 @@ IngestDialog.startButton.title=Start IngestDialog.closeButton.title=Close IngestManager.moduleErr=Module Error IngestManager.moduleErr.errListenToUpdates.msg=A module caused an error listening to Ingest Manager updates. See log to determine which module. Some data could be incomplete. -IngestMessage.toString.type.text=type\: {0} -IngestMessage.toString.date.text=\ date\: {0} -IngestMessage.toString.subject.text=\ subject\: {0} -IngestMessage.toString.details.text=\ details\: {0} -IngestMessage.toString.data.text=\ data\: {0} +IngestMessage.toString.type.text=type: {0} +IngestMessage.toString.date.text=\ date: {0} +IngestMessage.toString.subject.text=\ subject: {0} +IngestMessage.toString.details.text=\ details: {0} +IngestMessage.toString.data.text=\ data: {0} IngestMessage.exception.typeSrcSubjNotNull.msg=message type, source and subject cannot be null IngestMessage.exception.srcSubjNotNull.msg=source and subject cannot be null IngestMessage.exception.srcSubjDetailsDataNotNull.msg=source, subject, details and data cannot be null @@ -84,7 +84,7 @@ IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.dataSource=Data Source IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.activity=Activity IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.file=File IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.startTime=Start Time -IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.elapsedTime=Elapsed Time (H\:M\:S) +IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.elapsedTime=Elapsed Time (H:M:S) IngestManager.IngestThreadActivitySnapshot.idleThread=IDLE IngestManager.IngestMessage.ErrorMessageLimitReached.subject=Maximum Errors Posted IngestManager.IngestMessage.ErrorMessageLimitReached.msg=Maximum number ({0}) of error and/or warning messages posted. See log for additional errors/warnings (Help -> Open Log Folder). @@ -140,7 +140,7 @@ IngestJob.cancelReason.outOfDiskSpace.text=Out of disk space IngestJob.cancelReason.servicesDown.text=Services Down IngestJob.cancelReason.caseClosed.text=Case closed IngestJobSettingsPanel.globalSettingsButton.text=Global Settings -gest +gest= IngestJobSettingsPanel.globalSettingsButton.actionCommand=Advanced IngestJobSettingsPanel.globalSettingsButton.text=Global Settings IngestJobSettingsPanel.pastJobsButton.text=History diff --git a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/Bundle.properties-MERGED index 9ce034d1ff..6e469cd5d1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/Bundle.properties-MERGED @@ -42,9 +42,9 @@ DataSourceIntegrityIngestModule.process.errReadImgAtChunk=Error reading {0} at c DataSourceIntegrityIngestModule.shutDown.verified=\ verified DataSourceIntegrityIngestModule.shutDown.notVerified=\ not verified DataSourceIntegrityIngestModule.shutDown.verifyResultsHeader=

          Data Source Verification Results for {0}

          -DataSourceIntegrityIngestModule.shutDown.resultLi=
        • Result\:{0}
        • -DataSourceIntegrityIngestModule.shutDown.calcHashLi=
        • Calculated hash\: {0}
        • -DataSourceIntegrityIngestModule.shutDown.storedHashLi=
        • Stored hash\: {0}
        • +DataSourceIntegrityIngestModule.shutDown.resultLi=
        • Result:{0}
        • +DataSourceIntegrityIngestModule.shutDown.calcHashLi=
        • Calculated hash: {0}
        • +DataSourceIntegrityIngestModule.shutDown.storedHashLi=
        • Stored hash: {0}
        • DataSourceIntegrityIngestSettingsPanel.computeHashesCheckbox.text=Calculate data source hashes if none are present DataSourceIntegrityIngestSettingsPanel.jLabel1.text=Note that this module will not run on logical files DataSourceIntegrityIngestSettingsPanel.jLabel3.text=Ingest Settings diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties-MERGED index 03854d8300..b31c5d9593 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties-MERGED @@ -11,34 +11,29 @@ ExtractArchiveWithPasswordAction.progress.text=Unpacking contents of archive: {0 ExtractArchiveWithPasswordAction.prompt.text=Enter Password ExtractArchiveWithPasswordAction.prompt.title=Enter Password OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=\ - Embedded File Extraction Ingest Module\n\nThe Embedded File Extraction Ingest Module processes document files (such as doc, docx, ppt, pptx, xls, xlsx) and archive files (such as zip and others archive types supported by the 7zip extractor).\n\ - Contents of these files are extracted and the derived files are added back to the current ingest to be processed by the configured ingest modules.\n\ - If the derived file happens to be an archive file, it will be re-processed by the 7zip extractor - the extractor will process archive files N-levels deep.\n\n\ - The extracted files are navigable in the directory tree.\n\n\ - The module is supported on Windows, Linux and Mac operating systems. +OpenIDE-Module-Long-Description=Embedded File Extraction Ingest Module\n\nThe Embedded File Extraction Ingest Module processes document files (such as doc, docx, ppt, pptx, xls, xlsx) and archive files (such as zip and others archive types supported by the 7zip extractor).\nContents of these files are extracted and the derived files are added back to the current ingest to be processed by the configured ingest modules.\nIf the derived file happens to be an archive file, it will be re-processed by the 7zip extractor - the extractor will process archive files N-levels deep.\n\nThe extracted files are navigable in the directory tree.\n\nThe module is supported on Windows, Linux and Mac operating systems. OpenIDE-Module-Name=Embedded File Extraction OpenIDE-Module-Short-Description=Embedded File Extraction Ingest Module -EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.seek.exception.invalidOrigin=Invalid seek origin\: {0} +EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.seek.exception.invalidOrigin=Invalid seek origin: {0} EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.read.exception.errReadStream=Error reading content stream. EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel=Content-only Encryption (Archive File) EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull=Full Encryption (Archive File) -EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.details=Error initializing output dir\: {0}\: {1} -EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg=Possible ZIP bomb detected in archive\: {0}, item\: {1} +EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.details=Error initializing output dir: {0}: {1} +EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg=Possible ZIP bomb detected in archive: {0}, item: {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping items in {1}. -EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb=Possible ZIP bomb detected\: {0} +EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb=Possible ZIP bomb detected: {0} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb=The archive is {0} levels deep, skipping processing of {1} -EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg=Unknown item path in archive\: {0}, will use\: {1} -EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg=Not enough disk space to unpack archive item\: {0}, {1} +EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg=Unknown item path in archive: {0}, will use: {1} +EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg=Not enough disk space to unpack archive item: {0}, {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details=The archive item is too large to unpack, skipping unpacking this item. EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg=Error unpacking {0} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details=Error unpacking {0}. {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg=Encrypted files in archive detected. -EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details=Some files in archive\: {0} are encrypted. {1} extractor was unable to extract all files from this archive. -EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg=Error writing unpacked file to\: {0} -EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg=Error adding a derived file to db\:{0} +EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details=Some files in archive: {0} are encrypted. {1} extractor was unable to extract all files from this archive. +EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg=Error writing unpacked file to: {0} +EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg=Error adding a derived file to db:{0} EmbeddedFileExtractorIngestModule.ImageExtractor.docContainer.init.err=Doc container could not be initialized while reading -EmbeddedFileExtractorIngestModule.ImageExtractor.docxContainer.init.err=Docx container could not be initialized while reading\: {0} +EmbeddedFileExtractorIngestModule.ImageExtractor.docxContainer.init.err=Docx container could not be initialized while reading: {0} EmbeddedFileExtractorIngestModule.ImageExtractor.pptContainer.init.err=Ppt container could not be initialized while reading: {0} EmbeddedFileExtractorIngestModule.ImageExtractor.pptxContainer.init.err=Pptx container could not be initialized while reading: {0} EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err=Xls container could not be initialized while reading: {0} diff --git a/Core/src/org/sleuthkit/autopsy/modules/exif/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/exif/Bundle.properties-MERGED index 9905159d99..ee788daf61 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/exif/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/exif/Bundle.properties-MERGED @@ -1,9 +1,7 @@ CannotRunFileTypeDetection=Cannot run file type detection. ExifParserFileIngestModule.indexError.message=Failed to index EXIF Metadata artifact for keyword search. OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=\ - Exif metadata ingest module. \n\n\ - The ingest module analyzes image files, extracts Exif information and posts the Exif data as results. +OpenIDE-Module-Long-Description=Exif metadata ingest module. \n\nThe ingest module analyzes image files, extracts Exif information and posts the Exif data as results. OpenIDE-Module-Name=ExifParser OpenIDE-Module-Short-Description=Exif metadata ingest module ExifParserFileIngestModule.moduleName.text=Exif Parser diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/Bundle.properties-MERGED index cfaadf1635..5063bd55fa 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/Bundle.properties-MERGED @@ -36,27 +36,27 @@ FileExtMismatchSettingsPanel.jLabel1.text=File Types: FileExtMismatchSettingsPanel.newExtButton.text=New Extension FileExtMismatchSettingsPanel.newMimePrompt.message=Add a new MIME file type: FileExtMismatchSettingsPanel.newMimePrompt.title=New MIME -FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.message=MIME type text is empty\! +FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.message=MIME type text is empty! FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.title=Empty type -FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.message=MIME type not supported\! +FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.message=MIME type not supported! FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.title=Type not supported -FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.message=MIME type already exists\! +FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.message=MIME type already exists! FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.title=Type already exists FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.message=MIME type is not detectable by this module. FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.title=Type not detectable -FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.message=No MIME type selected\! +FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.message=No MIME type selected! FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.title=No type selected FileExtMismatchSettingsPanel.newExtPrompt.message=Add an allowed extension: FileExtMismatchSettingsPanel.newExtPrompt.title=New allowed extension -FileExtMismatchSettingsPanel.newExtPrompt.empty.message=Extension text is empty\! +FileExtMismatchSettingsPanel.newExtPrompt.empty.message=Extension text is empty! FileExtMismatchSettingsPanel.newExtPrompt.empty.title=Extension text empty -FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.message=No MIME type selected\! +FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.message=No MIME type selected! FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.title=No MIME type selected -FileExtMismatchSettingsPanel.newExtPrompt.extExists.message=Extension already exists\! +FileExtMismatchSettingsPanel.newExtPrompt.extExists.message=Extension already exists! FileExtMismatchSettingsPanel.newExtPrompt.extExists.title=Extension already exists -FileExtMismatchSettingsPanel.removeExtButton.noneSelected.message=No extension selected\! +FileExtMismatchSettingsPanel.removeExtButton.noneSelected.message=No extension selected! FileExtMismatchSettingsPanel.removeExtButton.noneSelected.title=No extension selected -FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.message=No MIME type selected\! +FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.message=No MIME type selected! FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.title=No MIME type selected FileExtMismatchSettingsPanel.removeTypeButton.toolTipText= FileExtMismatchModuleSettingsPanel.checkAllRadioButton.text=Check all file types diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED index c550846ccf..ed79db018d 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED @@ -40,10 +40,7 @@ ImportCentralRepoDbProgressDialog.errorParsingFile.message=Error parsing hash se ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed ImportCentralRepoDbProgressDialog.title.text=Central Repository Import Progress OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=\ - Hash Set ingest module. \n\n\ - The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\n\ - The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration. +OpenIDE-Module-Long-Description=Hash Set ingest module. \n\nThe ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\nThe module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration. OpenIDE-Module-Name=HashDatabases OptionsCategory_Name_HashDatabase=Hash Sets OptionsCategory_Keywords_HashDatabase=Hash Sets @@ -61,7 +58,7 @@ OpenIDE-Module-Short-Description=Hash Set Ingest Module and hash set tools HashDbImportDatabaseDialog.jLabel1.text=Name: HashDbImportDatabaseDialog.databasePathTextField.text= HashDbImportDatabaseDialog.knownBadRadioButton.text=Notable -HashDbImportDatabaseDialog.jLabel2.text=Type of hash set\: +HashDbImportDatabaseDialog.jLabel2.text=Type of hash set: HashDbImportDatabaseDialog.okButton.text=OK HashDbImportDatabaseDialog.cancelButton.text=Cancel HashDbCreateDatabaseDialog.jLabel2.text=Type: @@ -131,20 +128,20 @@ HashDbImportDatabaseDialog.unableToCopyToUserDirMsg=Unable to copy the hash set HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg=Failed to open hash set at {0}. HashLookupModuleFactory.moduleName.text=Hash Lookup HashLookupModuleFactory.moduleDescription.text=Identifies known and notable files using supplied hash sets, such as a standard NSRL hash set. -HashDbIngestModule.fileReadErrorMsg=Read Error\: {0} +HashDbIngestModule.fileReadErrorMsg=Read Error: {0} HashDbIngestModule.calcHashValueErr=Error encountered while calculating the hash value for {0} ({1}). -HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error\: {0} +HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error: {0} HashDbIngestModule.settingKnownBadStateErr=Error encountered while setting notable state for {0}. HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}. HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}. HashDbIngestModule.postToBB.fileName=File Name HashDbIngestModule.postToBB.md5Hash=MD5 Hash HashDbIngestModule.postToBB.hashsetName=Hash Set Name -HashDbIngestModule.postToBB.knownBadMsg=Notable\: {0} -HashDbIngestModule.complete.knownBadsFound=Notables found\: +HashDbIngestModule.postToBB.knownBadMsg=Notable: {0} +HashDbIngestModule.complete.knownBadsFound=Notables found: HashDbIngestModule.complete.totalCalcTime=Total Calculation Time HashDbIngestModule.complete.totalLookupTime=Total Lookup Time -HashDbIngestModule.complete.databasesUsed=Hash Sets Used\: +HashDbIngestModule.complete.databasesUsed=Hash Sets Used: HashDbIngestModule.complete.hashLookupResults=Hash Lookup Results HashDbManager.moduleErrorListeningToUpdatesMsg=A module caused an error listening to HashDbManager updates. See log to determine which module. Some data could be incomplete. HashDbManager.replacingDuplicateHashsetNameMsg=Duplicate hash set name {0} found.\nReplacing with {1}. @@ -164,18 +161,15 @@ HashDbSearchManager.MD5HashSearch=MD5 Hash Search HashDbSearchManager.noResultsFoundMsg=No results were found. HashDbSearchPanel.titleText.ingestOngoing=Ingest is ongoing; this service will be unavailable until it finishes. HashDbSearchPanel.noFilesHaveMD5HashMsg=No files currently have an MD5 hash. -HashDbSearchPanel.errorText.noHashesAddedMsg=Error\: No hashes have been added. -HashDbSearchPanel.errorText.hashAlreadyAddedMsg=Error\: Hash has already been added. -HashDbSearchPanel.errorText.invalidMD5HashMsg=Error\: That is not a valid MD5 hash. +HashDbSearchPanel.errorText.noHashesAddedMsg=Error: No hashes have been added. +HashDbSearchPanel.errorText.hashAlreadyAddedMsg=Error: Hash has already been added. +HashDbSearchPanel.errorText.invalidMD5HashMsg=Error: That is not a valid MD5 hash. HashDbSearchThread.progress.cancellingSearch={0} (Cancelling...) HashDbSearchThread.name.searching=Searching HashDbSearchThread.noMoreFilesWithMD5Msg=No other files with the same MD5 hash were found. ModalNoButtons.indexingDbsTitle=Indexing hash sets ModalNoButtons.indexingDbTitle=Indexing hash set -ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \n\ -The generated index will be left unusable. If you choose to continue,\n\ - please delete the corresponding -md5.idx file in the hash folder.\n\ - Exit indexing? +ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \nThe generated index will be left unusable. If you choose to continue,\nplease delete the corresponding -md5.idx file in the hash folder.\nExit indexing? ModalNoButtons.dlgTitle.unfinishedIndexing=Unfinished Indexing ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 hash set ModalNoButtons.indexThese.currentlyIndexing1OfNDbs=Currently indexing 1 of {0} @@ -212,7 +206,7 @@ HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=Select notable hash sets AddContentToHashDbAction.addFilesToHashSet.files=files AddContentToHashDbAction.addFilesToHashSet.file=file HashDbManager.errCreatingIndex.title=Error creating index -HashDbManager.errCreatingIndex.msg=Error creating index\: {0} +HashDbManager.errCreatingIndex.msg=Error creating index: {0} HashLookupModuleFactory.getIngestJobSettingsPanel.exception.msg=Expected settings argument to be instanceof HashLookupModuleSettings HashLookupModuleFactory.createFileIngestModule.exception.msg=Expected settings argument to be instanceof HashLookupModuleSettings HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.toolTipText=Calculate MD5 even if no hash set is selected diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED index 7ca4901b1b..ea423b415d 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED @@ -81,8 +81,8 @@ FilesSetRulePanel.nameTextField.text= FilesSetRulePanel.ruleNameLabel.text=Rule Name (Optional): FilesSetRulePanel.messages.emptyNameCondition=You must specify a name pattern for this rule. FilesSetRulePanel.messages.invalidNameRegex=The name regular expression is not valid:\n\n{0} -FilesSetRulePanel.messages.invalidCharInName=The name cannot contain \\, /, :, *, ?, \", <, or > unless it is a regular expression. -FilesSetRulePanel.messages.invalidCharInPath=The path cannot contain \\, :, *, ?, \", <, or > unless it is a regular expression. +FilesSetRulePanel.messages.invalidCharInName=The name cannot contain \\, /, :, *, ?, ", <, or > unless it is a regular expression. +FilesSetRulePanel.messages.invalidCharInPath=The path cannot contain \\, :, *, ?, ", <, or > unless it is a regular expression. FilesSetRulePanel.messages.invalidPathRegex=The path regular expression is not valid:\n\n{0} FilesSetDefsPanel.doFileSetsDialog.duplicateRuleSet.text=Rule set with name {0} already exists. FilesSetRulePanel.pathSeparatorInfoLabel.text=Use / as path separator diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties-MERGED index eafb65f2f3..2dc971a40d 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties-MERGED @@ -13,17 +13,17 @@ PhotoRecIngestModule.PermissionsNotSufficient=Insufficient permissions accessing PhotoRecIngestModule.PermissionsNotSufficientSeeReference=See 'Shared Drive Authentication' in Autopsy help. PhotoRecIngestModule.processTerminated=PhotoRec Carver ingest module was terminated due to exceeding max allowable run time when scanning PhotoRecIngestModule.moduleError=PhotoRec Carver Module Error -PhotoRecIngestModule.UnableToCarve=Unable to carve file\: {0} +PhotoRecIngestModule.UnableToCarve=Unable to carve file: {0} PhotoRecIngestModule.NotEnoughDiskSpace=Not enough disk space to save unallocated file. Carving will be skipped. -PhotoRecIngestModule.complete.numberOfCarved=Number of Files Carved\: -PhotoRecIngestModule.complete.totalWritetime=Total Time To Write To Disk\: -PhotoRecIngestModule.complete.totalParsetime=Total Parsing Time\: +PhotoRecIngestModule.complete.numberOfCarved=Number of Files Carved: +PhotoRecIngestModule.complete.totalWritetime=Total Time To Write To Disk: +PhotoRecIngestModule.complete.totalParsetime=Total Parsing Time: PhotoRecIngestModule.complete.photoRecResults=PhotoRec Results PhotoRecIngestModule.NotEnoughDiskSpace.detail.msg=PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space. PhotoRecIngestModule.cancelledByUser=PhotoRec cancelled by user. -PhotoRecIngestModule.error.exitValue=PhotoRec carver returned error exit value \= {0} when scanning {1} +PhotoRecIngestModule.error.exitValue=PhotoRec carver returned error exit value = {0} when scanning {1} PhotoRecIngestModule.error.msg=Error processing {0} with PhotoRec carver. -PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving\: +PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving: PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings PhotoRecCarverIngestJobSettingsPanel.keepCorruptedFilesCheckbox.text=Keep corrupted files unallocatedSpaceProcessingSettingsError.message=The selected file ingest filter ignores unallocated space. This module carves unallocated space. Please choose a filter which does not ignore unallocated space or disable this module. diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED index 91d7bef3ea..6452656c0a 100755 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED @@ -81,7 +81,7 @@ FileReportDataTypes.path.text=Full Path FileReportText.getName.text=Files - Text FileReportText.getDesc.text=A tab delimited text file containing information about individual files in the case. ReportBodyFile.progress.querying=Querying files... -ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed\! +ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed! ReportBodyFile.progress.loading=Loading files... ReportBodyFile.progress.processing=Now processing {0}... ReportBodyFile.getName.text=TSK Body File @@ -94,27 +94,27 @@ ReportKML.getDesc.text=KML format report with coordinates for relevant files. Th ReportKML.getFilePath.text=ReportKML.kml ReportBranding.defaultReportTitle.text=Autopsy Forensic Report ReportBranding.defaultReportFooter.text=Powered by Autopsy Open Source Digital Forensics Platform - www.sleuthkit.org -ReportExcel.numAartifacts.text=Number of artifacts\: +ReportExcel.numAartifacts.text=Number of artifacts: ReportExcel.getName.text=Excel Report ReportExcel.getDesc.text=A report about results and tagged items in Excel (XLS) format. ReportExcel.sheetName.text=Summary ReportExcel.cellVal.summary=Summary -ReportExcel.cellVal.caseName=Case Name\: -ReportExcel.cellVal.caseNum=Case Number\: -ReportExcel.cellVal.examiner=Examiner\: -ReportExcel.cellVal.numImages=Number of Images\: +ReportExcel.cellVal.caseName=Case Name: +ReportExcel.cellVal.caseNum=Case Number: +ReportExcel.cellVal.examiner=Examiner: +ReportExcel.cellVal.numImages=Number of Images: ReportGenerationPanel.confDlg.sureToClose.msg=Are you sure you'd like to close the dialog?\nAll reports will be canceled. ReportGenerationPanel.confDlg.title.closing=Closing ReportGenerationPanel.confDlg.cancelReport.msg=Are you sure you'd like to cancel the report? ReportGenerator.displayProgress.title.text=Report Generation Progress... ReportGenerator.progress.queryingDb.text=Querying database... ReportGenerator.progress.processingFile.text=Now processing {0} -ReportGenerator.artifactTable.taggedResults.text=Contains results that were tagged with one of the following\: +ReportGenerator.artifactTable.taggedResults.text=Contains results that were tagged with one of the following: ReportGenerator.progress.processing=Now processing {0}... ReportGenerator.msgShow.skippingArtType.title=Skipping artifact type {0} in reports ReportGenerator.msgShow.skippingArtType.msg=Unknown columns to report on -ReportGenerator.makeContTagTab.taggedFiles.msg=Contains files that were tagged with one of the following\: -ReportGenerator.makeBbArtTagTab.taggedRes.msg=This report only includes results tagged with\: +ReportGenerator.makeContTagTab.taggedFiles.msg=Contains files that were tagged with one of the following: +ReportGenerator.makeBbArtTagTab.taggedRes.msg=This report only includes results tagged with: ReportGenerator.tagTable.header.resultType=Result Type ReportGenerator.tagTable.header.tag=Tag ReportGenerator.tagTable.header.comment=Comment @@ -218,30 +218,30 @@ ReportGenerator.errors.reportErrorTitle=Error generating report ReportGenerator.errors.reportErrorText=Error generating report: ReportHTML.addThumbRows.dataType.title=Tagged Images - {0} ReportHTML.addThumbRows.dataType.msg=Tagged Results and Contents that contain images. -ReportHTML.thumbLink.tags=Tags\: +ReportHTML.thumbLink.tags=Tags: ReportHTML.getName.text=HTML Report ReportHTML.getDesc.text=A report about results and tagged items in HTML format. ReportHTML.writeIndex.title=for case {0} ReportHTML.writeIndex.noFrames.msg=Your browser is not compatible with our frame setup. -ReportHTML.writeIndex.noFrames.seeNav=Please see the navigation page for artifact links, -ReportHTML.writeIndex.seeSum=and the summary page for a case summary. +ReportHTML.writeIndex.noFrames.seeNav=Please see the navigation page for artifact links, +ReportHTML.writeIndex.seeSum=and the summary page for a case summary. ReportHTML.writeNav.title=Report Navigation ReportHTML.writeNav.h1=Report Navigation ReportHTML.writeNav.summary=Case Summary ReportHTML.writeSum.title=Case Summary -ReportHTML.writeSum.warningMsg=Warning, this report was run before ingest services completed\! +ReportHTML.writeSum.warningMsg=Warning, this report was run before ingest services completed! # # autopsy/test/scripts/regression.py._html_report_diff() uses reportGenOn.text, caseName, caseNum, # examiner as a regex signature to skip report.html and summary.html # ReportHTML.writeSum.reportGenOn.text=HTML Report Generated on {0} -ReportHTML.writeSum.imageInfoHeading=

          Image Information\:

          -ReportHTML.writeSum.softwareInfoHeading=

          Software Information\:

          -ReportHTML.writeSum.ingestHistoryHeading=

          Ingest History\:

          -ReportHTML.writeSum.modulesEnabledHeading=Enabled Modules\: -ReportHTML.writeSum.autopsyVersion=Autopsy Version\: -ReportHTML.writeSum.timezone=Timezone\: -ReportHTML.writeSum.path=Path\: +ReportHTML.writeSum.imageInfoHeading=

          Image Information:

          +ReportHTML.writeSum.softwareInfoHeading=

          Software Information:

          +ReportHTML.writeSum.ingestHistoryHeading=

          Ingest History:

          +ReportHTML.writeSum.modulesEnabledHeading=Enabled Modules: +ReportHTML.writeSum.autopsyVersion=Autopsy Version: +ReportHTML.writeSum.timezone=Timezone: +ReportHTML.writeSum.path=Path: ReportProgressPanel.progress.queuing=Queuing... ReportProgressPanel.initPathLabel.noFile=No report file ReportProgressPanel.start.cancelButton.text=Cancel @@ -273,7 +273,7 @@ ReportGenerator.artTableColHdr.processorArchitecture.text=Processor Architecture ReportGenerator.artTableColHdr.osName.text=Operating System Name ReportGenerator.artTableColHdr.osInstallDate.text=Install Date ReportGenerator.errList.failedMakeRptFolder=Failed to make report folder, may be unable to generate reports. -ReportGenerator.notifyErr.errsDuringRptGen=Errors during report generation\: +ReportGenerator.notifyErr.errsDuringRptGen=Errors during report generation: ReportGenerator.errList.failedGetContentTags=Failed to get content tags. ReportGenerator.errList.failedGetBBArtifactTags=Failed to get blackboard artifact tags. ReportGenerator.errList.errGetContentFromBBArtifact=Error while getting content from a blackboard artifact to report on. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties-MERGED index a495fd5daf..e6b3e114cb 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties-MERGED @@ -28,7 +28,7 @@ Timeline.frameName.text={0} - Autopsy Timeline Timeline.resultsPanel.title=Timeline Results Timeline.runJavaFxThread.progress.creating=Creating timeline . . . Timeline.zoomOutButton.text=Zoom Out -Timeline.goToButton.text=Go To\: +Timeline.goToButton.text=Go To: Timeline.yearBarChart.x.years=Years Timeline.resultPanel.loading=Loading... diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties-MERGED index 3255e82f2b..ed3e6724d8 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties-MERGED @@ -1,4 +1,4 @@ -/* +/*= * Autopsy Forensic Browser * * Copyright 2013-15 Basis Technology Corp. @@ -14,8 +14,8 @@ * 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. - */ +*=limitations under the License. +*/= AbstractTimelineChart.defaultTooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions. HistoryToolBar.historyLabel.text=History diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java index d744abe8b4..45af5c3025 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java @@ -26,7 +26,6 @@ import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; /** * A panel which displays an outline view with all auto ingest nodes and their diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index c993daa34e..56e2cb3015 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -33,6 +33,7 @@ import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.experimental.autoingest.Manifest; import org.sleuthkit.autopsy.ingest.DataSourceIngestJob.Snapshot; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager.IngestThreadActivitySnapshot; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 00a2bc4d37..1cde409622 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import javax.swing.Action; @@ -35,6 +36,13 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.Stage; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAdminActions; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboard; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents; +import org.sleuthkit.autopsy.experimental.autoingest.Bundle; +import org.sleuthkit.autopsy.experimental.autoingest.Manifest; +import org.sleuthkit.autopsy.experimental.autoingest.PrioritizationAction; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; import org.sleuthkit.autopsy.ingest.DataSourceIngestJob; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 395b8ad4cb..5cccbea1d7 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -94,10 +94,6 @@ AutoIngestControlPanel.runningTable.toolTipText=The Running table displays the c AutoIngestControlPanel.SharedConfigurationDisabled=Shared configuration disabled AutoIngestControlPanel.ShowLogFailed.Message=Case log file does not exist AutoIngestControlPanel.ShowLogFailed.Title=Unable to display case log -# {0} - case db status -# {1} - search svc Status -# {2} - coord svc Status -# {3} - msg broker status AutoIngestControlPanel.tbServicesStatusMessage.Message=Case databases {0}, keyword search {1}, coordination {2}, messaging {3} AutoIngestControlPanel.tbServicesStatusMessage.Message.Down=down AutoIngestControlPanel.tbServicesStatusMessage.Message.Unknown=unknown @@ -144,19 +140,45 @@ AutoIngestJobsNode.status.text=Status AutoIngestJobsPanel.waitNode.text=Please Wait... AutoIngestMetricsDialog.initReportText=Select a date above and click the 'Generate Metrics Report' button to generate\na metrics report. AutoIngestMetricsDialog.title.text=Auto Ingest Metrics -CaseDeletionTask.progress.connectingToCoordSvc=Connecting to coordination service (ZooKeeper)... +CaseDeletionTask.error.failedToLockInputDirs=Failed to exclusively lock the input directory {0}. +CaseDeletionTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock for case... +CaseDeletionTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock for case... +CaseDeletionTask.progress.connectingToCoordSvc=Connecting to the coordination service (ZooKeeper)... # {0} - case name -CaseDeletionTask.progress.deletingCase=Deleting {0}... +CaseDeletionTask.progress.deletingCase=Deleting case {0}... +# {0} - input directory name +CaseDeletionTask.progress.deletingInputDir=Deleting input directory {0}... # {0} - exception message -CaseDeletionTask.progress.errorConnectingToCoordSvc=Failed to connect to coordination service (see log for details): {0} +CaseDeletionTask.progress.errorConnectingToCoordSvc=Failed to connect to the coordination service (see log for details): {0} +# {0} - input directory name +CaseDeletionTask.progress.errorDeletingInputDir=An error occurred deleting the input directory at {0} # {0} - exception message -CaseDeletionTask.progress.errorDeletingOutput=The following error occurred deleting the case output (see log for details): {0} +CaseDeletionTask.progress.errorDeletingOutput=An error occurred deleting the case output (see log for details): {0} +CaseDeletionTask.progress.errorLockingCase=An error occurred while trying to exclusively lock the case (see log for details): {0}. # {0} - exception message -CaseDeletionTask.progress.errorLockingCase=A coordination service error occurred while trying to lock the case (see log for details): {0}. +CaseDeletionTask.progress.errorLockingCaseDir=An error occurred while trying to acquire the exclusive directory lock for the case (see log for details): {0}. +# {0} - exception message +CaseDeletionTask.progress.errorLockingCaseName=An error occurred while trying to acquire the exclusive name lock for the case (see log for details): {0}. +# {0} - input directory name +# {1} - exception message +CaseDeletionTask.progress.errorlockingInputDir=An error occurred Acquiring the exclusive lock on input directory {0} (see log for details): {1} +# {0} - input directory name +# {1} - exception message +CaseDeletionTask.progress.errorReleasingInputDirLock=An error occurred releasing the exclusive lock on input directory {0} (see log for details): {1} +# {0} - exception message +CaseDeletionTask.progress.errorReleasingInputLock=An error occurred releasing the input directory lock (see log for details): {0} CaseDeletionTask.progress.failedToLockCase=Failed to exclusively lock the case, it may be in use, did not delete. +# {0} - input directory name +CaseDeletionTask.progress.failedToLockInputDir=Failed to exclusively lock the input directory {0}. +CaseDeletionTask.progress.failedToLockInputDirs=Failed to exclusively lock the input directory {0}. CaseDeletionTask.progress.gettingJobNodeData=Getting coordination service node data for the auto ingest jobs... -CaseDeletionTask.progress.lockingCase=Acquiring exclusive lock for case... +CaseDeletionTask.progress.lockingCase=Acquiring exclusive locks for case... +# {0} - input directory name +CaseDeletionTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0}... +CaseDeletionTask.progress.lockingInputDirs=Acquiring exclusive locks on the input directories... CaseDeletionTask.progress.lockingJobs=Acquiring exclusive locks on all of the auto ingest job directories... +# {0} - input directory name +CaseDeletionTask.progress.releasingInputDirLock=Releasing the exclusive lock on input directory {0}... ConfirmationDialog.DoNotDelete=Do not delete ConfirmationDialog.Delete=Permanently delete ConfirmationDialog.DeleteAreYouSure=The entire case will be removed. Are you sure you want to delete case @@ -193,7 +215,6 @@ HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-use OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File -# {0} - caseErrorMessage OpenCaseAction.errorMsg=Failed to open case: {0} OpenCaseAction.menuItemText=Open OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change.\n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java index eb970d30f9..4bc6dd2bf8 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDeletionTask.java @@ -26,27 +26,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.progress.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException; /** - * A task that deletes one or more cases, entirely or in part, depending on the - * use case. + * A task that deletes one or more cases, entirely or in part. */ final class CaseDeletionTask implements Runnable { private static final Logger logger = Logger.getLogger(CaseDeletionTask.class.getName()); - private final List caseNodeDataList; + private final List casesToDelete; private final CaseDeletionTaskType deletionTaskType; private final ProgressIndicator progress; private CoordinationService coordinationService; @@ -83,38 +83,37 @@ final class CaseDeletionTask implements Runnable { } /** - * Constructs a task that deletes one or more cases, entirely or in part, - * depending on the use case. + * Constructs a task that deletes one or more cases, entirely or in part. * - * @param caseNodeDataList The coordination service node data for the cases - * to delete. + * @param casesToDelete The case directory lock coordination service node + * data for the cases to delete. * @param deletionTaskType The extent of the deletion to attempt for each * case. * @param progress A progress indicator. */ - CaseDeletionTask(List caseNodeDataList, CaseDeletionTaskType deletionTaskType, ProgressIndicator progress) { - this.caseNodeDataList = caseNodeDataList; + CaseDeletionTask(List casesToDelete, CaseDeletionTaskType deletionTaskType, ProgressIndicator progress) { + this.casesToDelete = casesToDelete; this.deletionTaskType = deletionTaskType; this.progress = progress; } @Override @NbBundle.Messages({ - "CaseDeletionTask.progress.connectingToCoordSvc=Connecting to coordination service (ZooKeeper)...", - "# {0} - exception message", "CaseDeletionTask.progress.errorConnectingToCoordSvc=Failed to connect to coordination service (see log for details): {0}", - "# {0} - case name", "CaseDeletionTask.progress.deletingCase=Deleting {0}...", - "CaseDeletionTask.progress.lockingCase=Acquiring exclusive lock for case...", - "# {0} - exception message", "CaseDeletionTask.progress.errorLockingCase=A coordination service error occurred while trying to lock the case (see log for details): {0}.", + "CaseDeletionTask.progress.connectingToCoordSvc=Connecting to the coordination service (ZooKeeper)...", + "# {0} - exception message", "CaseDeletionTask.progress.errorConnectingToCoordSvc=Failed to connect to the coordination service (see log for details): {0}", + "# {0} - case name", "CaseDeletionTask.progress.deletingCase=Deleting case {0}...", + "CaseDeletionTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock for case...", + "# {0} - exception message", "CaseDeletionTask.progress.errorLockingCaseName=An error occurred while trying to acquire the exclusive name lock for the case (see log for details): {0}.", + "CaseDeletionTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock for case...", + "# {0} - exception message", "CaseDeletionTask.progress.errorLockingCaseDir=An error occurred while trying to acquire the exclusive directory lock for the case (see log for details): {0}.", "CaseDeletionTask.progress.failedToLockCase=Failed to exclusively lock the case, it may be in use, did not delete.", "CaseDeletionTask.progress.gettingJobNodeData=Getting coordination service node data for the auto ingest jobs...", - "CaseDeletionTask.progress.lockingJobs=Acquiring exclusive locks on all of the auto ingest job directories...", - "# {0} - exception message", "CaseDeletionTask.progress.errorDeletingOutput=The following error occurred deleting the case output (see log for details): {0}",}) + "# {0} - exception message", "CaseDeletionTask.progress.errorReleasingInputLock=An error occurred releasing the input directory lock (see log for details): {0}", + "# {0} - exception message", "CaseDeletionTask.progress.errorDeletingOutput=An error occurred deleting the case output (see log for details): {0}" + }) public void run() { progress.start(Bundle.CaseDeletionTask_progress_connectingToCoordSvc()); try { - /* - * Connect to the coordination service. - */ try { coordinationService = CoordinationService.getInstance(); } catch (CoordinationService.CoordinationServiceException ex) { @@ -123,111 +122,156 @@ final class CaseDeletionTask implements Runnable { return; } - // RJCTODO: Get the auto ingest job nodes and bucket them by case - - for (CaseNodeData caseNodeData : caseNodeDataList) { + for (CaseNodeData caseNodeData : casesToDelete) { progress.progress(Bundle.CaseDeletionTask_progress_deletingCase(caseNodeData.getDisplayName())); - progress.progress(Bundle.CaseDeletionTask_progress_lockingCase()); /* - * Get an exclusive lock on the case. - * - * RJCTODO: Should the case name lock also be obtained and - * deleted? + * Acquire an exclusive case name lock. This will prevent auto + * ingest nodes from attempting to search for the case directory + * before it is deleted. */ - try (CoordinationService.Lock caseLock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseNodeData.getDirectory().toString())) { - if (caseLock == null) { - logger.log(Level.WARNING, String.format("Failed to get exclusive lock on %s, skipping", caseNodeData.getDisplayName())); + progress.progress(Bundle.CaseDeletionTask_progress_acquiringCaseNameLock()); + final String caseNameLockName = TimeStampUtils.removeTimeStamp(caseNodeData.getName()); + try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) { + if (nameLock == null) { + logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case name for %s (%s) in %s, skipping deletion", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); progress.progress(Bundle.CaseDeletionTask_progress_failedToLockCase()); continue; } /* - * Get all of the auto ingest job coordination service nodes for the case. + * Acquire an exclusive case directory lock. This will + * ensure that no other node (host) currently has the case + * open and will prevent another node (host) from trying to + * open the case as it is being deleted. */ - progress.progress(Bundle.CaseDeletionTask_progress_gettingJobNodeData()); - List jobNodeDataList = getJobNodeData(caseNodeData.getName()); - - /* - * Lock all of the auto ingest job directories for the case. - */ - progress.progress(Bundle.CaseDeletionTask_progress_lockingJobs()); - Map jobLocks = getJobLocks(jobNodeDataList); - - for (AutoIngestJobNodeData jobNodeData : jobNodeDataList) { - CoordinationService.Lock jobLock = jobLocks.remove(jobNodeData.getManifestFilePath()); - - if (deletionTaskType == CaseDeletionTaskType.INPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { - deleteAutoIngestNodeDirectory(jobNodeData); + progress.progress(Bundle.CaseDeletionTask_progress_acquiringCaseDirLock()); + try (CoordinationService.Lock caseLock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseNodeData.getDirectory().toString())) { + if (caseLock == null) { + logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case directory for %s (%s) in %s, skipping deletion", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + progress.progress(Bundle.CaseDeletionTask_progress_failedToLockCase()); + continue; } - jobLock.release(); - - if (deletionTaskType == CaseDeletionTaskType.OUTPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { - coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, jobNodeData.getManifestFilePath().toString()); - } - - } - - if (deletionTaskType == CaseDeletionTaskType.OUTPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { - // RJCTODO: Progress message - String metadataFilePath = null; - File caseDirectory = caseNodeData.getDirectory().toFile(); - File[] filesInDirectory = caseDirectory.listFiles(); - if (filesInDirectory != null) { - for (File file : filesInDirectory) { - if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { - metadataFilePath = file.getPath(); - } - } - } - - if (metadataFilePath == null) { - continue; // RJCTODO: Or blow away the directory? - } - - try { - Case.deleteCase(new CaseMetadata(Paths.get(metadataFilePath)), progress); - } catch (CaseMetadata.CaseMetadataException | CaseActionException ex) { - // RJCTODO: - } + // RJCTODO: Get all locks: case name, case directory, input dirs + // Delete input folders, if deleting, record success or failure (second set of bits for partial success?) + // Delete output, if deleting, record success or failure + // Delete case locks, if deleting + // Delete input locks, if deleting - this will allow reprocessing to start + deleteCaseInput(caseNodeData); + deleteCaseOutput(caseNodeData); +// releaseInputDirectoryLocks(); + + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error acquiring the case directory lock for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); + progress.progress(Bundle.CaseDeletionTask_progress_errorLockingCaseName(ex.getMessage())); } } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error attempting to acquire exclusive case directory lock for %s", caseNodeData.getName()), ex); - progress.progress(Bundle.CaseDeletionTask_progress_errorLockingCase(ex.getMessage())); + logger.log(Level.SEVERE, String.format("Error acquiring the case name lock for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); + progress.progress(Bundle.CaseDeletionTask_progress_errorLockingCaseDir(ex.getMessage())); } } } finally { progress.finish(); } + + } + + private void deleteCaseInput(CaseNodeData caseNodeData) throws CoordinationServiceException { + boolean errorsOccurred = false; + + /* + * Although it is tempting to do this operation for all of the cases in + * one go, a considerable amount of time may be required to delete each + * case, so the decision has been made to go case by case. + */ + progress.progress(Bundle.CaseDeletionTask_progress_gettingJobNodeData()); + List jobNodeDataList = getAutoIngestJobNodeData(caseNodeData.getName()); + + /* + * If deleting input directories only or entire cases, acquire exclusive + * auto ingest job (manifest file) locks and delete the input + * directories. + */ + if (deletionTaskType == CaseDeletionTaskType.INPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { + progress.progress(Bundle.CaseDeletionTask_progress_lockingInputDirs()); + Map jobLocks = getInputDirectoryLocks(caseNodeData, jobNodeDataList); + for (AutoIngestJobNodeData jobNodeData : jobNodeDataList) { + deleteInputDirectory(caseNodeData, jobNodeData); + CoordinationService.Lock jobLock = jobLocks.remove(jobNodeData.getManifestFilePath()); + } + } + + /** + * + */ + if (deletionTaskType == CaseDeletionTaskType.OUTPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { + for (AutoIngestJobNodeData jobNodeData : jobNodeDataList) { + coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, jobNodeData.getManifestFilePath().toString()); + } + } + } + + private void deleteCaseOutput(CaseNodeData caseNodeData) { + if (deletionTaskType == CaseDeletionTaskType.OUTPUT_ONLY || deletionTaskType == CaseDeletionTaskType.FULL) { + // RJCTODO: Progress message + String metadataFilePath = null; + final File caseDirectory = caseNodeData.getDirectory().toFile(); + final File[] filesInDirectory = caseDirectory.listFiles(); + if (filesInDirectory != null) { + for (File file : filesInDirectory) { + if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { + metadataFilePath = file.getPath(); + } + } + } + + if (metadataFilePath != null) { + try { + Case.deleteCase(new CaseMetadata(Paths.get(metadataFilePath)), false, progress); + } catch (CaseMetadata.CaseMetadataException | CaseActionException ex) { + // RJCTODO: + } + } else { + // RJCTODO: + } + } } /** - * RJCTODO + * Fetches all of the auto ingest job (manifest file) lock coordination + * service node data for a case. * - * @param caseName + * @param caseName The name of the case. * - * @return + * @return A list of auto ingest job (manifest file) lock node data for the + * case. * - * @throws CoordinationServiceException If there is a + * @throws CoordinationServiceException If there is an error getting a list + * of the auto ingest job (manifest + * file) lock nodes from the + * coordination service. */ @NbBundle.Messages({ "AutoIngestJobsDeletionTask.error.failedToGetJobNodes=Failed to get auto ingest job node data from coordination service." }) - private List getJobNodeData(String caseName) throws CoordinationServiceException { - final List nodes; - nodes = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); + private List getAutoIngestJobNodeData(String caseName) throws CoordinationServiceException { + /* + * Although it is tempting to save time by not doing this operation for + * each case separately, a considerable amount of time may be required + * to delete each case, so the decision has been made to get a "fresh" + * list when each case gets its turn on the chopping back. + */ + final List nodes = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); final List jobNodeData = new ArrayList<>(); for (String nodeName : nodes) { try { byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); if (nodeBytes == null || nodeBytes.length <= 0) { - // Empty node data, indicate - // RJCTODO: Delete empty node, indicate success or failure - //coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, nodeName); + // RJCTODO: Log and indicate, delete node? continue; } AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes); @@ -235,40 +279,102 @@ final class CaseDeletionTask implements Runnable { jobNodeData.add(nodeData); } } catch (CoordinationService.CoordinationServiceException | InterruptedException | InvalidDataException ex) { - // RJCTODO: Failed to get node data for a node, indicate logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex); + // RJCTODO: Indicate } } return jobNodeData; } /** - * RJCTODO - * @param jobNodeData - * @return + * Acquires either all or none of the input directory locks for a case. + * + * @param caseNodeData The case node data from the case + * directory lock node for the case. + * @param autoIngestJobNodeDataList The auto ingest job node data from the + * input directory lock nodes for the case. + * + * @return A mapping of manifest file paths to input directory lcoks for all + * input directories for the case; will be empty if all of the locks + * could not be obtained. */ - private Map getJobLocks(List jobNodeData) { - Map jobLocks = new HashMap<>(); - for (AutoIngestJobNodeData nodeData : jobNodeData) { + @NbBundle.Messages({ + "# {0} - input directory name", "CaseDeletionTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0}...", + "# {0} - input directory name", "CaseDeletionTask.progress.failedToLockInputDir=Failed to exclusively lock the input directory {0}.", + "# {0} - input directory name", "# {1} - exception message", "CaseDeletionTask.progress.errorlockingInputDir=An error occurred Acquiring the exclusive lock on input directory {0} (see log for details): {1}" + }) + private Map getInputDirectoryLocks(CaseNodeData caseNodeData, List autoIngestJobNodeDataList) { + final Map inputDirLocks = new HashMap<>(); + for (AutoIngestJobNodeData autoIngestJobNodeData : autoIngestJobNodeDataList) { + final Path inputDirPath = autoIngestJobNodeData.getManifestFilePath().getParent(); try { - CoordinationService.Lock lock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, nodeData.getManifestFilePath().toString()); - if (null != lock) { - jobLocks.put(nodeData.getManifestFilePath(), lock); + progress.progress(Bundle.CaseDeletionTask_progress_lockingInputDir(inputDirPath)); + final CoordinationService.Lock inputDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, autoIngestJobNodeData.getManifestFilePath().toString()); + if (null != inputDirLock) { + inputDirLocks.put(autoIngestJobNodeData.getManifestFilePath(), inputDirLock); } else { - // RJCTODO: release the locks already obtained and - // throw an exception indicating all locks cannot be obtained. + logger.log(Level.SEVERE, String.format("Failed to exclusively lock the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + progress.progress(Bundle.CaseDeletionTask_progress_failedToLockInputDir(inputDirPath)); + releaseInputDirectoryLocks(caseNodeData, inputDirLocks); + inputDirLocks.clear(); } } catch (CoordinationService.CoordinationServiceException ex) { - // RJCTODO: Release the locks already obtained and rethrow + logger.log(Level.SEVERE, String.format("Error exclusively locking the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + progress.progress(Bundle.CaseDeletionTask_progress_errorlockingInputDir(inputDirPath, ex.getMessage())); + releaseInputDirectoryLocks(caseNodeData, inputDirLocks); + inputDirLocks.clear(); } } - return jobLocks; + return inputDirLocks; } - private void deleteAutoIngestNodeDirectory(AutoIngestJobNodeData nodeData) { - // RJCTODO: Need to strip of file name - if (FileUtil.deleteDir(new File(nodeData.getManifestFilePath().toString()))) { - // RJCTODO: + /** + * Deletes a case input directory. + * + * @param caseNodeData The case node data from the case + * directory lock node for the case. + * @param autoIngestJobNodeDataList The auto ingest job node data from the + * input directory (manifest file path) + * lock node for the input directory.. + */ + @NbBundle.Messages({ + "# {0} - input directory name", "CaseDeletionTask.progress.deletingInputDir=Deleting input directory {0}...", + "# {0} - input directory name", "CaseDeletionTask.progress.errorDeletingInputDir=An error occurred deleting the input directory at {0}" + }) + private void deleteInputDirectory(CaseNodeData caseNodeData, AutoIngestJobNodeData autoIngestJobNodeData) { + final Path inputDirPath = autoIngestJobNodeData.getManifestFilePath().getParent(); + progress.progress(Bundle.CaseDeletionTask_progress_deletingInputDir(inputDirPath)); + if (FileUtil.deleteDir(new File(inputDirPath.toString()))) { + logger.log(Level.SEVERE, String.format("Failed to delete the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + progress.progress(Bundle.CaseDeletionTask_progress_errorDeletingInputDir(inputDirPath)); + } + } + + /** + * Releases the coordination service locks in a mapping of manifest file + * paths to input directory locks for a case. + * + * @param caseNodeData The case node data from the case directory lock node + * for the case. + * @param inputDirLocks The mapping of manifest files paths to to input + * directory locks for the case. + */ + @NbBundle.Messages({ + "# {0} - input directory name", "CaseDeletionTask.progress.releasingInputDirLock=Releasing the exclusive lock on input directory {0}...", + "# {0} - input directory name", "# {1} - exception message", "CaseDeletionTask.progress.errorReleasingInputDirLock=An error occurred releasing the exclusive lock on input directory {0} (see log for details): {1}" + }) + private void releaseInputDirectoryLocks(CaseNodeData caseNodeData, Map inputDirLocks) { + for (Map.Entry entry : inputDirLocks.entrySet()) { + final Path manifestFilePath = entry.getKey(); + final Path inputDirPath = manifestFilePath.getParent(); + final CoordinationService.Lock inputDirLock = entry.getValue(); + try { + progress.progress(Bundle.CaseDeletionTask_progress_releasingInputDirLock(inputDirPath)); + inputDirLock.release(); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to release exclusive lock on the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + progress.progress(Bundle.CaseDeletionTask_progress_errorReleasingInputDirLock(inputDirPath, ex.getMessage())); + } } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index f4febc1d7c..61c0d2d2c7 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -34,7 +34,7 @@ KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get sc KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found. KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\n\The module indexes files found in the disk image at ingest time.\n\It then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 62bacbc9fc..7e6984368f 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -37,7 +37,7 @@ ExtractOs.windowsVolume.label=OS Drive (Windows) ExtractOs.yellowDogLinuxOs.label=Linux (Yellow Dog) ExtractOs.yellowDogLinuxVolume.label=OS Drive (Linux Yellow Dog) OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome @@ -135,7 +135,7 @@ SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\n\count: {2}\nSplit Tokens: \n{3} +SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\ncount: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity UsbDeviceIdMapper.parseAndLookup.text=Product: {0} From 710cfc243f52db78c107f32bf184e61fe64a1cdc Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 6 Mar 2019 14:11:31 -0500 Subject: [PATCH 04/32] Interim check in of case deletion to allow develop branch merge --- .../MultiUserCaseBrowserCustomizer.java | 1 + ...ndicator.java => AppFrameProgressBar.java} | 64 ++++++++++++---- .../autopsy/progress/Bundle.properties-MERGED | 1 + .../ModalDialogProgressIndicator.java | 1 + .../autopsy/progress/ProgressIndicator.java | 23 ++++-- ...er.java => AutoIngestDashboardLogger.java} | 15 ++-- .../autoingest/Bundle.properties-MERGED | 73 +++++++++--------- .../autoingest/CasesDashboardCustomizer.java | 16 ++-- .../CasesDashboardTopComponent.java | 8 +- ...Action.java => DeleteCaseInputAction.java} | 38 ++++------ .../DeleteCaseInputAndOutputAction.java | 59 +++++++++++++++ ...java => DeleteCaseInputAndOutputTask.java} | 4 +- ...nputTask.java => DeleteCaseInputTask.java} | 4 +- ...ction.java => DeleteCaseOutputAction.java} | 32 +++----- ...putTask.java => DeleteCaseOutputTask.java} | 4 +- ...eDeletionTask.java => DeleteCaseTask.java} | 74 +++++++++---------- .../autoingest/DeleteCasesAction.java | 71 ------------------ .../ShowCaseDeletionStatusAction.java | 48 ------------ .../recentactivity/Bundle.properties-MERGED | 4 +- 19 files changed, 252 insertions(+), 288 deletions(-) rename Core/src/org/sleuthkit/autopsy/progress/{AppProgressBarProgressIndicator.java => AppFrameProgressBar.java} (52%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{CaseDashboardLogger.java => AutoIngestDashboardLogger.java} (88%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{DeleteCaseInputDirectoriesAction.java => DeleteCaseInputAction.java} (59%) create mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{DeleteAutoIngestCaseTask.java => DeleteCaseInputAndOutputTask.java} (90%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{DeleteAutoIngestCaseInputTask.java => DeleteCaseInputTask.java} (92%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{DeleteCasesForReprocessingAction.java => DeleteCaseOutputAction.java} (62%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{DeleteAutoIngestCaseOutputTask.java => DeleteCaseOutputTask.java} (92%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{AutoIngestCaseDeletionTask.java => DeleteCaseTask.java} (85%) delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCasesAction.java delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ShowCaseDeletionStatusAction.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java index 7653da89db..5cdca90ca8 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java @@ -148,6 +148,7 @@ public interface MultiUserCaseBrowserCustomizer { CREATE_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_createTime()), DIRECTORY(Bundle.MultiUserCaseBrowserCustomizer_column_directory()), LAST_ACCESS_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_lastAccessTime()); + // RJCTODO: Add properties for deleted items flags private final String displayName; diff --git a/Core/src/org/sleuthkit/autopsy/progress/AppProgressBarProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/AppFrameProgressBar.java similarity index 52% rename from Core/src/org/sleuthkit/autopsy/progress/AppProgressBarProgressIndicator.java rename to Core/src/org/sleuthkit/autopsy/progress/AppFrameProgressBar.java index fb9867682b..8f1de8a6e9 100755 --- a/Core/src/org/sleuthkit/autopsy/progress/AppProgressBarProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/progress/AppFrameProgressBar.java @@ -23,61 +23,99 @@ import org.openide.util.Cancellable; /** * A progress indicator that displays progress using a progress bar in the lower - * right hand corner of the application main frame, i.e., a NetBean + * right hand corner of the application main frame, i.e., a NetBeans * ProgressHandle. */ -public final class AppProgressBarProgressIndicator implements ProgressIndicator { +public final class AppFrameProgressBar implements ProgressIndicator { - private final ProgressHandle progressHandle; + private final String displayName; + private Cancellable cancellationBehavior; + private ProgressHandle progressHandle; + private volatile boolean cancelling; /** * Constructs a progress indicator that displays progress using a progress * bar in the lower right hand corner of the application main frame, i.e., a - * NetBean ProgressHandle. + * NetBeans ProgressHandle. + * + * @param displayName The display name for the progress bar (a fixed name + * that appears above the current progress message). */ - public AppProgressBarProgressIndicator(String displayName, Cancellable cancellationAction) { - this.progressHandle = ProgressHandle.createHandle(displayName, cancellationAction); + public AppFrameProgressBar(String displayName) { + this.displayName = displayName; + } + + /** + * Sets the cancellation behavior that should happen when a user clicks on + * the "x" button of the progress bar. + * + * @param cancellationBehavior A org.openide.util.Cancellable that + * implements the desired cancellation behavior. + */ + public void setCancellationBehavior(Cancellable cancellationBehavior) { + this.cancellationBehavior = cancellationBehavior; } @Override public void start(String message, int totalWorkUnits) { + cancelling = false; + this.progressHandle = ProgressHandle.createHandle(displayName, cancellationBehavior); progressHandle.start(totalWorkUnits); progressHandle.progress(message); } @Override public void start(String message) { + cancelling = false; + this.progressHandle = ProgressHandle.createHandle(displayName, cancellationBehavior); progressHandle.start(); progressHandle.progress(message); } @Override public void switchToIndeterminate(String message) { - progressHandle.switchToIndeterminate(); - progressHandle.progress(message); + if (!cancelling) { + progressHandle.switchToIndeterminate(); + progressHandle.progress(message); + } } @Override public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { - progressHandle.switchToDeterminate(totalWorkUnits); - progressHandle.progress(message, workUnitsCompleted); + if (!cancelling) { + progressHandle.switchToDeterminate(totalWorkUnits); + progressHandle.progress(message, workUnitsCompleted); + } } @Override public void progress(String message) { - progressHandle.progress(message); + if (!cancelling) { + progressHandle.progress(message); + } } @Override public void progress(int workUnitsCompleted) { - progressHandle.progress(workUnitsCompleted); + if (!cancelling) { + progressHandle.progress(workUnitsCompleted); + } } @Override public void progress(String message, int workUnitsCompleted) { - progressHandle.progress(message, workUnitsCompleted); + if (!cancelling) { + progressHandle.progress(message, workUnitsCompleted); + } } + @Override + public void setCancelling(String cancellingMessage) { + cancelling = true; + progressHandle.switchToIndeterminate(); + progressHandle.progress(cancellingMessage); + } + @Override public void finish() { progressHandle.finish(); diff --git a/Core/src/org/sleuthkit/autopsy/progress/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/progress/Bundle.properties-MERGED index 7fcb87097c..7fc3a591e1 100755 --- a/Core/src/org/sleuthkit/autopsy/progress/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/progress/Bundle.properties-MERGED @@ -3,3 +3,4 @@ # and open the template in the editor. ProgressPanel.progressMessage.text=Message +TaskCanceller.progress.cancellingMessage=Cancelling... diff --git a/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java index 909c08d29e..8d92e40d70 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java @@ -128,6 +128,7 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * * @param cancellingMessage */ + @Override public synchronized void setCancelling(String cancellingMessage) { cancelling = true; SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java index 8e5b881182..c0637dbc3f 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java @@ -49,7 +49,7 @@ public interface ProgressIndicator { * * @param message The initial progress message. */ - public void switchToIndeterminate(String message); + void switchToIndeterminate(String message); /** * Switches the progress indicator to determinate mode (the total number of @@ -59,14 +59,14 @@ public interface ProgressIndicator { * @param workUnitsCompleted The number of work units completed so far. * @param totalWorkUnits The total number of work units to be completed. */ - public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits); + void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits); /** * Updates the progress indicator with a progress message. * * @param message The progress message. */ - public void progress(String message); + void progress(String message); /** * Updates the progress indicator with the number of work units completed so @@ -75,7 +75,7 @@ public interface ProgressIndicator { * * @param workUnitsCompleted Number of work units completed so far. */ - public void progress(int workUnitsCompleted); + void progress(int workUnitsCompleted); /** * Updates the progress indicator with a progress message and the number of @@ -85,7 +85,20 @@ public interface ProgressIndicator { * @param message The progress message. * @param workUnitsCompleted Number of work units completed so far. */ - public void progress(String message, int workUnitsCompleted); + void progress(String message, int workUnitsCompleted); + + /** + * If the progress indicator supports cancelling the underlying task, sets a + * cancelling message and causes the progress indicator to no longer accept + * updates unless start is called again. + * + * The default implementation assumes that cancelling the underlying task is + * not supported. + * + * @param cancellingMessage The cancelling messages. + */ + default void setCancelling(String cancellingMessage) { + } /** * Finishes the progress indicator when the task is completed. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDashboardLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardLogger.java similarity index 88% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDashboardLogger.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardLogger.java index d21f1a2a2e..fadeda09ec 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseDashboardLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardLogger.java @@ -30,26 +30,25 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** - * A logger for the case dashboard log. + * A logger for the auto ingest dashboard log. */ -final class CaseDashboardLogger { +final class AutoIngestDashboardLogger { private static final int LOG_SIZE = 50000000; // In bytes, zero is unlimited, set to roughly 10mb currently private static final int LOG_FILE_COUNT = 10; - private static final Logger logger = Logger.getLogger("CaseDashboardLogger"); //NON-NLS + private static final Logger logger = Logger.getLogger("AutoIngestDashboardLogger"); //NON-NLS private static final String NEWLINE = System.lineSeparator(); - @GuardedBy("AutoIngestSystemLogger") + @GuardedBy("AutoIngestDashboardLogger") private static boolean configured; /** - * Gets a logger for the auto ingest system log, separate from both the case - * auto ingest log and the application log. + * Gets a logger for the auto ingest dashboard log. * * @return The logger. */ synchronized static Logger getLogger() { if (!configured) { - Path logFilePath = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "var", "log", "case_dashboard.log"); + Path logFilePath = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "var", "log", "auto_ingest_dashboard.log"); //NON-NLS try { FileHandler fileHandler = new FileHandler(logFilePath.toString(), LOG_SIZE, LOG_FILE_COUNT); fileHandler.setEncoding(PlatformUtil.getLogFileEncoding()); @@ -86,7 +85,7 @@ final class CaseDashboardLogger { /** * Prevents instantiation of this utility class. */ - private CaseDashboardLogger() { + private AutoIngestDashboardLogger() { } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index ade4cecdcd..d9bb8ec6bf 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -94,6 +94,10 @@ AutoIngestControlPanel.runningTable.toolTipText=The Running table displays the c AutoIngestControlPanel.SharedConfigurationDisabled=Shared configuration disabled AutoIngestControlPanel.ShowLogFailed.Message=Case log file does not exist AutoIngestControlPanel.ShowLogFailed.Title=Unable to display case log +# {0} - case db status +# {1} - search svc Status +# {2} - coord svc Status +# {3} - msg broker status AutoIngestControlPanel.tbServicesStatusMessage.Message=Case databases {0}, keyword search {1}, coordination {2}, messaging {3} AutoIngestControlPanel.tbServicesStatusMessage.Message.Down=down AutoIngestControlPanel.tbServicesStatusMessage.Message.Unknown=unknown @@ -125,7 +129,6 @@ AutoIngestDashboard.tbServicesStatusMessage.Message.Up=up AutoIngestDashboard.tbServicesStatusMessage.Message.Down=down AutoIngestDashboard.tbServicesStatusMessage.Message.Unknown=unknown AutoIngestDashboardTopComponent.exceptionMessage.failedToCreateDashboard=Failed to create Auto Ingest Dashboard. -AutoIngestJobsDeletionTask.error.failedToGetJobNodes=Failed to get auto ingest job node data from coordination service. AutoIngestJobsNode.caseName.text=Case Name AutoIngestJobsNode.dataSource.text=Data Source AutoIngestJobsNode.hostName.text=Host Name @@ -140,40 +143,6 @@ AutoIngestJobsNode.status.text=Status AutoIngestJobsPanel.waitNode.text=Please Wait... AutoIngestMetricsDialog.initReportText=Select a date above and click the 'Generate Metrics Report' button to generate\na metrics report. AutoIngestMetricsDialog.title.text=Auto Ingest Metrics -CaseDeletionTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock -CaseDeletionTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock -CaseDeletionTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks -CaseDeletionTask.progress.connectingToCoordSvc=Connecting to the coordination service -CaseDeletionTask.progress.deletingCase=Deleting case {0}... -CaseDeletionTask.progress.deletingCaseOutput=Deleting case output -CaseDeletionTask.progress.deletingDirLockNode=Deleting case directory lock node -# {0} - input directory name -CaseDeletionTask.progress.deletingInputDir=Deleting input directory {0} -CaseDeletionTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes -CaseDeletionTask.progress.deletingInputDirs=Deleting input directory -CaseDeletionTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node -CaseDeletionTask.progress.deletingNameLockNode=Deleting case name lock node -CaseDeletionTask.progress.deletingOutput=Deleting case output -CaseDeletionTask.progress.deletingResourcesLockNode=Deleting case resources lock node -CaseDeletionTask.progress.errorConnectingToCoordSvc=Failed to connect to the coordination service (see log for details): {0}. -CaseDeletionTask.progress.errorDeletingInputDir=An error occurred deleting the input directory at {0} -CaseDeletionTask.progress.errorDeletingOutput=An error occurred deleting the case output (see log for details): {0}. -CaseDeletionTask.progress.errorLockingCaseDir=An error occurred while trying to acquire the exclusive directory lock for the case (see log for details): {0}. -CaseDeletionTask.progress.errorLockingCaseName=An error occurred while trying to acquire the exclusive name lock for the case (see log for details): {0}. -CaseDeletionTask.progress.errorlockingInputDir=An error occurred Acquiring the exclusive lock on input directory {0} (see log for details): {1} -CaseDeletionTask.progress.errorReleasingInputDirLock=An error occurred releasing the exclusive lock on input directory {0} (see log for details): {1}. -CaseDeletionTask.progress.errorReleasingInputLock=An error occurred releasing the input directory lock (see log for details): {0}. -CaseDeletionTask.progress.failedToLockCase=Failed to exclusively lock the case, it may be in use, did not delete. -CaseDeletionTask.progress.failedToLockInputDir=Failed to exclusively lock the input directory {0}. -CaseDeletionTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs -CaseDeletionTask.progress.locatingCaseMetadataFile=Locating case metadata file -# {0} - input directory name -CaseDeletionTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0} -CaseDeletionTask.progress.releasingInputDirLock=Releasing the exclusive lock on input directory {0} -CaseDeletionTask.progress.releasingInputDirLocks=Acquiring exclusive input directory locks -# {0} - manifest file path -CaseDeletionTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0} -CaseDeletionTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks ConfirmationDialog.DoNotDelete=Do not delete ConfirmationDialog.Delete=Permanently delete ConfirmationDialog.DeleteAreYouSure=The entire case will be removed. Are you sure you want to delete case @@ -202,14 +171,41 @@ CTL_CasesDashboardAction=Multi-User Cases Dashboard CTL_CasesDashboardTopComponent=Cases DataSourceOnCDriveError.noOpenCase.errMsg=Warning: Exception while getting open case. DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on "C:" drive -DeleteCaseInputDirectoriesAction.menuItemText=Delete Input Directories -DeleteCasesAction.menuItemText=Delete Case and Jobs -DeleteCasesForReprocessingAction.menuItemText=Delete for Reprocessing +DeleteCaseInputAction.menuItemText=Delete Input +DeleteCaseInputAction.progressDisplayName=Delete Input +DeleteCaseInputAction.taskName=input +DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output +DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output +DeleteCaseInputAndOutputAction.taskName=input-and-output +DeleteCaseOutputAction.menuItemText=Delete Output +DeleteCaseOutputAction.progressDisplayName=Delete Output +DeleteCaseOutputAction.taskName=output +DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock +DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock +DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks +DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service +DeleteCaseTask.progress.deletingCaseOutput=Deleting case output +DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock node +# {0} - input directory name +DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0} +DeleteCaseTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes +DeleteCaseTask.progress.deletingInputDirs=Deleting input directory +DeleteCaseTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node +DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node +DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources lock node +DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs +DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file +# {0} - input directory name +DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0} +# {0} - manifest file path +DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0} +DeleteCaseTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz) HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File +# {0} - caseErrorMessage OpenCaseAction.errorMsg=Failed to open case: {0} OpenCaseAction.menuItemText=Open OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change.\n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it. @@ -321,7 +317,6 @@ PrioritizationAction.prioritizeJobAction.error=Failed to prioritize job "%s". PrioritizationAction.prioritizeJobAction.title=Prioritize Job PrioritizedIconCellRenderer.notPrioritized.tooltiptext=This job has not been prioritized. PrioritizedIconCellRenderer.prioritized.tooltiptext=This job has been prioritized. The most recently prioritized job should be processed next. -ShowCaseDeletionStatusAction.menuItemText=Show Deletion Status SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping SingleUserCaseImporter.WillImport=Will import: SingleUserCaseImporter.None=None diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java index 498a4e67ad..7a2444cc32 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java @@ -31,23 +31,26 @@ import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrows */ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer { - private final DeleteCaseInputDirectoriesAction deleteCaseInputAction; - private final DeleteCasesForReprocessingAction deleteCaseOutputAction; - private final DeleteCasesAction deleteCaseAction; + private final DeleteCaseInputAction deleteCaseInputAction; + private final DeleteCaseOutputAction deleteCaseOutputAction; + private final DeleteCaseInputAndOutputAction deleteCaseAction; /** * Constructs a customizer for the multi-user case browser panel used in the * administrative dashboard for auto ingest cases to present a tabular view * of the multi-user cases known to the coordination service. + * + * @param executor An executor for tasks for actions that do work in the + * background. */ CasesDashboardCustomizer() { /* * These actions are shared by all nodes in order to support multiple * selection. */ - deleteCaseInputAction = new DeleteCaseInputDirectoriesAction(); - deleteCaseOutputAction = new DeleteCasesForReprocessingAction(); - deleteCaseAction = new DeleteCasesAction(); + deleteCaseInputAction = new DeleteCaseInputAction(); + deleteCaseOutputAction = new DeleteCaseOutputAction(); + deleteCaseAction = new DeleteCaseInputAndOutputAction(); } @Override @@ -79,7 +82,6 @@ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer { actions.add(deleteCaseInputAction); actions.add(deleteCaseOutputAction); actions.add(deleteCaseAction); - actions.add(new ShowCaseDeletionStatusAction(nodeData)); return actions; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java index a0ac41f8a4..06aebb21dd 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java @@ -18,9 +18,6 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.logging.Level; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; @@ -56,11 +53,9 @@ import org.sleuthkit.autopsy.coreutils.Logger; public final class CasesDashboardTopComponent extends TopComponent implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; - private static final String TASK_THREAD_NAME = "Case-dashboard-task-%d"; private static final Logger logger = Logger.getLogger(CasesDashboardTopComponent.class.getName()); private final ExplorerManager explorerManager; private final MultiUserCasesBrowserPanel caseBrowserPanel; - private final Executor executor; /** * Opens a singleton top component that provides an adminstrative dashboard @@ -68,7 +63,7 @@ public final class CasesDashboardTopComponent extends TopComponent implements Ex * mode" defined by the auto ingest jobs top component. */ // RJCTODO: Consider moving all of the dashboard code into its own - // admindashboards or dashboards package. + // autoingest.dashboard package. public static void openTopComponent() { CasesDashboardTopComponent topComponent = (CasesDashboardTopComponent) WindowManager.getDefault().findTopComponent("CasesDashboardTopComponent"); // NON-NLS if (topComponent == null) { @@ -104,7 +99,6 @@ public final class CasesDashboardTopComponent extends TopComponent implements Ex caseBrowserPanel = new MultiUserCasesBrowserPanel(explorerManager, new CasesDashboardCustomizer()); caseBrowserScrollPane.add(caseBrowserPanel); caseBrowserScrollPane.setViewportView(caseBrowserPanel); - executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(TASK_THREAD_NAME).build()); // RJCTODO: Need shutdown } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputDirectoriesAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java similarity index 59% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputDirectoriesAction.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java index aab830be8f..25bdf612ab 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputDirectoriesAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java @@ -18,13 +18,10 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.Collection; -import javax.swing.AbstractAction; +import java.util.concurrent.ExecutorService; import org.openide.util.NbBundle; -import org.openide.util.Utilities; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.progress.ProgressIndicator; /** * An action that deletes the auto ingest job input directories associated with @@ -32,13 +29,8 @@ import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; * ingest jobs are not deleted. This supports the use case where the directories * may need to be directed to reclaim space, but the option to restore the * directories without having the jobs be reprocessed is retained. - * - * This cases to delete are discovered by querying the actions global context - * lookup for CaseNodeData objects. See - * https://platform.netbeans.org/tutorials/nbm-selection-1.html and - * https://platform.netbeans.org/tutorials/nbm-selection-2.html for details. */ -final class DeleteCaseInputDirectoriesAction extends AbstractAction { +final class DeleteCaseInputAction extends DeleteCaseAction { private static final long serialVersionUID = 1L; @@ -51,26 +43,22 @@ final class DeleteCaseInputDirectoriesAction extends AbstractAction { * reprocessed is retained. */ @NbBundle.Messages({ - "DeleteCaseInputDirectoriesAction.menuItemText=Delete Input Directories" + "DeleteCaseInputAction.menuItemText=Delete Input", + "DeleteCaseInputAction.progressDisplayName=Delete Input", + "DeleteCaseInputAction.taskName=input" }) - DeleteCaseInputDirectoriesAction() { - super(Bundle.DeleteCaseInputDirectoriesAction_menuItemText()); - setEnabled(false); // RJCTODO: Enable when implemented + DeleteCaseInputAction() { + super(Bundle.DeleteCaseInputAction_menuItemText(), Bundle.DeleteCaseInputAction_progressDisplayName(), Bundle.DeleteCaseInputAction_taskName()); } @Override - public void actionPerformed(ActionEvent event) { - final Collection selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class)); -// if (!selectedNodeData.isEmpty()) { -// /* -// * RJCTODO: Create a background task that does the deletion and -// * displays results in a dialog with a scrolling text pane. -// */ -// } + DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + return new DeleteCaseInputTask(caseNodeData, progress); } - + @Override - public DeleteCaseInputDirectoriesAction clone() throws CloneNotSupportedException { + public DeleteCaseInputAction clone() throws CloneNotSupportedException { + super.clone(); throw new CloneNotSupportedException(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java new file mode 100755 index 0000000000..cb92d3b9d6 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java @@ -0,0 +1,59 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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 org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.progress.ProgressIndicator; + +/** + * An action that completely deletes one or more multi-user cases, including any + * associated auto ingest job input directories and all coordination service + * nodes. + */ +final class DeleteCaseInputAndOutputAction extends DeleteCaseAction { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an action that completely deletes one or more multi-user + * cases, including any associated auto ingest job input directories and + * coordination service nodes. + */ + @Messages({ + "DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output", + "DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output", + "DeleteCaseInputAndOutputAction.taskName=input-and-output" + }) + DeleteCaseInputAndOutputAction() { + super(Bundle.DeleteCaseInputAndOutputAction_menuItemText(), Bundle.DeleteCaseInputAndOutputAction_progressDisplayName(), Bundle.DeleteCaseInputAndOutputAction_taskName()); + } + + @Override + DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + return new DeleteCaseInputAndOutputTask(caseNodeData, progress); + } + + @Override + public DeleteCaseInputAndOutputAction clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java similarity index 90% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseTask.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java index 30b45f9d08..bd2c38fa14 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java @@ -24,7 +24,7 @@ import org.sleuthkit.autopsy.progress.ProgressIndicator; /** * A task that deletes a case produced via auto ingest. */ -final class DeleteAutoIngestCaseTask extends AutoIngestCaseDeletionTask { +final class DeleteCaseInputAndOutputTask extends DeleteCaseTask { /** * Constructs a task that deletes a case produced via auto ingest. @@ -33,7 +33,7 @@ final class DeleteAutoIngestCaseTask extends AutoIngestCaseDeletionTask { * data for the case. * @param progress A progress indicator. */ - DeleteAutoIngestCaseTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + DeleteCaseInputAndOutputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { super(caseNodeData, progress); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseInputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java similarity index 92% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseInputTask.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java index cc75ba66a4..0dd1ff7837 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseInputTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java @@ -28,7 +28,7 @@ import org.sleuthkit.autopsy.progress.ProgressIndicator; * retaining the option to restore the input directories, effectively restoring * the case. */ -final class DeleteAutoIngestCaseInputTask extends AutoIngestCaseDeletionTask { +final class DeleteCaseInputTask extends DeleteCaseTask { /** * Constructs a task to delete the auto ingest job input directories for a @@ -41,7 +41,7 @@ final class DeleteAutoIngestCaseInputTask extends AutoIngestCaseDeletionTask { * data for the case. * @param progress A progress indicator. */ - DeleteAutoIngestCaseInputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + DeleteCaseInputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { super(caseNodeData, progress); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCasesForReprocessingAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java similarity index 62% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCasesForReprocessingAction.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index 9a0c4d50fb..b421e74fa5 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCasesForReprocessingAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -25,6 +25,7 @@ import javax.swing.AbstractAction; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.progress.ProgressIndicator; /** * An action that deletes everything except the auto ingest job input @@ -32,13 +33,8 @@ import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; * where a case needs to be reprocessed, so the input directories are not * deleted even though the coordination service nodes for the auto ingest jobs * are deleted. - * - * This cases to delete are discovered by querying the actions global context - * lookup for CaseNodeData objects. See - * https://platform.netbeans.org/tutorials/nbm-selection-1.html and - * https://platform.netbeans.org/tutorials/nbm-selection-2.html for details. */ -final class DeleteCasesForReprocessingAction extends AbstractAction { +final class DeleteCaseOutputAction extends DeleteCaseAction { private static final long serialVersionUID = 1L; @@ -50,26 +46,22 @@ final class DeleteCasesForReprocessingAction extends AbstractAction { * ingest jobs are deleted. */ @NbBundle.Messages({ - "DeleteCasesForReprocessingAction.menuItemText=Delete for Reprocessing" + "DeleteCaseOutputAction.menuItemText=Delete Output", + "DeleteCaseOutputAction.progressDisplayName=Delete Output", + "DeleteCaseOutputAction.taskName=output" }) - DeleteCasesForReprocessingAction() { - super(Bundle.DeleteCasesForReprocessingAction_menuItemText()); - setEnabled(false); // RJCTODO: Enable when implemented + DeleteCaseOutputAction() { + super(Bundle.DeleteCaseOutputAction_menuItemText(), Bundle.DeleteCaseOutputAction_progressDisplayName(), Bundle.DeleteCaseOutputAction_taskName()); } @Override - public void actionPerformed(ActionEvent event) { - final Collection selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class)); -// if (!selectedNodeData.isEmpty()) { -// /* -// * RJCTODO: Create a background task that does the deletion and -// * displays results in a dialog with a scrolling text pane. -// */ -// } + DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + return new DeleteCaseOutputTask(caseNodeData, progress); } - + @Override - public DeleteCasesForReprocessingAction clone() throws CloneNotSupportedException { + public DeleteCaseOutputAction clone() throws CloneNotSupportedException { + super.clone(); throw new CloneNotSupportedException(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseOutputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java similarity index 92% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseOutputTask.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java index 05a58cd7fe..e1d7921904 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteAutoIngestCaseOutputTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java @@ -27,7 +27,7 @@ import org.sleuthkit.autopsy.progress.ProgressIndicator; * input directories intact. The use case is reprocessing a case with a clean * slate without having to restore the input directories. */ -final class DeleteAutoIngestCaseOutputTask extends AutoIngestCaseDeletionTask { +final class DeleteCaseOutputTask extends DeleteCaseTask { /** * Constructs a task to delete the auto ingest job coordination service @@ -40,7 +40,7 @@ final class DeleteAutoIngestCaseOutputTask extends AutoIngestCaseDeletionTask { * data for the case. * @param progress A progress indicator. */ - DeleteAutoIngestCaseOutputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + DeleteCaseOutputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { super(caseNodeData, progress); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletionTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java similarity index 85% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletionTask.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index a2790d1738..815ab3e8fb 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletionTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -45,10 +45,10 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.Inval * A base class for tasks that delete part or all of a case produced via auto * ingest. */ -abstract class AutoIngestCaseDeletionTask implements Runnable { +abstract class DeleteCaseTask implements Runnable { private static final String RESOURCES_LOCK_SUFFIX = "_resources"; //NON-NLS - private static final Logger logger = CaseDashboardLogger.getLogger(); + private static final Logger logger = AutoIngestDashboardLogger.getLogger(); private final CaseNodeData caseNodeData; private final ProgressIndicator progress; private final List nodeDataForAutoIngestJobs; @@ -63,7 +63,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * data for the case to be deleted. * @param progress A progress indicator. */ - AutoIngestCaseDeletionTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + DeleteCaseTask(CaseNodeData caseNodeData, ProgressIndicator progress) { this.caseNodeData = caseNodeData; this.progress = progress; this.nodeDataForAutoIngestJobs = new ArrayList<>(); @@ -72,15 +72,15 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { @Override @NbBundle.Messages({ - "CaseDeletionTask.progress.connectingToCoordSvc=Connecting to the coordination service", - "CaseDeletionTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock", - "CaseDeletionTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock", - "CaseDeletionTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs", - "CaseDeletionTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks" + "DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service", + "DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock", + "DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock", + "DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs", + "DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks" }) public void run() { try { - progress.start(Bundle.CaseDeletionTask_progress_connectingToCoordSvc()); + progress.start(Bundle.DeleteCaseTask_progress_connectingToCoordSvc()); try { coordinationService = CoordinationService.getInstance(); } catch (CoordinationService.CoordinationServiceException ex) { @@ -95,7 +95,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * auto ingest nodes from searching for and finding the case * directory of the case to be deleted. */ - progress.progress(Bundle.CaseDeletionTask_progress_acquiringCaseNameLock()); + progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock()); logger.log(Level.INFO, String.format("Exclusively locking the case name for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); final String caseNameLockName = TimeStampUtils.removeTimeStamp(caseNodeData.getName()); try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) { @@ -113,7 +113,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * deleted open and prevents another node from trying to open * the case as it is being deleted. */ - progress.progress(Bundle.CaseDeletionTask_progress_acquiringCaseDirLock()); + progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock()); logger.log(Level.INFO, String.format("Exclusively locking the case directory for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); try (CoordinationService.Lock caseLock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseNodeData.getDirectory().toString())) { if (caseLock == null) { @@ -121,7 +121,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { return; } - progress.progress(Bundle.CaseDeletionTask_progress_gettingJobNodeData()); + progress.progress(Bundle.DeleteCaseTask_progress_gettingJobNodeData()); logger.log(Level.INFO, String.format("Fetching auto ingest job node data for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); try { getAutoIngestJobNodeData(); @@ -131,7 +131,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { } if (!nodeDataForAutoIngestJobs.isEmpty()) { - progress.progress(Bundle.CaseDeletionTask_progress_acquiringInputDirLocks()); + progress.progress(Bundle.DeleteCaseTask_progress_acquiringInputDirLocks()); logger.log(Level.INFO, String.format("Exclusively locking the case directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); getInputDirectoryLocks(); if (manifestFileLocks.isEmpty()) { @@ -193,15 +193,15 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * be called when holding all of the exclusive locks for the case. */ @NbBundle.Messages({ - "CaseDeletionTask.progress.deletingInputDirs=Deleting input directory", - "# {0} - input directory name", "CaseDeletionTask.progress.deletingInputDir=Deleting input directory {0}" + "DeleteCaseTask.progress.deletingInputDirs=Deleting input directory", + "# {0} - input directory name", "DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}" }) protected void deleteInputDirectories() { - progress.progress(Bundle.CaseDeletionTask_progress_deletingInputDirs()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDirs()); logger.log(Level.INFO, String.format("Deleting input directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) { final Path inputDirPath = jobNodeData.getManifestFilePath().getParent(); - progress.progress(Bundle.CaseDeletionTask_progress_deletingInputDir(inputDirPath)); + progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDir(inputDirPath)); logger.log(Level.INFO, String.format("Deleting input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); if (FileUtil.deleteDir(new File(inputDirPath.toString()))) { logger.log(Level.WARNING, String.format("Failed to delete the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); @@ -216,11 +216,11 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * the case. */ @NbBundle.Messages({ - "CaseDeletionTask.progress.locatingCaseMetadataFile=Locating case metadata file", - "CaseDeletionTask.progress.deletingCaseOutput=Deleting case output" + "DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file", + "DeleteCaseTask.progress.deletingCaseOutput=Deleting case output" }) protected void deleteCaseOutput() { - progress.progress(Bundle.CaseDeletionTask_progress_locatingCaseMetadataFile()); + progress.progress(Bundle.DeleteCaseTask_progress_locatingCaseMetadataFile()); logger.log(Level.INFO, String.format("Locating metadata file for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); String metadataFilePath = null; final File caseDirectory = caseNodeData.getDirectory().toFile(); @@ -234,7 +234,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { } if (metadataFilePath != null) { - progress.progress(Bundle.CaseDeletionTask_progress_deletingCaseOutput()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseOutput()); logger.log(Level.INFO, String.format("Deleting output for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); try { Case.deleteCase(new CaseMetadata(Paths.get(metadataFilePath)), false, progress); @@ -254,13 +254,13 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * locks for all of the input directories for the case. */ @Messages({ - "CaseDeletionTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node", - "CaseDeletionTask.progress.deletingResourcesLockNode=Deleting case resources lock node", - "CaseDeletionTask.progress.deletingDirLockNode=Deleting case directory lock node", - "CaseDeletionTask.progress.deletingNameLockNode=Deleting case name lock node" + "DeleteCaseTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node", + "DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources lock node", + "DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock node", + "DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node" }) protected void deleteCaseLockNodes() { - progress.progress(Bundle.CaseDeletionTask_progress_deletingJobLogLockNode()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); logger.log(Level.INFO, String.format("Deleting case auto ingest job log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); Path logFilePath = AutoIngestJobLogger.getLogPath(caseNodeData.getDirectory()); try { @@ -270,7 +270,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { // RJCTODO: Set delete flags } - progress.progress(Bundle.CaseDeletionTask_progress_deletingResourcesLockNode()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); logger.log(Level.INFO, String.format("Deleting case resources log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX; try { @@ -280,7 +280,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { // RJCTODO: Set delete flags } - progress.progress(Bundle.CaseDeletionTask_progress_deletingDirLockNode()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode()); logger.log(Level.INFO, String.format("Deleting case directory lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); final Path caseDirectoryPath = caseNodeData.getDirectory(); try { @@ -290,7 +290,7 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { // RJCTODO: Set delete flags } - progress.progress(Bundle.CaseDeletionTask_progress_deletingNameLockNode()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode()); logger.log(Level.INFO, String.format("Deleting case name lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); final String caseNameLockName = TimeStampUtils.removeTimeStamp(caseNodeData.getName()); try { @@ -306,10 +306,10 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * called after releasing all of the locks for the case. */ @Messages({ - "CaseDeletionTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes" + "DeleteCaseTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes" }) protected void deleteInputDirectoryLockNodes() { - progress.progress(Bundle.CaseDeletionTask_progress_deletingInputDirLockNodes()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDirLockNodes()); logger.log(Level.INFO, String.format("Deleting input directory lock nodes for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) { try { @@ -369,12 +369,12 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * could not be obtained. */ @NbBundle.Messages({ - "# {0} - input directory name", "CaseDeletionTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0}",}) + "# {0} - input directory name", "DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0}",}) private void getInputDirectoryLocks() { for (AutoIngestJobNodeData autoIngestJobNodeData : nodeDataForAutoIngestJobs) { final Path inputDirPath = autoIngestJobNodeData.getManifestFilePath().getParent(); try { - progress.progress(Bundle.CaseDeletionTask_progress_lockingInputDir(inputDirPath)); + progress.progress(Bundle.DeleteCaseTask_progress_lockingInputDir(inputDirPath)); final CoordinationService.Lock inputDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, autoIngestJobNodeData.getManifestFilePath().toString()); if (null != inputDirLock) { manifestFileLocks.put(autoIngestJobNodeData.getManifestFilePath(), inputDirLock); @@ -396,17 +396,17 @@ abstract class AutoIngestCaseDeletionTask implements Runnable { * for the case. */ @NbBundle.Messages({ - "CaseDeletionTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks", - "# {0} - manifest file path", "CaseDeletionTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}" + "DeleteCaseTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks", + "# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}" }) private void releaseInputDirectoryLocks() { if (!manifestFileLocks.isEmpty()) { - progress.progress(Bundle.CaseDeletionTask_progress_releasingManifestLocks()); + progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLocks()); for (Map.Entry entry : manifestFileLocks.entrySet()) { final Path manifestFilePath = entry.getKey(); final CoordinationService.Lock manifestFileLock = entry.getValue(); try { - progress.progress(Bundle.CaseDeletionTask_progress_releasingManifestLock(manifestFilePath)); + progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); manifestFileLock.release(); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error re3leasing exclusive lock on %s for %s (%s) in %s", manifestFilePath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCasesAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCasesAction.java deleted file mode 100755 index a1fcc632bb..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCasesAction.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 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.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.Collection; -import javax.swing.AbstractAction; -import org.openide.util.NbBundle; -import org.openide.util.Utilities; -import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; - -/** - * An action that completely deletes one or more multi-user cases, including any - * associated auto ingest job input directories and coordination service nodes. - * - * This cases to delete are discovered by querying the actions global context - * lookup for CaseNodeData objects. See - * https://platform.netbeans.org/tutorials/nbm-selection-1.html and - * https://platform.netbeans.org/tutorials/nbm-selection-2.html for details. - */ -final class DeleteCasesAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - - /** - * Constructs an action that completely deletes one or more multi-user - * cases, including any associated auto ingest job input directories and - * coordination service nodes. - */ - @NbBundle.Messages({ - "DeleteCasesAction.menuItemText=Delete Case and Jobs" - }) - DeleteCasesAction() { - super(Bundle.DeleteCasesAction_menuItemText()); - setEnabled(false); // RJCTODO: Enable when implemented - } - - @Override - public void actionPerformed(ActionEvent event) { -// final Collection selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class)); -// if (!selectedNodeData.isEmpty()) { -// /* -// * RJCTODO: Create a background task that does the deletion and -// * displays results in a dialog with a scrolling text pane. -// */ -// } - } - - @Override - public DeleteCasesAction clone() throws CloneNotSupportedException { - throw new CloneNotSupportedException(); - } - -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ShowCaseDeletionStatusAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ShowCaseDeletionStatusAction.java deleted file mode 100755 index c691cd2038..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ShowCaseDeletionStatusAction.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.experimental.autoingest; - -import java.awt.event.ActionEvent; -import javax.swing.AbstractAction; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; - -/** - * An action that shows a popup that enumerates the deletion status of the - * various parts of a multi-user case known to the coordination service. - */ -final class ShowCaseDeletionStatusAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - // private final CaseNodeData caseNodeData; - - /** - * Constructs an action that shows a popup that enumerates the deletion - * status of the various parts of a multi-user case known to the - * coordination service. - * - * @param caseNodeData The coordination service node data for the case. - */ - @NbBundle.Messages({ - "ShowCaseDeletionStatusAction.menuItemText=Show Deletion Status" - }) - ShowCaseDeletionStatusAction(CaseNodeData caseNodeData) { - super(Bundle.ShowCaseDeletionStatusAction_menuItemText()); - // this.caseNodeData = caseNodeData; // RJCTODO: Use - setEnabled(false); // RJCTODO: Enable when implemented - } - - @Override - public void actionPerformed(ActionEvent e) { - // RJCTODO: Implement - } - - @Override - public ShowCaseDeletionStatusAction clone() throws CloneNotSupportedException { - throw new CloneNotSupportedException(); - } - -} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 6c5419d31e..937b659039 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -49,7 +49,7 @@ ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bo ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files ExtractSafari_Module_Name=Safari OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome @@ -178,7 +178,7 @@ SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\ncount: {2}\nSplit Tokens: \n{3} +SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\n\count: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity UsbDeviceIdMapper.parseAndLookup.text=Product: {0} From ad227a3db5dc17522e7f865c6478e689d1c1542a Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 11 Mar 2019 11:51:41 -0400 Subject: [PATCH 05/32] INterim check in of adminstrative case deletion --- .../CaseCoordinationServiceUtils.java | 74 ++++++++++++ .../autopsy/progress/TaskCancellable.java | 68 +++++++++++ .../autoingest/DeleteCaseAction.java | 107 ++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java create mode 100755 Core/src/org/sleuthkit/autopsy/progress/TaskCancellable.java create mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java new file mode 100755 index 0000000000..cda266cb8d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java @@ -0,0 +1,74 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.multiusercases; + +import java.nio.file.Path; +import java.nio.file.Paths; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; + +/** + * Utility methods for using the coordination service for multi-user cases. + */ +public class CaseCoordinationServiceUtils { + + private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS + private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS + + public static String getCaseResourcesLockName(Path caseDirectoryPath) { + return caseDirectoryPath + RESOURCES_LOCK_SUFFIX; + } + + public static String getCaseAutoIngestLogLockName(Path caseDirectoryPath) { + return Paths.get(caseDirectoryPath.toString(), CASE_AUTO_INGEST_LOG_NAME).toString(); + } + + public static String getCaseDirectoryLockName(Path caseDirectoryPath) { + return caseDirectoryPath.toString(); + } + + public static String getCaseLockName(Path caseDirectoryPath) { + String caseName = caseDirectoryPath.getFileName().toString(); + if (TimeStampUtils.endsWithTimeStamp(caseName)) { + caseName = TimeStampUtils.removeTimeStamp(caseName); + if (caseName.endsWith("_")) { + caseName = caseName.substring(0, caseName.length() - 1); + } + } + return caseName; + } + + public static boolean isCaseAutoIngestLogLockName(String lockName) { + return Paths.get(lockName).getFileName().toString().equals(CASE_AUTO_INGEST_LOG_NAME); + } + + public static boolean isCaseResourcesLockName(String lockName) { + return Paths.get(lockName).getFileName().toString().endsWith(RESOURCES_LOCK_SUFFIX); + } + + public static boolean isCaseLockName(String lockName) { + return !(lockName.contains("\\") || lockName.contains("//")); + } + + /** + * Prevents instantiation of this uitlity class. + */ + private CaseCoordinationServiceUtils() { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/progress/TaskCancellable.java b/Core/src/org/sleuthkit/autopsy/progress/TaskCancellable.java new file mode 100755 index 0000000000..1ce473e9b0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/progress/TaskCancellable.java @@ -0,0 +1,68 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.progress; + +import java.util.concurrent.Future; +import org.openide.util.Cancellable; +import org.openide.util.NbBundle; + +/** + * Pluggable cancellation behavior for use in progress indicators (such as the + * application frame progress indicator) that support cancelling a task using an + * implementation of org.openide.util.Cancellable. Encapsulates a Future to + * be cancelled and sets the cancelling flag and message of the progress + * indicator. + */ +public class TaskCancellable implements Cancellable { + + private final ProgressIndicator progress; + private Future future; + + /** + * Constructs a pluggable cancellation behavior for use in progress + * indicators (such as the application frame progress indicator) that + * support cancelling a task using an implementation of + * org.openide.util.Cancellable. Encapsulates a Future to be cancelled + * and sets the cancelling flag and message of the progress indicator. + * + * @param progress + */ + public TaskCancellable(ProgressIndicator progress) { + this.progress = progress; + } + + /** + * Sets the Future used to cancel the associated task. + * + * @param future The future for the associated task. + */ + public synchronized void setFuture(Future future) { + this.future = future; + } + + @Override + @NbBundle.Messages({ + "TaskCanceller.progress.cancellingMessage=Cancelling..." + }) + public synchronized boolean cancel() { + progress.setCancelling(Bundle.TaskCanceller_progress_cancellingMessage()); + return future.cancel(true); + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java new file mode 100755 index 0000000000..39eb32d6f0 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java @@ -0,0 +1,107 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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 com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import javax.swing.AbstractAction; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.progress.AppFrameProgressBar; +import org.sleuthkit.autopsy.progress.TaskCancellable; +import org.sleuthkit.autopsy.progress.ProgressIndicator; + +/** + * An abstract class for an action that deletes one or more auto ingest cases + * using a thread pool, one task per case. Uses the Template Method design + * pattern to allow subclasses to specify the deletion task to be performed. + * + * This cases to delete are discovered by querying the actions global context + * lookup for CaseNodeData objects. See + * https://platform.netbeans.org/tutorials/nbm-selection-1.html and + * https://platform.netbeans.org/tutorials/nbm-selection-2.html for details. + */ +abstract class DeleteCaseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private static final int NUMBER_OF_THREADS = 4; + private static final String THREAD_NAME_SUFFIX = "-task-%d"; //NON-NLS + private static final String PROGRESS_DISPLAY_NAME = "%s for %s"; //NON-NLS + private final String taskDisplayName; + private final ExecutorService executor; + + /** + * Constructs an abstract class for an action that deletes one or more auto + * ingest cases using a thread pool, one task per case. Uses the Template + * Method design pattern to allow subclasses to specify the deletion task to + * be performed. + * + * @param menuItemText The menu item text for the action. + * @param taskDisplayName The task display name for the progress indicator + * for the task, to be inserted in the first position + * of "%s for %s", where the second substitution is + * the case name. + * @param taskName The task name, to be inserted in the first + * position of "%s-task-%d", where the second + * substitution is the pool thread number. + */ + DeleteCaseAction(String menuItemText, String taskDisplayName, String taskName) { + super(menuItemText); + this.taskDisplayName = taskDisplayName; + String threadNameFormat = taskName + THREAD_NAME_SUFFIX; + executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS, new ThreadFactoryBuilder().setNameFormat(threadNameFormat).build()); + } + + @Override + public void actionPerformed(ActionEvent event) { + Collection selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class)); + for (CaseNodeData nodeData : selectedNodeData) { + AppFrameProgressBar progress = new AppFrameProgressBar(String.format(PROGRESS_DISPLAY_NAME, taskDisplayName, nodeData.getDisplayName())); + TaskCancellable taskCanceller = new TaskCancellable(progress); + progress.setCancellationBehavior(taskCanceller); + Future future = executor.submit(getTask(nodeData, progress)); + taskCanceller.setFuture(future); + } + // RJCTODO: FIre events, remove case deletion from jobs dashboard + } + + /** + * Uses the Template Method design pattern to allow subclasses to specify + * the deletion task to be performed in a worker thread by this action. + * + * @param caseNodeData The case directory lock coordination service node + * data for the case to be deleted. + * @param progress A progress indicator for the task. + * + * @return A case deletion task, ready to be executed. + */ + abstract DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress); + + @Override + public DeleteCaseAction clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + +} From 486fbfe152dd764f822f0f38a7d7945ea704d6d3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 11 Mar 2019 11:52:00 -0400 Subject: [PATCH 06/32] Interim check in of adminstrative case deletion --- .../casemodule/Bundle.properties-MERGED | 32 +- .../sleuthkit/autopsy/casemodule/Case.java | 517 +++++++-------- .../multiusercases/CaseNodeData.java | 2 +- .../MultiUserCaseNodeDataCollector.java | 131 ++-- .../Bundle.properties-MERGED | 5 + .../MultiUserCaseBrowserCustomizer.java | 15 +- .../MultiUserCasesRootNode.java | 2 +- .../CoordinationService.java | 34 +- .../autoingest/AutoIngestManager.java | 6 +- .../AutoIngestMetricsCollector.java | 2 +- .../autoingest/AutoIngestMonitor.java | 2 +- .../autoingest/Bundle.properties-MERGED | 35 +- .../DeleteCaseInputAndOutputTask.java | 15 +- .../autoingest/DeleteCaseInputTask.java | 30 +- .../autoingest/DeleteCaseOutputAction.java | 5 - .../autoingest/DeleteCaseOutputTask.java | 13 +- .../autoingest/DeleteCaseTask.java | 608 +++++++++++------- 17 files changed, 808 insertions(+), 646 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index 3ec3393309..dbd5215554 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -11,7 +11,7 @@ Case.deleteCaseFailureMessageBox.message=Error deleting case: {0} Case.deleteCaseFailureMessageBox.title=Failed to Delete Case Case.exceptionMessage.cancelledByUser=Cancelled by user. Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first. -Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user. +Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host. Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case. # {0} - exception message @@ -34,7 +34,7 @@ Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case datab Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}. Case.exceptionMessage.emptyCaseDir=Must specify a case directory path. Case.exceptionMessage.emptyCaseName=Must specify a case name. -Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details. +Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details. # {0} - exception message Case.exceptionMessage.execExceptionWrapperMessage={0} # {0} - exception message @@ -58,36 +58,12 @@ Case.progressMessage.connectingToCoordSvc=Connecting to coordination service... Case.progressMessage.creatingCaseDatabase=Creating case database... Case.progressMessage.creatingCaseDirectory=Creating case directory... Case.progressMessage.creatingCaseNodeData=Creating coordination service node data... -Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log lock coordination service node... Case.progressMessage.deletingCaseDatabase=Deleting case database... Case.progressMessage.deletingCaseDirectory=Deleting case directory... -Case.progressMessage.deletingDirectoryCoordinationServiceNode=Deleting case directory lock coordination service node... -Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resourceslock coordination service node... +Case.progressMessage.deletingCaseDirLockNode=Deleting case directory lock coordination service node... +Case.progressMessage.deletingResourcesLockNode=Deleting case resources lock node... Case.progressMessage.deletingTextIndex=Deleting text index... -# {0} - exception message -Case.progressMessage.errorConnectingToCoordSvc=An error occurred connecting to the coordination service (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorDeletingCaseDb=An error occurred deleting the case database (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorDeletingCaseDir=An error occurred deleting the case directory (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorDeletingCaseDirLockNode=An error occurred deleting the case directory lock node (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorDeletingJobLogLockNode=An error occurred deleting the case auto ingest log lock node (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorDeletingResourcesLockNode=An error occurred deleting the case resources lock node (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorDeletingTextIndex=An error occurred deleting the text index for the case (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorLockingCase=An error occurred exclusively locking the case for deletion (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}. -# {0} - exception message -Case.progressMessage.errorSavingDeletedItemsFlags=An error occurred saving the deleted items flags in the coordination service database (see log for details): {0}. Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case... -Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data. Case.progressMessage.openingApplicationServiceResources=Opening application service case resources... Case.progressMessage.openingCaseDatabase=Opening case database... Case.progressMessage.openingCaseLevelServices=Opening case-level services... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 1e0ebcde91..7a4ed43043 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -702,8 +702,45 @@ public class Case { } CaseMetadata metadata = currentCase.getMetadata(); closeCurrentCase(); - ProgressIndicator progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); - deleteCase(metadata, true, progressIndicator); + deleteCase(metadata); + } + } + + /** + * Deletes a case. The case to be deleted must not be the "current case." + * + * @param metadata The case metadata. + * + * @throws CaseActionException If there were one or more errors deleting the + * case. The exception will have a user-friendly + * message and may be a wrapper for a + * lower-level exception. + */ + @Messages({ + "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first." + }) + public static void deleteCase(CaseMetadata metadata) throws CaseActionException { + synchronized (caseActionSerializationLock) { + if (null != currentCase) { + throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()); + } + } + + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + progressIndicator.start(Bundle.Case_progressMessage_preparing()); + try { + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { + deleteSingleUserCase(metadata, progressIndicator); + } else { + deleteMultiUserCase(metadata, progressIndicator); + } + } finally { + progressIndicator.finish(); } } @@ -884,40 +921,6 @@ public class Case { return imgPaths; } - /** - * Deletes a case. The case to be deletd must not be the "current case." - * - * @param metadata The case metadata. - * @param deleteCaseLockNodes Whether or not to delete the coordination - * service lock nodes for the case. - * @param progressIndicator A progress indicator. - * - * @throws CaseActionException If there were one or more errors deleting the - * case. - */ - @Beta - @Messages({ - "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first." - }) - public static void deleteCase(CaseMetadata metadata, boolean deleteCaseLockNodes, ProgressIndicator progressIndicator) throws CaseActionException { - synchronized (caseActionSerializationLock) { - if (null != currentCase) { - throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()); - } - } - - progressIndicator.start(Bundle.Case_progressMessage_preparing()); - try { - if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - deleteSingleUserCase(metadata, progressIndicator); - } else { - deleteMultiUserCase(metadata, progressIndicator); - } - } finally { - progressIndicator.finish(); - } - } - /** * Deletes a single-user case. * @@ -925,12 +928,12 @@ public class Case { * @param progressIndicator A progress indicator. * * @throws CaseActionException If there were one or more errors deleting the - * case. + * case. The exception will have a user-friendly + * message and may be a wrapper for a + * lower-level exception. */ @Messages({ - "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details.", - "# {0} - exception message", "Case.progressMessage.errorDeletingTextIndex=An error occurred deleting the text index for the case (see log for details): {0}.", - "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDir=An error occurred deleting the case directory (see log for details): {0}." + "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details." }) private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { boolean errorsOccurred = false; @@ -938,16 +941,14 @@ public class Case { deleteTextIndex(metadata, progressIndicator); } catch (KeywordSearchServiceException ex) { errorsOccurred = true; - logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingTextIndex(ex.getMessage())); + logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); } try { deleteCaseDirectory(metadata, progressIndicator); } catch (CaseActionException ex) { errorsOccurred = true; - logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDir(ex.getMessage())); + logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); } deleteFromRecentCases(metadata, progressIndicator); @@ -960,173 +961,184 @@ public class Case { /** * Deletes a multi-user case. * - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. + * @param metadata The case metadata. + * @param deleteCaseLockNodes Whether or not to delete the coordination + * service case directory and case name lock + * nodes for the case. + * @param progressIndicator A progress indicator. * * @throws CaseActionException If there were one or more errors deleting the - * case. + * case. The exception will have a user-friendly + * message and may be a wrapper for a + * lower-level exception. */ @Messages({ "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", - "# {0} - exception message", "Case.progressMessage.errorConnectingToCoordSvc=An error occurred connecting to the coordination service (see log for details): {0}.", "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", - "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user.", - "# {0} - exception message", "Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}.", - "# {0} - exception message", "Case.progressMessage.errorLockingCase=An error occurred exclusively locking the case for deletion (see log for details): {0}.", - "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", - "# {0} - exception message", "Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}.", - "Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data.", - "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDb=An error occurred deleting the case database (see log for details): {0}.", - "# {0} - exception message", "Case.progressMessage.errorSavingDeletedItemsFlags=An error occurred saving the deleted items flags in the coordination service database (see log for details): {0}." + "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.", + "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case..." }) private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { - CaseNodeData caseNodeData; - boolean errorsOccurred = false; progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc()); CoordinationService coordinationService; try { coordinationService = CoordinationService.getInstance(); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorConnectingToCoordSvc(ex.getMessage())); throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } - /* - * Acquire an exclusive case name lock. This will prevent auto ingest - * nodes from attempting to search for the case directory before it is - * deleted. - */ - progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser()); - final String caseNameLockName = TimeStampUtils.removeTimeStamp(metadata.getCaseName()); - try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) { - if (nameLock == null) { - logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because the case name was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); + CaseNodeData caseNodeData; + boolean errorsOccurred = false; + try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { + if (dirLock == null) { + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); } - /* - * Acquire an exclusive case directory lock. This ensures that no - * other node (host) currently has the case open and prevents - * another node (host) from trying to open the case as it is being - * deleted. - */ - try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { - if (dirLock == null) { - logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because the case was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); - throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); - } - - progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData()); - try { - byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); - if (nodeBytes != null && nodeBytes.length > 0) { - caseNodeData = new CaseNodeData(nodeBytes); - } else { - logger.log(Level.SEVERE, String.format("Missing coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - progressIndicator.progress(Bundle.Case_progressMessage_missingCoordSvcNodeData()); - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); - } - } catch (CoordinationServiceException | InterruptedException | IOException ex) { - logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorGettingCoordSvcNodeData(ex.getMessage())); + progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData()); + try { + byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); + if (nodeBytes != null && nodeBytes.length > 0) { + caseNodeData = new CaseNodeData(nodeBytes); + } else { + logger.log(Level.WARNING, String.format("Missing coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } - - try { - deleteCaseDatabase(metadata, progressIndicator, caseNodeData); - } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { - errorsOccurred = true; - logger.log(Level.SEVERE, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDb(ex.getMessage())); - } - - try { - deleteTextIndex(metadata, progressIndicator, caseNodeData); - } catch (KeywordSearchServiceException ex) { - errorsOccurred = true; - logger.log(Level.SEVERE, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingTextIndex(ex.getMessage())); - } - - try { - deleteCaseDirectory(metadata, progressIndicator, caseNodeData); - } catch (CaseActionException ex) { - errorsOccurred = true; - logger.log(Level.SEVERE, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDir(ex.getMessage())); - } - - deleteFromRecentCases(metadata, progressIndicator); - - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorLockingCase(ex.getMessage())); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } + errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator); + } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case name for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorLockingCaseName(ex.getMessage())); + logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } - deleteCaseResourcesCoordinationServiceNode(metadata, progressIndicator, coordinationService); - deleteCaseAutoIngestLogCoordinationServiceNode(metadata, progressIndicator, coordinationService); - // RJCTODO: Delete case name lock node - if (!errorsOccurred) { - deleteCaseDirectoryCoordinationServiceNode(metadata, progressIndicator, coordinationService); - } else { - /* - * Save the deleted item flags for the partially deleted case so - * that system adminstrators can try to fix whatever wnet wrong and - * then retry deleting the case. - */ try { - coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), caseNodeData.toArray()); - } catch (IOException | CoordinationServiceException | InterruptedException ex) { - logger.log(Level.SEVERE, String.format("Failed to write the node dat with the deleted items flags for the partially deleted case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorSavingDeletedItemsFlags(ex.getMessage())); + deleteCaseDirectoryLockNode(caseNodeData, progressIndicator); + } catch (CoordinationServiceException | InterruptedException ex) { + logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + errorsOccurred = true; } + } + + if (errorsOccurred) { throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } } /** - * Attempts to delete the case database for a multi-user case. + * IMPORTANT: This is a "beta" method and is subject to change or removal + * without notice! + * + * Attempts to delete the case database, the text index, the case directory, + * and the case resources coordination service lock code for a case and + * removes the case from the recent cases menu of the mian application + * window. * + * @param caseNodeData The coordination service node data for the case. * @param metadata The case metadata. * @param progressIndicator A progress indicator. - * @param caseNodeData The coordination service node data for the case. * - * @throws UserPreferencesException If there is an error getting the + * @return True if one or more errors occurred (see log for details), false + * otherwise. + * + * @throws InterruptedException If the thread this code is running in is + * interrupted while blocked, i.e., if + * cancellation of the opersation is detected + * during a wait. + */ + @Beta + public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws InterruptedException { + boolean errorsOccurred = false; + try { + deleteCaseDatabase(caseNodeData, metadata, progressIndicator); + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + try { + deleteTextIndex(caseNodeData, metadata, progressIndicator); + } catch (KeywordSearchServiceException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + try { + deleteCaseDirectory(caseNodeData, metadata, progressIndicator); + } catch (CaseActionException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + deleteFromRecentCases(metadata, progressIndicator); + + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + try { + deleteCaseResourcesLockNode(caseNodeData, progressIndicator); + } catch (CoordinationServiceException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + return errorsOccurred; + } + + /** + * Attempts to delete the case database for a multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * + * @throws UserPreferencesException if there is an error getting the * database server connection info. - * @throws ClassNotFoundException If there is an error gettting the + * @throws ClassNotFoundException if there is an error gettting the * required JDBC driver. - * @throws SQLException If there is an error executing the SQL + * @throws SQLException if there is an error executing the SQL * to drop the database from the database * server. + * @throws InterruptedException If interrupted while blocked waiting for + * coordination service data to be written + * to the coordination service node + * database. */ @Messages({ - "Case.progressMessage.deletingCaseDatabase=Deleting case database...",}) - private static void deleteCaseDatabase(CaseMetadata metadata, ProgressIndicator progressIndicator, - CaseNodeData caseNodeData) throws UserPreferencesException, ClassNotFoundException, SQLException { - if (caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { - return; + "Case.progressMessage.deletingCaseDatabase=Deleting case database..." + }) + private static void deleteCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); + CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo(); + String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { + String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS + statement.execute(deleteCommand); + } + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DB); } - progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); - CaseDbConnectionInfo db; - 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); - } - caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.CASE_DB); } /** @@ -1151,19 +1163,21 @@ public class Case { /** * Attempts to delete the text index for a multi-user case. * + * @param caseNodeData The coordination service node data for the case. * @param metadata The case mnetadata. * @param progressIndicator A progress indicator. - * @param caseNodeData The coordination service node data for the case - * if it is a multi-user case; for a single-user - * case pass null. * * @throws KeywordSearchServiceException If there is an error deleting the * text index. + * @throws InterruptedException If interrupted while blocked + * waiting for coordination service + * data to be written to the + * coordination service node database. */ - private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws KeywordSearchServiceException { + private static void deleteTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException, InterruptedException { if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) { deleteTextIndex(metadata, progressIndicator); - caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.TEXT_INDEX); + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX); } } @@ -1180,27 +1194,32 @@ public class Case { "Case.progressMessage.deletingCaseDirectory=Deleting case directory..." }) private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + // RJCTODO: Update FileUtil.deleteDir to use robocopy on Windows + // when the path is >= 255 chars. Actually, deprecate this method and + // replace it with one that throws instead of returning a boolean value. progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); - // RJCTODO: Use robocopy on Windows, possibly just for longer paths if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); } } /** - * Attempts to delete the case directory for a case. + * Attempts to delete the case directory for a multi-user case. * + * @param caseNodeData The coordination service node data for the case. * @param metadata The case mnetadata. * @param progressIndicator A progress indicator. - * @param caseNodeData The coordination service node data for the case. * - * @throws CaseActionException If there is an error deleting the case - * directory. + * @throws CaseActionException if there is an error deleting the case + * directory. + * @throws InterruptedException If interrupted while blocked waiting for + * coordination service data to be written to + * the coordination service node database. */ - private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws CaseActionException { + private static void deleteCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException { if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) { deleteCaseDirectory(metadata, progressIndicator); - caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.CASE_DIR); + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR); } } @@ -1223,71 +1242,82 @@ public class Case { } } + // RJCTODO: Copy-paste instead /** - * Deletes the case resources coordination nodes for a multi-user case. + * IMPORTANT: This is a "beta" method and is subject to change or removal + * without notice! * - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. - */ - @Messages({ - "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resourceslock coordination service node...", - "# {0} - exception message", "Case.progressMessage.errorDeletingResourcesLockNode=An error occurred deleting the case resources lock node (see log for details): {0}." - }) - static void deleteCaseResourcesCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { - progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode()); - String resourcesLockNodePath = metadata.getCaseDirectory() + RESOURCES_LOCK_SUFFIX; - try { - coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingResourcesLockNode(ex.getMessage())); - } - } - - /** - * Deletes the case auto ingest log lock coordination service node for a - * multi-user case, if it exists. - * - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. - */ - @Messages({ - "Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log lock coordination service node...", - "# {0} - exception message", "Case.progressMessage.errorDeletingJobLogLockNode=An error occurred deleting the case auto ingest log lock node (see log for details): {0}." - }) - static void deleteCaseAutoIngestLogCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { - progressIndicator.progress(Bundle.Case_progressMessage_deletingAutoIngestLogCoordSvcNode()); - Path logFilePath = Paths.get(metadata.getCaseDirectory(), AUTO_INGEST_LOG_FILE_NAME); - try { - /* - * Does nothing if the node does not exist. - */ - coordinationService.deleteNode(CategoryNode.CASES, logFilePath.toString()); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error deleting the case auto ingest log lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingJobLogLockNode(ex.getMessage())); - } - } - - /** - * Deletes the case (directory lock) coordination service node for a + * Deletes the case resources lock coordination service node for a * multi-user case. * - * @param metadata The metadata for the case to delete. + * @param caseNodeData The coordination service node data for the case. * @param progressIndicator The progress indicator for the deletion * operation. + * + * @throws CoordinationServiceException If there is an error deleting the + * coordination service node. + * @throws InterruptedException If a thread interrupt occurs while + * blocked waiting for the operation to + * complete. */ @Messages({ - "Case.progressMessage.deletingDirectoryCoordinationServiceNode=Deleting case directory lock coordination service node...", - "# {0} - exception message", "Case.progressMessage.errorDeletingCaseDirLockNode=An error occurred deleting the case directory lock node (see log for details): {0}." + "Case.progressMessage.deletingResourcesLockNode=Deleting case resources lock node..." }) - static void deleteCaseDirectoryCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) { - String caseDirectoryLockNodePath = metadata.getCaseDirectory(); + @Beta + private static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException { + progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode()); + String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX;//RJCTODO: Use utility + CoordinationService coordinationService = CoordinationService.getInstance(); + coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); + } + + // RJCTODO: Copy-paste instead + /** + * IMPORTANT: This is a "beta" method and is subject to change or removal + * without notice! + * + * Deletes the case directory lock coordination service node for a + * multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param progressIndicator The progress indicator for the deletion + * operation. + * + * @throws CoordinationServiceException If there is an error deleting the + * coordination service node. + * @throws InterruptedException If a thread interrupt occurs while + * blocked waiting for the operation to + * complete. + */ + @Beta + @Messages({ + "Case.progressMessage.deletingCaseDirLockNode=Deleting case directory lock coordination service node..." + }) + public static void deleteCaseDirectoryLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException { + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirLockNode()); + String caseDirectoryLockNodePath = caseNodeData.getDirectory().toString(); + CoordinationService coordinationService = CoordinationService.getInstance(); + coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath); + } + + /** + * Sets a deleted item flag in the coordination service node data for a + * multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param flag The flag to set. + * + * @throws InterruptedException If interrupted while blocked waiting for + * coordination service data to be written to + * the coordination service node database. + */ + private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException { try { - coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error deleting the case directory lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDirLockNode(ex.getMessage())); + caseNodeData.setDeletedFlag(flag); + CoordinationService coordinationService = CoordinationService.getInstance(); + coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray()); + } catch (IOException | CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); } } @@ -3310,31 +3340,4 @@ public class Case { deleteReports(reports); } - /** - * Deletes a case. - * - * @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. - * @deprecated Use deleteCase(CaseMetadata, ProgressIndicator) instead. - * instead. - */ - @Deprecated - public static void deleteCase(CaseMetadata metadata) throws CaseActionException { - /* - * Set up either a GUI progress indicator without a cancel button (can't - * cancel deleting a case) or a logging progress indicator. - */ - ProgressIndicator progressIndicator; - if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); - } else { - progressIndicator = new LoggingProgressIndicator(); - } - deleteCase(metadata, true, progressIndicator); - } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java index 0840fa480c..35414ed629 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java @@ -289,7 +289,7 @@ public final class CaseNodeData { CASE_DB(2), CASE_DIR(4), DATA_SOURCES(8), - JOB_MANIFEST_NODES(16); + MANIFEST_FILE_LOCK_NODES(16); private final short value; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java index 63b11728ab..f3fa64b924 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java @@ -29,76 +29,60 @@ import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coreutils.Logger; /** * Queries the coordination service to collect the multi-user case node data * stored in the case directory lock ZooKeeper nodes. */ -final public class MultiUserCaseNodeDataCollector { +final public class MultiUserCaseNodeDataCollector { // RJCTODO: Shorten name after multi-case keyword search code is in. private static final Logger logger = Logger.getLogger(MultiUserCaseNodeDataCollector.class.getName()); - private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS - private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS /** * Queries the coordination service to collect the multi-user case node data * stored in the case directory lock ZooKeeper nodes. * - * @return A list of CaseNodedata objects that convert data for a case - * directory lock coordination service node to and from byte arrays. + * @return The node data for the multi-user cases known to the coordination + * service. * - * @throws CoordinationServiceException If there is an error + * @throws CoordinationServiceException If there is an error interacting + * with the coordination service. + * @throws InterruptedException If the current thread is interrupted + * while waiting for the coordination + * service. */ - public static List getNodeData() throws CoordinationService.CoordinationServiceException { + public static List getNodeData() throws CoordinationServiceException, InterruptedException { final List cases = new ArrayList<>(); final CoordinationService coordinationService = CoordinationService.getInstance(); final List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES); for (String nodeName : nodeList) { - /* - * Ignore auto ingest case name lock nodes. - */ - final Path nodeNameAsPath = Paths.get(nodeName); - if (!(nodeNameAsPath.toString().contains("\\") || nodeNameAsPath.toString().contains("//"))) { - continue; - } - - /* - * Ignore case auto ingest log lock nodes and resource lock nodes. - */ - final String lastNodeNameComponent = nodeNameAsPath.getFileName().toString(); - if (lastNodeNameComponent.equals(CASE_AUTO_INGEST_LOG_NAME)) { - continue; - } - - /* - * Ignore case resources lock nodes. - */ - if (lastNodeNameComponent.endsWith(RESOURCES_LOCK_SUFFIX)) { + if (CaseCoordinationServiceUtils.isCaseLockName(nodeName) + || CaseCoordinationServiceUtils.isCaseResourcesLockName(nodeName) + || CaseCoordinationServiceUtils.isCaseAutoIngestLogLockName(nodeName)) { continue; } /* * Get the data from the case directory lock node. This data may not - * exist for "legacy" nodes. If it is missing, create it. + * exist or may exist only in an older version. If it is missing or + * incomplete, create or update it. */ try { CaseNodeData nodeData; - byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); + final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); if (nodeBytes != null && nodeBytes.length > 0) { nodeData = new CaseNodeData(nodeBytes); - if (nodeData.getVersion() == 0) { - /* - * Version 0 case node data was only written if errors - * occurred during an auto ingest job and consisted of - * only the set errors flag. - */ - nodeData = createNodeDataFromCaseMetadata(nodeName, true); + if (nodeData.getVersion() < CaseNodeData.getCurrentVersion()) { + nodeData = updateNodeData(nodeName, nodeData); } } else { - nodeData = createNodeDataFromCaseMetadata(nodeName, false); + nodeData = updateNodeData(nodeName, null); + } + if (nodeData != null) { + cases.add(nodeData); } - cases.add(nodeData); } catch (CoordinationService.CoordinationServiceException | InterruptedException | IOException | ParseException | CaseMetadata.CaseMetadataException ex) { logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex); @@ -109,15 +93,15 @@ final public class MultiUserCaseNodeDataCollector { } /** - * Creates and saves case directory lock coordination service node data from - * the metadata file for the case associated with the node. + * Updates the case directory lock coordination service node data for a + * case. * - * @param nodeName The coordination service node name, i.e., the case - * directory path. - * @param errorsOccurred Whether or not errors occurred during an auto - * ingest job for the case. + * @param nodeName The coordination service node name, i.e., the case + * directory path. + * @param oldNodeData . * - * @return A CaseNodedata object. + * @return A CaseNodedata object or null if the coordination service node is + * an "orphan" with no corresponding case directry. * * @throws IOException If there is an error writing the * node data to a byte array. @@ -130,28 +114,67 @@ final public class MultiUserCaseNodeDataCollector { * @throws InterruptedException If a coordination service operation * is interrupted. */ - private static CaseNodeData createNodeDataFromCaseMetadata(String nodeName, boolean errorsOccurred) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException { - CaseNodeData nodeData = null; + private static CaseNodeData updateNodeData(String nodeName, CaseNodeData oldNodeData) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException { Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS); File caseDirectory = caseDirectoryPath.toFile(); - if (caseDirectory.exists()) { + if (!caseDirectory.exists()) { + logger.log(Level.WARNING, String.format("Found orphan coordination service node %s, attempting clean up", caseDirectoryPath)); + deleteLockNodes(CoordinationService.getInstance(), caseDirectoryPath); + return null; + } + + CaseNodeData nodeData = null; + if (oldNodeData == null || oldNodeData.getVersion() == 0) { File[] files = caseDirectory.listFiles(); for (File file : files) { String name = file.getName().toLowerCase(); if (name.endsWith(CaseMetadata.getFileExtension())) { CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath())); nodeData = new CaseNodeData(metadata); - nodeData.setErrorsOccurred(errorsOccurred); + if (oldNodeData != null) { + /* + * Version 0 case node data was only written if errors + * occurred during an auto ingest job. + */ + nodeData.setErrorsOccurred(true); + } break; } } } + if (nodeData != null) { - CoordinationService coordinationService = CoordinationService.getInstance(); - coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray()); - return nodeData; - } else { - throw new IOException(String.format("Could not find case metadata file for %s", nodeName)); + CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray()); + } + + return nodeData; + } + + /** + * Attempts to delete the coordination service lock nodes for a case, + * logging any failures. + * + * @param coordinationService The coordination service. + * @param caseDirectoryPath The case directory path. + */ + private static void deleteLockNodes(CoordinationService coordinationService, Path caseDirectoryPath) { + deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseResourcesLockName(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath)); + } + + /** + * Attempts to delete a coordination service node, logging failure. + * + * @param coordinationService The coordination service. + * @param nodeName A node name. + */ + private static void deleteCoordinationServiceNode(CoordinationService coordinationService, String nodeName) { + try { + coordinationService.deleteNode(CoordinationService.CategoryNode.CASES, nodeName); + } catch (CoordinationService.CoordinationServiceException | InterruptedException ex) { + logger.log(Level.WARNING, String.format("Error deleting coordination service node %s", nodeName), ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED index 7ca1d937a3..6f24fde4f6 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED @@ -1,5 +1,10 @@ +MultiUserCaseBrowserCustomizer.column.caseDbDeleteStatus=Case Database Deleted +MultiUserCaseBrowserCustomizer.column.caseDirDeleteStatus=Case Directory Deleted MultiUserCaseBrowserCustomizer.column.createTime=Create Time +MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Deleted MultiUserCaseBrowserCustomizer.column.directory=Directory MultiUserCaseBrowserCustomizer.column.displayName=Name MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time +MultiUserCaseBrowserCustomizer.column.manifestCoordSvcNodesDeleteStatus=Manifest ZooKeeper Node Deleted +MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted MultiUserCasesBrowserPanel.waitNode.message=Please Wait... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java index 5cdca90ca8..3bccb63ed4 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java @@ -141,14 +141,23 @@ public interface MultiUserCaseBrowserCustomizer { "MultiUserCaseBrowserCustomizer.column.displayName=Name", "MultiUserCaseBrowserCustomizer.column.createTime=Create Time", "MultiUserCaseBrowserCustomizer.column.directory=Directory", - "MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time" + "MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time", + "MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted", + "MultiUserCaseBrowserCustomizer.column.caseDbDeleteStatus=Case Database Deleted", + "MultiUserCaseBrowserCustomizer.column.caseDirDeleteStatus=Case Directory Deleted", + "MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Deleted", + "MultiUserCaseBrowserCustomizer.column.manifestCoordSvcNodesDeleteStatus=Manifest ZooKeeper Node Deleted" }) public enum Column { DISPLAY_NAME(Bundle.MultiUserCaseBrowserCustomizer_column_displayName()), CREATE_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_createTime()), DIRECTORY(Bundle.MultiUserCaseBrowserCustomizer_column_directory()), - LAST_ACCESS_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_lastAccessTime()); - // RJCTODO: Add properties for deleted items flags + LAST_ACCESS_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_lastAccessTime()), + TEXT_INDEX_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_textIndexDeleteStatus()), + CASE_DB_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDbDeleteStatus()), + CASE_DIR_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDirDeleteStatus()), + DATA_SOURCES_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_dataSourcesDeleteStatus()), + MANIFEST_FILE_LOCK_NODES_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_manifestCoordSvcNodesDeleteStatus()); private final String displayName; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java index 0d214e856d..3287ea53bb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java @@ -65,7 +65,7 @@ final class MultiUserCasesRootNode extends AbstractNode { try { List caseNodeData = MultiUserCaseNodeDataCollector.getNodeData(); keys.addAll(caseNodeData); - } catch (CoordinationService.CoordinationServiceException ex) { + } catch (CoordinationService.CoordinationServiceException | InterruptedException ex) { logger.log(Level.SEVERE, "Failed to get case node data from coodination service", ex); } return true; diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index ea087664b2..c63e517b9d 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -363,15 +363,22 @@ public final class CoordinationService { * @param category The desired category in the namespace. * @param nodePath The node to be deleted. * - * @throws CoordinationServiceException If there is an error deleting the - * node. + * @throws CoordinationServiceException If there is an error deleting the + * node. + * @throws java.lang.InterruptedException If a thread interrupt occurs while + * blocked waiting for the operation + * to complete. */ - public void deleteNode(CategoryNode category, String nodePath) throws CoordinationServiceException { + public void deleteNode(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException { String fullNodePath = getFullyQualifiedNodePath(category, nodePath); try { curator.delete().forPath(fullNodePath); } catch (Exception ex) { - throw new CoordinationServiceException(String.format("Failed to delete node %s", fullNodePath), ex); + if (ex instanceof InterruptedException) { + throw (InterruptedException) ex; + } else { + throw new CoordinationServiceException(String.format("Failed to delete node %s", fullNodePath), ex); + } } } @@ -382,15 +389,22 @@ public final class CoordinationService { * * @return A list of child node names. * - * @throws CoordinationServiceException If there is an error getting the - * node list. + * @throws CoordinationServiceException If there is an error getting the + * node list. + * @throws java.lang.InterruptedException If a thread interrupt occurs while + * blocked waiting for the operation + * to complete. */ - public List getNodeList(CategoryNode category) throws CoordinationServiceException { + public List getNodeList(CategoryNode category) throws CoordinationServiceException, InterruptedException { try { List list = curator.getChildren().forPath(categoryNodeToPath.get(category.getDisplayName())); return list; } catch (Exception ex) { - throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex); + if (ex instanceof InterruptedException) { + throw (InterruptedException) ex; + } else { + throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex); + } } } @@ -404,9 +418,9 @@ public final class CoordinationService { */ private String getFullyQualifiedNodePath(CategoryNode category, String nodePath) { // nodePath on Unix systems starts with a "/" and ZooKeeper doesn't like two slashes in a row - if(nodePath.startsWith("/")){ + if (nodePath.startsWith("/")) { return categoryNodeToPath.get(category.getDisplayName()) + nodePath.toUpperCase(); - }else{ + } else { return categoryNodeToPath.get(category.getDisplayName()) + "/" + nodePath.toUpperCase(); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 41e8218aa7..e3c678a719 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -2292,9 +2292,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()}); currentJob.setProcessingStage(AutoIngestJob.Stage.OPENING_CASE, Date.from(Instant.now())); /* - * Acquire and hold a case name lock so that only one node at as - * time can scan the output directory at a time. This prevents - * making duplicate cases for the same auto ingest case. + * Acquire and hold a case name lock so that only one node at a time + * can search the output directory for an existing case. This + * prevents making duplicate cases for the same auto ingest case. */ try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, 30, TimeUnit.MINUTES)) { if (null != caseLock) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java index 36a376e3a0..402714a021 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsCollector.java @@ -96,7 +96,7 @@ final class AutoIngestMetricsCollector { return newMetricsSnapshot; - } catch (CoordinationService.CoordinationServiceException ex) { + } catch (CoordinationService.CoordinationServiceException | InterruptedException ex) { LOGGER.log(Level.SEVERE, "Failed to get node list from coordination service", ex); return new MetricsSnapshot(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 16fc961c4b..1251155100 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -378,7 +378,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen return newJobsSnapshot; - } catch (CoordinationServiceException ex) { + } catch (CoordinationServiceException | InterruptedException ex) { LOGGER.log(Level.SEVERE, "Failed to get node list from coordination service", ex); return new JobsSnapshot(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index d9bb8ec6bf..189e72954a 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -180,26 +180,31 @@ DeleteCaseInputAndOutputAction.taskName=input-and-output DeleteCaseOutputAction.menuItemText=Delete Output DeleteCaseOutputAction.progressDisplayName=Delete Output DeleteCaseOutputAction.taskName=output -DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock -DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock +DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring an exclusive case directory lock... +DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock... DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks -DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service -DeleteCaseTask.progress.deletingCaseOutput=Deleting case output -DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock node +DeleteCaseTask.progress.acquiringManifestFileLocks=Acquiring exclusive manifest file locks... +DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks... +DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service... +DeleteCaseTask.progress.deletingCaseOutput=Deleting case database, text index, and directory... +DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node... # {0} - input directory name -DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0} +DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}... DeleteCaseTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes -DeleteCaseTask.progress.deletingInputDirs=Deleting input directory -DeleteCaseTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node -DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node +DeleteCaseTask.progress.deletingInputDirs=Deleting input directory... +DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job log lock node... +DeleteCaseTask.progress.deletingManifestFileLockNodes=Deleting manifest file lock nodes... +DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node... DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources lock node -DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs -DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file -# {0} - input directory name -DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0} +DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs... +DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file... +DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on manifest {0} +# {0} - manifest file name +DeleteCaseTask.progress.lockingManifestFile=Acquiring exclusive lock on manifest {0}... # {0} - manifest file path -DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0} -DeleteCaseTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks +DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}... +DeleteCaseTask.progress.releasingManifestLocks=Releasing exclusive manifest file locks... +DeleteCaseTask.progress.startMessage=Preparing for deletion... GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz) HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java index bd2c38fa14..518e92265c 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java @@ -38,19 +38,24 @@ final class DeleteCaseInputAndOutputTask extends DeleteCaseTask { } @Override - void deleteWhileHoldingAllLocks() { + void deleteWhileHoldingAllLocks() throws InterruptedException { deleteInputDirectories(); deleteCaseOutput(); } @Override - void deleteAfterCaseLocksReleased() { - deleteCaseLockNodes(); + void deleteAfterManifestLocksReleased() throws InterruptedException { + deleteManifestFileLockNodes(); + } + + @Override + void deleteAfterCaseDirectoryLockReleased() throws InterruptedException { + this.deleteCaseDirectoryLockNode(); } @Override - void deleteAfterAllLocksReleased() { - deleteInputDirectoryLockNodes(); + void deleteAfterCaseNameLockReleased() throws InterruptedException { + this.deleteCaseNameLockNode(); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java index 0dd1ff7837..38bb494235 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java @@ -24,18 +24,18 @@ import org.sleuthkit.autopsy.progress.ProgressIndicator; /** * A task to delete the auto ingest job input directories for a case produced * via auto ingest, while leaving the auto ingest job coordination service nodes - * and the rest of the case intact. The use case is freeing space while - * retaining the option to restore the input directories, effectively restoring - * the case. + * (manifest file lock nodes) and the rest of the case intact. The use case is + * freeing space while retaining the option to restore the input directories, + * effectively restoring the case. */ final class DeleteCaseInputTask extends DeleteCaseTask { /** - * Constructs a task to delete the auto ingest job input directories for a + * Constructs task to delete the auto ingest job input directories for a * case produced via auto ingest, while leaving the auto ingest job - * coordination service nodes and the rest of the case intact. The use case - * is freeing space while retaining the option to restore the input - * directories, effectively restoring the case. + * coordination service nodes (manifest file lock nodes) and the rest of the + * case intact. The use case is freeing space while retaining the option to + * restore the input directories, effectively restoring the case. * * @param caseNodeData The case directory lock coordination service node * data for the case. @@ -46,16 +46,20 @@ final class DeleteCaseInputTask extends DeleteCaseTask { } @Override - void deleteWhileHoldingAllLocks() { + void deleteWhileHoldingAllLocks() throws InterruptedException { + } + + @Override + void deleteAfterCaseDirectoryLockReleased() throws InterruptedException { + } + + @Override + void deleteAfterCaseNameLockReleased() throws InterruptedException { deleteInputDirectories(); } @Override - void deleteAfterCaseLocksReleased() { - } - - @Override - void deleteAfterAllLocksReleased() { + void deleteAfterManifestLocksReleased() throws InterruptedException { } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index b421e74fa5..bfcae6b39b 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -18,12 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.Collection; -import javax.swing.AbstractAction; import org.openide.util.NbBundle; -import org.openide.util.Utilities; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.progress.ProgressIndicator; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java index e1d7921904..7b20dea7f8 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java @@ -45,17 +45,22 @@ final class DeleteCaseOutputTask extends DeleteCaseTask { } @Override - void deleteWhileHoldingAllLocks() { + void deleteWhileHoldingAllLocks() throws InterruptedException { deleteCaseOutput(); } @Override - void deleteAfterCaseLocksReleased() { + void deleteAfterCaseDirectoryLockReleased() throws InterruptedException { } @Override - void deleteAfterAllLocksReleased() { - deleteInputDirectoryLockNodes(); + void deleteAfterManifestLocksReleased() throws InterruptedException { + deleteManifestFileLockNodes(); + } + + @Override + void deleteAfterCaseNameLockReleased() throws InterruptedException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 815ab3e8fb..295bd0dc3e 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -29,138 +30,74 @@ 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.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseCoordinationServiceUtils; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.progress.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.TimeStampUtils; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException; /** - * A base class for tasks that delete part or all of a case produced via auto - * ingest. + * A base class for tasks that delete part or all of a given case. */ abstract class DeleteCaseTask implements Runnable { - private static final String RESOURCES_LOCK_SUFFIX = "_resources"; //NON-NLS private static final Logger logger = AutoIngestDashboardLogger.getLogger(); private final CaseNodeData caseNodeData; + private final String caseDisplayName; + private final String caseUniqueName; + private final Path caseDirectoryPath; private final ProgressIndicator progress; private final List nodeDataForAutoIngestJobs; - private final Map manifestFileLocks; + private final Map manifestFileLocks; private CoordinationService coordinationService; /** * Constructs the base class part of a task that deletes part or all of a - * case produced via auto ingest. + * given case. * * @param caseNodeData The case directory lock coordination service node - * data for the case to be deleted. + * data for the case. * @param progress A progress indicator. */ DeleteCaseTask(CaseNodeData caseNodeData, ProgressIndicator progress) { this.caseNodeData = caseNodeData; this.progress = progress; + /* + * Design Decision Note: It was decided to add the following state to + * instances of this class make it easier to access given that the class + * design favors instance methods over static methods. + */ + this.caseDisplayName = caseNodeData.getDisplayName(); + this.caseUniqueName = caseNodeData.getName(); + this.caseDirectoryPath = caseNodeData.getDirectory(); this.nodeDataForAutoIngestJobs = new ArrayList<>(); this.manifestFileLocks = new HashMap<>(); } @Override @NbBundle.Messages({ - "DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service", - "DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock", - "DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock", - "DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs", - "DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks" + "DeleteCaseTask.progress.startMessage=Preparing for deletion..." }) public void run() { try { - progress.start(Bundle.DeleteCaseTask_progress_connectingToCoordSvc()); - try { - coordinationService = CoordinationService.getInstance(); - } catch (CoordinationService.CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to connect to the coordination service to delete %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - return; - } - - /* - * Acquire an exclusive case name lock. This is the lock that auto - * ingest nodes acquire when creating or opening a case specified in - * an auto ingest job manifest file. Acquiring this lock prevents - * auto ingest nodes from searching for and finding the case - * directory of the case to be deleted. - */ - progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock()); - logger.log(Level.INFO, String.format("Exclusively locking the case name for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - final String caseNameLockName = TimeStampUtils.removeTimeStamp(caseNodeData.getName()); - try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) { - if (nameLock == null) { - logger.log(Level.WARNING, String.format("Failed to exclusively lock the case name for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - return; - } - - /* - * Acquire an exclusive case directory lock. This is the lock - * that is aquired by any node (auto ingest or examiner) - * attempting to create or open a case and is held by such a - * node for as long as the case is open. Acquiring this lock - * ensures that no other node currently has the case to be - * deleted open and prevents another node from trying to open - * the case as it is being deleted. - */ - progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock()); - logger.log(Level.INFO, String.format("Exclusively locking the case directory for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - try (CoordinationService.Lock caseLock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseNodeData.getDirectory().toString())) { - if (caseLock == null) { - logger.log(Level.WARNING, String.format("Failed to exclusively lock the case directory for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - return; - } - - progress.progress(Bundle.DeleteCaseTask_progress_gettingJobNodeData()); - logger.log(Level.INFO, String.format("Fetching auto ingest job node data for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - try { - getAutoIngestJobNodeData(); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to fetch auto ingest job node data for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - return; - } - - if (!nodeDataForAutoIngestJobs.isEmpty()) { - progress.progress(Bundle.DeleteCaseTask_progress_acquiringInputDirLocks()); - logger.log(Level.INFO, String.format("Exclusively locking the case directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - getInputDirectoryLocks(); - if (manifestFileLocks.isEmpty()) { - logger.log(Level.WARNING, String.format("Failed to exclusively lock the input directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - return; - } - } else { - logger.log(Level.INFO, String.format("No auto ingest job node data found for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - } - - deleteWhileHoldingAllLocks(); - - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error acquiring exclusive case directory lock for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - } - - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error acquiring exclusive case name lock for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - } - - deleteAfterCaseLocksReleased(); - releaseInputDirectoryLocks(); - deleteAfterAllLocksReleased(); + progress.start(Bundle.DeleteCaseTask_progress_startMessage()); + logger.log(Level.INFO, String.format("Beginning deletion of %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + deleteCase(); + logger.log(Level.SEVERE, String.format("Deletion of %s (%s) in %s completed", caseDisplayName, caseUniqueName, caseDirectoryPath)); } catch (Throwable ex) { /* - * Unexpected runtime exceptions firewall. + * Unexpected runtime exceptions firewall. This task is designed to + * be able to be run in an executor service thread pool without + * calling get() on the task's Future, so this ensures that + * such errors do get ignored. */ - logger.log(Level.SEVERE, String.format("Unexpected error deleting %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); + logger.log(Level.INFO, String.format("Unexpected error deleting %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); } finally { progress.finish(); @@ -169,224 +106,392 @@ abstract class DeleteCaseTask implements Runnable { } /** - * Deletes the parts of the case that need to be deleted while holding the - * following exclusive locks: case name lock, case directory lock, and all - * input directory locks. - */ - abstract void deleteWhileHoldingAllLocks(); - - /** - * Deletes the parts of the case that need to be deleted after releasing - * exclusive locks on the case name and case directory, but while still - * holding exclusive locks on all of the input directories. - */ - abstract void deleteAfterCaseLocksReleased(); - - /** - * Deletes the parts of the case that need to be deleted after all of the - * locks are released. - */ - abstract void deleteAfterAllLocksReleased(); - - /** - * Deletes the auto ingest job input directories for the case. Should only - * be called when holding all of the exclusive locks for the case. + * Deletes part or all of the given case. */ @NbBundle.Messages({ - "DeleteCaseTask.progress.deletingInputDirs=Deleting input directory", - "# {0} - input directory name", "DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}" + "DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...", + "DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock...", + "DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring an exclusive case directory lock...", + "DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs...", + "DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks..." }) - protected void deleteInputDirectories() { - progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDirs()); - logger.log(Level.INFO, String.format("Deleting input directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) { - final Path inputDirPath = jobNodeData.getManifestFilePath().getParent(); - progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDir(inputDirPath)); - logger.log(Level.INFO, String.format("Deleting input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - if (FileUtil.deleteDir(new File(inputDirPath.toString()))) { - logger.log(Level.WARNING, String.format("Failed to delete the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - // RJCTODO: Update deletion flags + private void deleteCase() { + progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc()); + logger.log(Level.INFO, String.format("Connecting to coordination service for deletion of %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + try { + coordinationService = CoordinationService.getInstance(); + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to connect to the coordination service, cannot delete %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + return; + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; + } + + /* + * Acquire an exclusive case name lock. This is the lock that auto + * ingest nodes acquire exclusively when creating or opening a case + * specified in an auto ingest job manifest file to ensure that only one + * auto ingest node at a time can search the auto ingest output + * directory for an existing case matching the one in the manifest file. + * Acquiring this lock effectively locks auto ingest node job processing + * tasks out of the case to be deleted. + */ + progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock()); + logger.log(Level.INFO, String.format("Acquiring an exclusive case name lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath); + try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockNodeName)) { + if (nameLock == null) { + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case name lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; + } + + /* + * Acquire an exclusive case directory lock. A shared case directory + * lock is acquired by any node (auto ingest or examiner) when it + * opens a case and is held by the node for as long as the case is + * open. Acquiring this lock exclusively ensures that no other node + * currently has the case to be deleted open and prevents another + * node from trying to open the case while it is being deleted. + */ + progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock()); + logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + String caseDirLockNodeName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseDirectoryPath); + try (CoordinationService.Lock caseDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirLockNodeName)) { + if (caseDirLock == null) { + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; + } + + progress.progress(Bundle.DeleteCaseTask_progress_gettingJobNodeData()); + logger.log(Level.INFO, String.format("Fetching auto ingest job node data for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + try { + getAutoIngestJobNodeData(); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error fetching auto ingest job node data for %s (%s) in %s, cannot delete case", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + return; + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + return; + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; + } + + if (!nodeDataForAutoIngestJobs.isEmpty()) { + progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); + logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + getManifestFileLocks(); + if (manifestFileLocks.isEmpty()) { + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; + } + } else { + logger.log(Level.INFO, String.format("No auto ingest job node data found for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + releaseManifestFileLocks(); + return; + } + + try { + deleteWhileHoldingAllLocks(); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + return; + } + + releaseManifestFileLocks(); + + try { + deleteAfterManifestLocksReleased(); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + return; + } + + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error acquiring exclusive case directory lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + return; + } + + try { + deleteAfterCaseDirectoryLockReleased(); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + return; + } + + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error acquiring exclusive case name lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + } + + try { + deleteAfterCaseNameLockReleased(); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + return; } } /** - * Deletes the case directory, the case database, and the text index for the - * case. Should only be called when holding all of the exclusive locks for - * the case. + * Deletes the parts of the case that need to be deleted while holding all + * of the exclusive locks: the case name lock, the case directory lock, amd + * the manifest file locks. Note that the locks are acquired in that order + * and released in the opposite order. + */ + abstract void deleteWhileHoldingAllLocks() throws InterruptedException; + + /** + * Deletes the parts of the case that need to be deleted after the release + * of the exclusive manifest file locks, while still holding the exclusive + * case name and case directory locks; the manifest file locks are the first + * locks released. + */ + abstract void deleteAfterManifestLocksReleased() throws InterruptedException; + + /** + * Deletes the parts of the case that need to be deleted after the release + * of the exclusive manifest file locks and case directory lock, while still + * holding the exclusive case name; the case name lock is the last lock + * released. + */ + abstract void deleteAfterCaseDirectoryLockReleased() throws InterruptedException; + + /** + * Deletes the parts of the case that need to be deleted after the release + * of all of the exclusive locks; the case name lock is the last lock + * released. + */ + abstract void deleteAfterCaseNameLockReleased() throws InterruptedException; + + /** + * Deletes the auto ingest job input directories for the case. Intended to + * be called by subclasses, if required, in their customization of the + * deleteWhileHoldingAllLocks step of the case deletion algorithm. */ @NbBundle.Messages({ - "DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file", - "DeleteCaseTask.progress.deletingCaseOutput=Deleting case output" + "# {0} - input directory name", "DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}..." + }) + protected void deleteInputDirectories() { + boolean allInputDirsDeleted = true; + for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) { + Path inputDirPath = jobNodeData.getManifestFilePath().getParent(); + File inputDir = inputDirPath.toFile(); + if (inputDir.exists()) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDir(inputDirPath)); + logger.log(Level.INFO, String.format("Deleting input directory %s for %s (%s) in %s", inputDirPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); + if (!FileUtil.deleteDir(new File(inputDirPath.toString()))) { + logger.log(Level.WARNING, String.format("Failed to delete the input directory %s for %s (%s) in %s", inputDirPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); + allInputDirsDeleted = false; + } + } + } + if (allInputDirsDeleted) { + setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES); + } + } + + /** + * Deletes the case database, the text index, and the case directory for the + * case. Intended to be called by subclasses, if required, in their + * customization of the deleteWhileHoldingAllLocks step of the case deletion + * algorithm. + */ + @NbBundle.Messages({ + "DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file...", + "DeleteCaseTask.progress.deletingCaseOutput=Deleting case database, text index, and directory...", + "DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job log lock node..." }) protected void deleteCaseOutput() { progress.progress(Bundle.DeleteCaseTask_progress_locatingCaseMetadataFile()); - logger.log(Level.INFO, String.format("Locating metadata file for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - String metadataFilePath = null; - final File caseDirectory = caseNodeData.getDirectory().toFile(); + logger.log(Level.INFO, String.format("Locating metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + CaseMetadata caseMetadata = null; + final File caseDirectory = caseDirectoryPath.toFile(); final File[] filesInDirectory = caseDirectory.listFiles(); if (filesInDirectory != null) { for (File file : filesInDirectory) { if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { - metadataFilePath = file.getPath(); + try { + caseMetadata = new CaseMetadata(Paths.get(file.getPath())); + } catch (CaseMetadata.CaseMetadataException ex) { + logger.log(Level.WARNING, String.format("Error getting opening case metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + } + break; } } } - if (metadataFilePath != null) { + if (caseMetadata != null) { progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseOutput()); - logger.log(Level.INFO, String.format("Deleting output for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - try { - Case.deleteCase(new CaseMetadata(Paths.get(metadataFilePath)), false, progress); - } catch (CaseMetadata.CaseMetadataException | CaseActionException ex) { - // RJCTODO: Set delete flags? - logger.log(Level.WARNING, String.format("Error deleting output for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - } + logger.log(Level.INFO, String.format("Deleting output for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress); // RJCTODO: Make this method throw the interrupted exception. } else { - logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - // RJCTODO: Set delete flags + logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); } - } - /** - * Deletes the case name, case directory, case resources, and case auto - * ingest log for the case. Should only be called when holding the exclusive - * locks for all of the input directories for the case. - */ - @Messages({ - "DeleteCaseTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node", - "DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources lock node", - "DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock node", - "DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node" - }) - protected void deleteCaseLockNodes() { progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); - logger.log(Level.INFO, String.format("Deleting case auto ingest job log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - Path logFilePath = AutoIngestJobLogger.getLogPath(caseNodeData.getDirectory()); + logger.log(Level.INFO, String.format("Deleting case auto ingest job log lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + Path logFilePath = AutoIngestJobLogger.getLogPath(caseDirectoryPath); //RJCTODO: USe util here try { coordinationService.deleteNode(CategoryNode.CASES, logFilePath.toString()); } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting auto ingest job log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - // RJCTODO: Set delete flags - } - - progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); - logger.log(Level.INFO, String.format("Deleting case resources log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX; - try { - coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case resources lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - // RJCTODO: Set delete flags - } - - progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode()); - logger.log(Level.INFO, String.format("Deleting case directory lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - final Path caseDirectoryPath = caseNodeData.getDirectory(); - try { - coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryPath.toString()); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - // RJCTODO: Set delete flags - } - - progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode()); - logger.log(Level.INFO, String.format("Deleting case name lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - final String caseNameLockName = TimeStampUtils.removeTimeStamp(caseNodeData.getName()); - try { - coordinationService.deleteNode(CategoryNode.CASES, caseNameLockName); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - // RJCTODO: Set delete flags + logger.log(Level.WARNING, String.format("Error deleting case auto ingest job log lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); } } /** - * Deletes the input directory lock nodes for the case. Should only be - * called after releasing all of the locks for the case. + * Deletes the manifest file lock coordination service nodes for the case. + * Intended to be called by subclasses, if required, in their customization + * of the deleteAfterManifestLocksReleased step of the case deletion + * algorithm. */ @Messages({ - "DeleteCaseTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes" + "DeleteCaseTask.progress.deletingManifestFileLockNodes=Deleting manifest file lock nodes..." }) - protected void deleteInputDirectoryLockNodes() { - progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDirLockNodes()); - logger.log(Level.INFO, String.format("Deleting input directory lock nodes for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + protected void deleteManifestFileLockNodes() throws InterruptedException { + boolean allInputDirsDeleted = true; + progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileLockNodes()); + logger.log(Level.INFO, String.format("Deleting manifest file lock nodes for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) { try { - logger.log(Level.INFO, String.format("Deleting manifest file lock node for %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + logger.log(Level.INFO, String.format("Deleting manifest file lock node for %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseDisplayName, caseUniqueName, caseDirectoryPath)); coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, jobNodeData.getManifestFilePath().toString()); } catch (CoordinationServiceException ex) { - Path inputDirPath = jobNodeData.getManifestFilePath().getParent(); - logger.log(Level.WARNING, String.format("Error deleting input directory lock node %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - // RJCTODO: Set delete flags + logger.log(Level.WARNING, String.format("Error deleting manifest file lock node %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + allInputDirsDeleted = false; } } + if (allInputDirsDeleted) { + setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_LOCK_NODES); + } + // RJCTODO: Expand case type in case metadata to include auto ingest cases. + // Disable delete menu item for auto ingest cases, and possibly also add data source + // capability. + } + + /** + * Deletes the case directory coordination service lock node for the case. + * Intended to be called by subclasses, if required, in their customization + * of the deleteAfterCaseDirectoryLockReleased step of the case deletion + * algorithm. + */ + @Messages({ + "DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node..." + }) + protected void deleteCaseDirectoryLockNode() throws InterruptedException { + progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode()); + try { + Case.deleteCaseDirectoryLockNode(caseNodeData, progress); // RJCTODO: Case does not need to expose this? + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + } } /** - * Fetches all of the auto ingest job (manifest file) lock coordination - * service node data for a case. + * Deletes the case name coordination service lock node for the case. + * Intended to be called by subclasses, if required, in their customization + * of the deleteAfterCaseNameLockReleased step of the case deletion + * algorithm. * - * @param caseName The name of the case. - * - * @return A list of auto ingest job (manifest file) lock node data for the - * case. - * - * @throws CoordinationServiceException If there is an error getting a list - * of the auto ingest job (manifest - * file) lock nodes from the - * coordination service. + * @throws InterruptedException */ - private void getAutoIngestJobNodeData() throws CoordinationServiceException { - String caseName = caseNodeData.getName(); - final List nodes = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); - for (String nodeName : nodes) { + @Messages({ + "DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node..." // RJCTODO: Use consistent terminology + }) + protected void deleteCaseNameLockNode() throws InterruptedException { + progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode()); + try { + String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath); + coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + } + } + + /** + * Fetches the auto ingest job data from the manifest file lock coordination + * service nodes for a case. + * + * @throws CoordinationServiceException If there is an error interacting + * with the coordination service. + * @throws InterruptedException If the current thread is interrupted + * while waiting for the coordination + * service. + */ + private void getAutoIngestJobNodeData() throws CoordinationServiceException, InterruptedException { + String caseName = caseDisplayName; + final List nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); + for (String nodeName : nodeNames) { try { byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); if (nodeBytes == null || nodeBytes.length <= 0) { + logger.log(Level.WARNING, String.format("Missing auto ingest job node data for manifest file lock node %s, deleting node", nodeName)); + try { + coordinationService.deleteNode(CategoryNode.MANIFESTS, nodeName); + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Failed to delete empty manifest file lock node %s", nodeName)); + } continue; } AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes); if (caseName.equals(nodeData.getCaseName())) { nodeDataForAutoIngestJobs.add(nodeData); } - } catch (CoordinationService.CoordinationServiceException | InterruptedException | InvalidDataException ex) { - logger.log(Level.WARNING, String.format("Failed to get coordination service node data for %s", nodeName), ex); + } catch (CoordinationService.CoordinationServiceException | InvalidDataException ex) { + logger.log(Level.WARNING, String.format("Failed to get auto ingest job node data for %s", nodeName), ex); } } } /** - * Acquires either all or none of the input directory locks for a case. - * - * @param caseNodeData The case node data from the case - * directory lock node for the case. - * @param autoIngestJobNodeDataList The auto ingest job node data from the - * input directory lock nodes for the case. - * - * @return A mapping of manifest file paths to input directory lcoks for all - * input directories for the case; will be empty if all of the locks - * could not be obtained. + * Acquires either all or none of the manifest file locks for a case. */ @NbBundle.Messages({ - "# {0} - input directory name", "DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0}",}) - private void getInputDirectoryLocks() { + "# {0} - manifest file name", "DeleteCaseTask.progress.lockingManifestFile=Acquiring exclusive lock on manifest {0}..." + }) + private void getManifestFileLocks() { for (AutoIngestJobNodeData autoIngestJobNodeData : nodeDataForAutoIngestJobs) { - final Path inputDirPath = autoIngestJobNodeData.getManifestFilePath().getParent(); + String manifestPath = autoIngestJobNodeData.getManifestFilePath().toString(); try { - progress.progress(Bundle.DeleteCaseTask_progress_lockingInputDir(inputDirPath)); - final CoordinationService.Lock inputDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, autoIngestJobNodeData.getManifestFilePath().toString()); + progress.progress(Bundle.DeleteCaseTask_progress_lockingManifestFile(manifestPath)); + logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); + CoordinationService.Lock inputDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath); if (null != inputDirLock) { - manifestFileLocks.put(autoIngestJobNodeData.getManifestFilePath(), inputDirLock); + manifestFileLocks.put(manifestPath, inputDirLock); } else { - logger.log(Level.WARNING, String.format("Failed to exclusively lock the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - releaseInputDirectoryLocks(); + logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); + releaseManifestFileLocks(); manifestFileLocks.clear(); + break; } } catch (CoordinationService.CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to exclusively lock the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - releaseInputDirectoryLocks(); + logger.log(Level.SEVERE, String.format("Error exclusively locking the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + releaseManifestFileLocks(); manifestFileLocks.clear(); + break; } } } @@ -396,23 +501,36 @@ abstract class DeleteCaseTask implements Runnable { * for the case. */ @NbBundle.Messages({ - "DeleteCaseTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks", - "# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}" + "# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}..." }) - private void releaseInputDirectoryLocks() { + private void releaseManifestFileLocks() { if (!manifestFileLocks.isEmpty()) { - progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLocks()); - for (Map.Entry entry : manifestFileLocks.entrySet()) { - final Path manifestFilePath = entry.getKey(); - final CoordinationService.Lock manifestFileLock = entry.getValue(); + for (Map.Entry entry : manifestFileLocks.entrySet()) { + String manifestFilePath = entry.getKey(); + CoordinationService.Lock manifestFileLock = entry.getValue(); try { progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); + logger.log(Level.INFO, String.format("Releasing the exclusive lock on the manifest file %s for %s (%s) in %s", manifestFilePath, caseDisplayName, caseUniqueName, caseDirectoryPath)); manifestFileLock.release(); } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error re3leasing exclusive lock on %s for %s (%s) in %s", manifestFilePath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); + logger.log(Level.SEVERE, String.format("Error releasing exclusive lock on the manifest file %s for %s (%s) in %s", manifestFilePath, caseDisplayName, caseUniqueName, caseDirectoryPath), ex); } } } } + /** + * Sets a deleted item flag for the case. + * + * @param flag The flag to set. + */ + private void setDeletedItemFlag(CaseNodeData.DeletedFlags flag) { + try { + caseNodeData.setDeletedFlag(flag); + coordinationService.setNodeData(CategoryNode.CASES, caseDirectoryPath.toString(), caseNodeData.toArray()); + } catch (IOException | CoordinationServiceException | InterruptedException ex) { + logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + } + } + } From 95c3cf2d74b6717bf7411ad0bb98a251e36e88d1 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 19 Mar 2019 15:12:41 -0400 Subject: [PATCH 07/32] Interim commit of improved case deletion --- .../sleuthkit/autopsy/casemodule/Case.java | 82 +- .../CaseCoordinationServiceUtils.java | 2 +- .../MultiUserCaseNodeDataCollector.java | 2 +- .../MultiUserCaseBrowserCustomizer.java | 16 +- .../MultiUserCaseNode.java | 33 + .../autoingest/AutoIngestJob.java | 2 +- .../autoingest/AutoIngestManager.java | 524 ++++++------ .../autoingest/CasesDashboardCustomizer.java | 5 + .../CasesDashboardTopComponent.java | 2 - .../autoingest/DeleteCaseInputAction.java | 3 +- .../DeleteCaseInputAndOutputAction.java | 3 +- .../autoingest/DeleteCaseOutputAction.java | 3 +- .../autoingest/DeleteCaseTask.java | 793 ++++++++++-------- 13 files changed, 796 insertions(+), 674 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 7a4ed43043..efadae5fb7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -32,11 +32,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -60,7 +60,6 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; 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; @@ -100,7 +99,6 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.ThreadUtils; -import org.sleuthkit.autopsy.coreutils.TimeStampUtils; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.events.AutopsyEvent; @@ -717,7 +715,8 @@ public class Case { * lower-level exception. */ @Messages({ - "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first." + "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.", + "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled." }) public static void deleteCase(CaseMetadata metadata) throws CaseActionException { synchronized (caseActionSerializationLock) { @@ -737,7 +736,16 @@ public class Case { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { deleteSingleUserCase(metadata, progressIndicator); } else { - deleteMultiUserCase(metadata, progressIndicator); + try { + deleteMultiUserCase(metadata, progressIndicator); + } catch (InterruptedException ex) { + /* + * Task cancellation is not currently supported for this + * code path, so this catch block is not expected to be + * executed. + */ + throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex); + } } } finally { progressIndicator.finish(); @@ -978,7 +986,7 @@ public class Case { "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.", "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case..." }) - private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException { progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc()); CoordinationService coordinationService; try { @@ -1010,8 +1018,17 @@ public class Case { throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } - errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator); + errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger); + try { + deleteCaseResourcesLockNode(caseNodeData, progressIndicator); + } catch (CoordinationServiceException | InterruptedException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + } + + // RJCTODO: Is this behavior implemented correctly? + } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); @@ -1034,15 +1051,16 @@ public class Case { /** * IMPORTANT: This is a "beta" method and is subject to change or removal * without notice! - * + * * Attempts to delete the case database, the text index, the case directory, * and the case resources coordination service lock code for a case and - * removes the case from the recent cases menu of the mian application + * removes the case from the recent cases menu of the main application * window. * * @param caseNodeData The coordination service node data for the case. * @param metadata The case metadata. * @param progressIndicator A progress indicator. + * @param logger A logger. * * @return True if one or more errors occurred (see log for details), false * otherwise. @@ -1053,10 +1071,10 @@ public class Case { * during a wait. */ @Beta - public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws InterruptedException { + public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { boolean errorsOccurred = false; try { - deleteCaseDatabase(caseNodeData, metadata, progressIndicator); + deleteCaseDatabase(caseNodeData, metadata, progressIndicator, logger); } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { errorsOccurred = true; logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); @@ -1066,7 +1084,7 @@ public class Case { } try { - deleteTextIndex(caseNodeData, metadata, progressIndicator); + deleteTextIndex(caseNodeData, metadata, progressIndicator, logger); } catch (KeywordSearchServiceException ex) { errorsOccurred = true; logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); @@ -1076,7 +1094,7 @@ public class Case { } try { - deleteCaseDirectory(caseNodeData, metadata, progressIndicator); + deleteCaseDirectory(caseNodeData, metadata, progressIndicator, logger); } catch (CaseActionException ex) { errorsOccurred = true; logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); @@ -1085,23 +1103,6 @@ public class Case { return errorsOccurred; } - deleteFromRecentCases(metadata, progressIndicator); - - if (Thread.currentThread().isInterrupted()) { - Thread.currentThread().interrupt(); - return errorsOccurred; - } - - try { - deleteCaseResourcesLockNode(caseNodeData, progressIndicator); - } catch (CoordinationServiceException ex) { - errorsOccurred = true; - logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - return errorsOccurred; - } - return errorsOccurred; } @@ -1127,15 +1128,20 @@ public class Case { @Messages({ "Case.progressMessage.deletingCaseDatabase=Deleting case database..." }) - private static void deleteCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { + private static void deleteCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); + logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo(); String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS Class.forName("org.postgresql.Driver"); //NON-NLS try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { - String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS - statement.execute(deleteCommand); + String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; + ResultSet queryResult = statement.executeQuery(dbExistsQuery); + if (queryResult.next()) { + String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS + statement.execute(deleteCommand); + } } setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DB); } @@ -1174,8 +1180,9 @@ public class Case { * data to be written to the * coordination service node database. */ - private static void deleteTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException, InterruptedException { + private static void deleteTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException { if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) { + logger.log(Level.INFO, String.format("Deleting text index for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); deleteTextIndex(metadata, progressIndicator); setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX); } @@ -1198,6 +1205,7 @@ public class Case { // when the path is >= 255 chars. Actually, deprecate this method and // replace it with one that throws instead of returning a boolean value. progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); + logger.log(Level.INFO, String.format("Deleting case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); } @@ -1216,7 +1224,7 @@ public class Case { * coordination service data to be written to * the coordination service node database. */ - private static void deleteCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException { + private static void deleteCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException { if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) { deleteCaseDirectory(metadata, progressIndicator); setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR); @@ -1242,7 +1250,6 @@ public class Case { } } - // RJCTODO: Copy-paste instead /** * IMPORTANT: This is a "beta" method and is subject to change or removal * without notice! @@ -1264,14 +1271,13 @@ public class Case { "Case.progressMessage.deletingResourcesLockNode=Deleting case resources lock node..." }) @Beta - private static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException { + public static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException { progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode()); String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX;//RJCTODO: Use utility CoordinationService coordinationService = CoordinationService.getInstance(); coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); } - // RJCTODO: Copy-paste instead /** * IMPORTANT: This is a "beta" method and is subject to change or removal * without notice! diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java index cda266cb8d..ae252e1988 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java @@ -42,7 +42,7 @@ public class CaseCoordinationServiceUtils { return caseDirectoryPath.toString(); } - public static String getCaseLockName(Path caseDirectoryPath) { + public static String getCaseNameLockName(Path caseDirectoryPath) { String caseName = caseDirectoryPath.getFileName().toString(); if (TimeStampUtils.endsWithTimeStamp(caseName)) { caseName = TimeStampUtils.removeTimeStamp(caseName); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java index f3fa64b924..6f17ec2d70 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java @@ -161,7 +161,7 @@ final public class MultiUserCaseNodeDataCollector { // RJCTODO: Shorten name aft deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseResourcesLockName(caseDirectoryPath)); deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseDirectoryPath)); deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseDirectoryPath)); - deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseNameLockName(caseDirectoryPath)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java index 3bccb63ed4..692e2f2cf1 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseBrowserCustomizer.java @@ -142,22 +142,22 @@ public interface MultiUserCaseBrowserCustomizer { "MultiUserCaseBrowserCustomizer.column.createTime=Create Time", "MultiUserCaseBrowserCustomizer.column.directory=Directory", "MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time", + "MultiUserCaseBrowserCustomizer.column.manifestFileZNodesDeleteStatus=Manifest Znodes Deleted", + "MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Deleted", "MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted", "MultiUserCaseBrowserCustomizer.column.caseDbDeleteStatus=Case Database Deleted", - "MultiUserCaseBrowserCustomizer.column.caseDirDeleteStatus=Case Directory Deleted", - "MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Deleted", - "MultiUserCaseBrowserCustomizer.column.manifestCoordSvcNodesDeleteStatus=Manifest ZooKeeper Node Deleted" + "MultiUserCaseBrowserCustomizer.column.caseDirDeleteStatus=Case Directory Deleted" }) public enum Column { DISPLAY_NAME(Bundle.MultiUserCaseBrowserCustomizer_column_displayName()), CREATE_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_createTime()), DIRECTORY(Bundle.MultiUserCaseBrowserCustomizer_column_directory()), LAST_ACCESS_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_lastAccessTime()), - TEXT_INDEX_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_textIndexDeleteStatus()), - CASE_DB_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDbDeleteStatus()), - CASE_DIR_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDirDeleteStatus()), - DATA_SOURCES_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_dataSourcesDeleteStatus()), - MANIFEST_FILE_LOCK_NODES_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_manifestCoordSvcNodesDeleteStatus()); + MANIFEST_FILE_ZNODES_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_manifestFileZNodesDeleteStatus()), + DATA_SOURCES_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_dataSourcesDeleteStatus()), + TEXT_INDEX_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_textIndexDeleteStatus()), + CASE_DB_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDbDeleteStatus()), + CASE_DIR_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDirDeleteStatus()); private final String displayName; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java index b54cae2238..a9237aa7a1 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java @@ -25,8 +25,10 @@ import javax.swing.Action; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.DeletedFlags; import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrowserCustomizer.Column; import org.sleuthkit.autopsy.datamodel.NodeProperty; @@ -75,6 +77,21 @@ final class MultiUserCaseNode extends AbstractNode { case LAST_ACCESS_DATE: sheetSet.put(new NodeProperty<>(propName, propName, propName, caseNodeData.getLastAccessDate())); break; + case MANIFEST_FILE_ZNODES_DELETE_STATUS: + sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_LOCK_NODES))); + break; + case DATA_SOURCES_DELETE_STATUS: + sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.DATA_SOURCES))); + break; + case TEXT_INDEX_DELETE_STATUS: + sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.TEXT_INDEX))); + break; + case CASE_DB_DELETE_STATUS: + sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.CASE_DB))); + break; + case CASE_DIR_DELETE_STATUS: + sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.CASE_DIR))); + break; default: break; } @@ -95,4 +112,20 @@ final class MultiUserCaseNode extends AbstractNode { return customizer.getPreferredAction(caseNodeData); } + /** + * Interprets the deletion status of part of a case. + * + * @param flag The coordination service node data deleted items flag + * to interpret. + * + * @return A string stating "True" or "False." + */ + @NbBundle.Messages({ + "MultiUserCaseNode.columnValue.true=True", + "MultiUserCaseNode.column.createTime=False", + }) + private String isDeleted(CaseNodeData.DeletedFlags flag) { + return caseNodeData.isDeletedFlagSet(flag) ? "True" : "False"; + } + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 56e2cb3015..7a9a446ae6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -33,7 +33,6 @@ import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.experimental.autoingest.Manifest; import org.sleuthkit.autopsy.ingest.DataSourceIngestJob.Snapshot; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager.IngestThreadActivitySnapshot; @@ -194,6 +193,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn this.ingestThreadsSnapshot = Collections.emptyList(); this.ingestJobsSnapshot = Collections.emptyList(); this.moduleRunTimesSnapshot = Collections.emptyMap(); + } catch (Exception ex) { throw new AutoIngestJobException(String.format("Error creating automated ingest job"), ex); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index e3c678a719..a6efc7105e 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 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import static java.nio.file.FileVisitOption.FOLLOW_LINKS; import java.nio.file.FileVisitResult; @@ -32,7 +33,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; -import java.sql.SQLException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -40,11 +40,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.EnumSet; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Observable; import java.util.Set; import java.util.UUID; @@ -70,7 +68,6 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.core.ServicesMonitor.ServicesMonitorException; -import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; @@ -126,6 +123,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private static final int NUM_INPUT_SCAN_SCHEDULING_THREADS = 1; private static final String INPUT_SCAN_SCHEDULER_THREAD_NAME = "AIM-input-scan-scheduler-%d"; private static final String INPUT_SCAN_THREAD_NAME = "AIM-input-scan-%d"; + private static final int INPUT_SCAN_LOCKING_TIMEOUT_MINS = 5; private static final String AUTO_INGEST_THREAD_NAME = "AIM-job-processing-%d"; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); private static final String EVENT_CHANNEL_NAME = "Auto-Ingest-Manager-Events"; @@ -145,6 +143,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private static final String JOB_STATUS_PUBLISHING_THREAD_NAME = "AIM-job-status-event-publisher-%d"; private static final long MAX_MISSED_JOB_STATUS_UPDATES = 10; private static final int DEFAULT_PRIORITY = 0; + private static String CASE_MANIFESTS_LIST_FILE_NAME = "auto-ingest-job-manifests.txt"; private static final Logger sysLogger = AutoIngestSystemLogger.getLogger(); private static AutoIngestManager instance; private final AutopsyEventPublisher eventPublisher; @@ -157,8 +156,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private final ConcurrentHashMap hostNamesToRunningJobs; private final Object jobsLock; @GuardedBy("jobsLock") - private final Map> casesToManifests; - @GuardedBy("jobsLock") private List pendingJobs; @GuardedBy("jobsLock") private AutoIngestJob currentJob; @@ -174,6 +171,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private volatile AutoIngestNodeStateEvent lastPublishedStateEvent; + static String getCaseManifestsListFileName() { + return CASE_MANIFESTS_LIST_FILE_NAME; + } + /** * Gets a singleton auto ingest manager responsible for processing auto * ingest jobs defined by manifest files that can be added to any level of a @@ -205,7 +206,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen hostNamesToRunningJobs = new ConcurrentHashMap<>(); hostNamesToLastMsgTime = new ConcurrentHashMap<>(); jobsLock = new Object(); - casesToManifests = new HashMap<>(); pendingJobs = new ArrayList<>(); completedJobs = new ArrayList<>(); try { @@ -694,7 +694,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen int oldPriority = job.getPriority(); job.setPriority(DEFAULT_PRIORITY); try { - this.updateCoordinationServiceManifestNode(job); + this.updateAutoIngestJobData(job); } catch (CoordinationServiceException | InterruptedException ex) { job.setPriority(oldPriority); throw new AutoIngestManagerException("Error updating case priority", ex); @@ -744,7 +744,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen int oldPriority = job.getPriority(); job.setPriority(maxPriority); try { - this.updateCoordinationServiceManifestNode(job); + this.updateAutoIngestJobData(job); } catch (CoordinationServiceException | InterruptedException ex) { job.setPriority(oldPriority); throw new AutoIngestManagerException("Error updating case priority", ex); @@ -796,7 +796,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen int oldPriority = jobToDeprioritize.getPriority(); jobToDeprioritize.setPriority(DEFAULT_PRIORITY); try { - this.updateCoordinationServiceManifestNode(jobToDeprioritize); + this.updateAutoIngestJobData(jobToDeprioritize); } catch (CoordinationServiceException | InterruptedException ex) { jobToDeprioritize.setPriority(oldPriority); throw new AutoIngestManagerException("Error updating job priority", ex); @@ -854,7 +854,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen int oldPriority = jobToPrioritize.getPriority(); jobToPrioritize.setPriority(maxPriority); try { - this.updateCoordinationServiceManifestNode(jobToPrioritize); + this.updateAutoIngestJobData(jobToPrioritize); } catch (CoordinationServiceException | InterruptedException ex) { jobToPrioritize.setPriority(oldPriority); throw new AutoIngestManagerException("Error updating job priority", ex); @@ -909,7 +909,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen completedJob.setCompletedDate(new Date(0)); completedJob.setProcessingStatus(PENDING); completedJob.setProcessingStage(AutoIngestJob.Stage.PENDING, Date.from(Instant.now())); - updateCoordinationServiceManifestNode(completedJob); + updateAutoIngestJobData(completedJob); pendingJobs.add(completedJob); } catch (CoordinationServiceException ex) { sysLogger.log(Level.SEVERE, String.format("Coordination service error while reprocessing %s", manifestPath), ex); @@ -996,15 +996,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } /** - * Sets the coordination service manifest node. - * - * Note that a new auto ingest job node data object will be created from the - * job passed in. Thus, if the data version of the node has changed, the - * node will be "upgraded" as well as updated. + * Writes the node data for an auto ingest job to the job's manifest file + * lock coordination service node. * * @param job The auto ingest job. */ - void updateCoordinationServiceManifestNode(AutoIngestJob job) throws CoordinationServiceException, InterruptedException { + void updateAutoIngestJobData(AutoIngestJob job) throws CoordinationServiceException, InterruptedException { AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(job); String manifestNodePath = job.getManifest().getFilePath().toString(); byte[] rawData = nodeData.toArray(); @@ -1016,14 +1013,21 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * * @param caseDirectoryPath The case directory path. * - * @throws CoordinationService.CoordinationServiceException - * @throws InterruptedException - * @throws IOException + * @throws CoordinationServiceException If there was an error getting the + * node data from the cooordination + * service. + * @throws IOException If the node data was missing or + * there was an error interpreting it. + * @throws InterruptedException If the thread running the input + * directory scan task is interrupted + * while blocked, i.e., if auto ingest + * is shutting down. */ - private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws CoordinationServiceException, InterruptedException, IOException { - CaseNodeData caseNodeData = new CaseNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString())); + private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws IOException, CoordinationServiceException, InterruptedException { + byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString()); + CaseNodeData caseNodeData = new CaseNodeData(rawData); caseNodeData.setErrorsOccurred(true); - byte[] rawData = caseNodeData.toArray(); + rawData = caseNodeData.toArray(); coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString(), rawData); } @@ -1088,6 +1092,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private final List newPendingJobsList = new ArrayList<>(); private final List newCompletedJobsList = new ArrayList<>(); + private Lock currentDirLock; /** * Searches the input directories for manifest files. The search results @@ -1109,9 +1114,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } catch (Exception ex) { /* - * NOTE: Need to catch all exceptions here. Otherwise - * uncaught exceptions will propagate up to the calling - * thread and may stop it from running. + * NOTE: Need to catch all unhandled exceptions here. + * Otherwise uncaught exceptions will propagate up to the + * calling thread and may stop it from running. */ sysLogger.log(Level.SEVERE, String.format("Error scanning the input directory %s", rootInputDirectory), ex); } @@ -1145,20 +1150,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } /** - * Invoked for a file in a directory. If the file is a manifest file, - * creates a pending pending or completed auto ingest job for the - * manifest, based on the data stored in the coordination service node - * for the manifest. - *

          - * Note that the mapping of case names to manifest paths that is used - * for case deletion is updated as well. + * Creates a pending or completed auto ingest job if the file visited is + * a manifest file, based on the data stored in the coordination service + * node for the manifest. * * @param filePath The path of the file. * @param attrs The file system attributes of the file. * * @return TERMINATE if auto ingest is shutting down, CONTINUE if it has * not. - * */ @Override public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) { @@ -1167,6 +1167,11 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } try { + /* + * Determine whether or not the file is an auto ingest job + * manifest file. If it is, then parse it. Otherwise, move on to + * the next file in the directory. + */ Manifest manifest = null; for (ManifestFileParser parser : Lookup.getDefault().lookupAll(ManifestFileParser.class)) { if (parser.fileIsManifest(filePath)) { @@ -1186,76 +1191,83 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen return TERMINATE; } - if (null != manifest) { - /* - * Update the mapping of case names to manifest paths that - * is used for case deletion. - */ - String caseName = manifest.getCaseName(); - Path manifestPath = manifest.getFilePath(); - if (casesToManifests.containsKey(caseName)) { - Set manifestPaths = casesToManifests.get(caseName); - manifestPaths.add(manifestPath); - } else { - Set manifestPaths = new HashSet<>(); - manifestPaths.add(manifestPath); - casesToManifests.put(caseName, manifestPaths); - } + if (manifest == null) { + return CONTINUE; + } - /* - * Add a job to the pending jobs queue, the completed jobs - * list, or do crashed job recovery, as required. - */ - try { - byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString()); + /* + * If a manifest file has been found, get a manifest file lock, + * analyze the job state, and put a job into the appropriate job + * list. There is a short wait here in case the input directory + * scanner file visitor of another auto ingest node (AIN) has + * the lock. If the lock ultmiately can't be obtained, the wait + * was not long enough, or another auto ingest node (AIN) is + * holding the lock because it is executing the job, or a case + * deletion task has aquired the lock. In all of these cases the + * manifest can be skipped for this scan. + */ + try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), INPUT_SCAN_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES)) { + if (null != manifestLock) { + byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString()); if (null != rawData && rawData.length > 0) { - try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData); - AutoIngestJob.ProcessingStatus processingStatus = nodeData.getProcessingStatus(); - switch (processingStatus) { - case PENDING: - addPendingJob(manifest, nodeData); - break; - case PROCESSING: - doRecoveryIfCrashed(manifest, nodeData); - break; - case COMPLETED: - addCompletedJob(manifest, nodeData); - break; - case DELETED: // No longer used, retained for legacy jobs only. - /* - * Ignore jobs marked as "deleted." - */ - break; - default: - sysLogger.log(Level.SEVERE, "Unknown ManifestNodeData.ProcessingStatus"); - break; - } - } catch (AutoIngestJobNodeData.InvalidDataException | AutoIngestJobException ex) { - sysLogger.log(Level.SEVERE, String.format("Invalid auto ingest job node data for %s", manifestPath), ex); + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData); + AutoIngestJob.ProcessingStatus processingStatus = nodeData.getProcessingStatus(); + switch (processingStatus) { + case PENDING: + addPendingJob(manifest, nodeData); + break; + case PROCESSING: + /* + * If an exclusive manifest file lock was + * obtained for an auto ingest job in the + * processing state, the auto ingest node + * (AIN) executing the job crashed and the + * lock was released when the coordination + * service detected that the AIN was no + * longer alive. + */ + doCrashRecovery(manifest, nodeData); + break; + case COMPLETED: + addCompletedJob(manifest, nodeData); + break; + case DELETED: + /* + * Ignore jobs marked as deleted. Note that + * this state is no longer used and is + * retained for legacy jobs only. + */ + break; + default: + sysLogger.log(Level.SEVERE, "Unknown ManifestNodeData.ProcessingStatus"); + break; } } else { try { addNewPendingJob(manifest); } catch (AutoIngestJobException ex) { - sysLogger.log(Level.SEVERE, String.format("Invalid manifest data for %s", manifestPath), ex); + sysLogger.log(Level.SEVERE, String.format("Invalid manifest data for %s", manifest.getFilePath()), ex); } } - } catch (CoordinationServiceException ex) { - sysLogger.log(Level.SEVERE, String.format("Error transmitting node data for %s", manifestPath), ex); - return CONTINUE; - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - return TERMINATE; } + } catch (CoordinationServiceException | AutoIngestJobException | AutoIngestJobNodeData.InvalidDataException ex) { + sysLogger.log(Level.SEVERE, String.format("Error handling manifest at %s", manifest.getFilePath()), ex); + } catch (InterruptedException ex) { + /* + * The thread running the input directory scan task was + * interrupted while blocked, i.e., auto ingest is shutting + * down. + */ + return TERMINATE; } } catch (Exception ex) { - // Catch all unhandled and unexpected exceptions. Otherwise one bad file - // can stop the entire input folder scanning. Given that the exception is unexpected, - // I'm hesitant to add logging which requires accessing or de-referencing data. - sysLogger.log(Level.SEVERE, "Unexpected exception in file visitor", ex); - return CONTINUE; + /* + * This is an exception firewall so that an unexpected runtime + * exception from the handling of a single manifest file does + * not take out the input directory scanner. + */ + sysLogger.log(Level.SEVERE, String.format("Unexpected exception handling %s", filePath), ex); } if (!Thread.currentThread().isInterrupted()) { @@ -1266,49 +1278,36 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } /** - * Adds an existing job to the pending jobs queue. + * Adds an auto ingest job to the pending jobs queue. * * @param manifest The manifest for the job. - * @param nodeData The data stored in the coordination service node for - * the job. + * @param nodeData The data stored in the manifest file lock + * coordination service node for the job. * - * @throws InterruptedException if the thread running the input - * directory scan task is interrupted while - * blocked, i.e., if auto ingest is - * shutting down. + * @throws AutoIngestJobException If there was an error working + * with the node data. + * @throws CoordinationServiceException If a lock node data version + * update was required and there + * was an error writing the node + * data by the coordination + * service. + * @throws InterruptedException If the thread running the input + * directory scan task is + * interrupted while blocked, i.e., + * if auto ingest is shutting down. */ - private void addPendingJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws InterruptedException, AutoIngestJobException { + private void addPendingJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws AutoIngestJobException, CoordinationServiceException, InterruptedException { AutoIngestJob job; if (nodeData.getVersion() == AutoIngestJobNodeData.getCurrentVersion()) { job = new AutoIngestJob(nodeData); } else { job = new AutoIngestJob(manifest); - job.setPriority(nodeData.getPriority()); // Retain priority, present in all versions of the node data. + job.setPriority(nodeData.getPriority()); Path caseDirectory = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); if (null != caseDirectory) { job.setCaseDirectoryPath(caseDirectory); } - - /* - * Try to upgrade/update the coordination service manifest node - * data for the job. - * - * An exclusive lock is obtained before doing so because another - * host may have already found the job, obtained an exclusive - * lock, and started processing it. However, this locking does - * make it possible that two processing hosts will both try to - * obtain the lock to do the upgrade operation at the same time. - * If this happens, the host that is holding the lock will - * complete the upgrade operation, so there is nothing more for - * this host to do. - */ - try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { - if (null != manifestLock) { - updateCoordinationServiceManifestNode(job); - } - } catch (CoordinationServiceException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); - } + updateAutoIngestJobData(job); } newPendingJobsList.add(job); } @@ -1318,150 +1317,117 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * * @param manifest The manifest for the job. * - * @throws InterruptedException if the thread running the input - * directory scan task is interrupted while - * blocked, i.e., if auto ingest is - * shutting down. + * @throws AutoIngestJobException If there was an error creating + * the node data. + * @throws CoordinationServiceException If there was an error writing + * the node data by the + * coordination service. + * @throws InterruptedException If the thread running the input + * directory scan task is + * interrupted while blocked, i.e., + * if auto ingest is shutting down. */ - private void addNewPendingJob(Manifest manifest) throws InterruptedException, AutoIngestJobException { - /* - * Create the coordination service manifest node data for the job. - * Note that getting the lock will create the node for the job (with - * no data) if it does not already exist. - * - * An exclusive lock is obtained before creating the node data - * because another host may have already found the job, obtained an - * exclusive lock, and started processing it. However, this locking - * does make it possible that two hosts will both try to obtain the - * lock to do the create operation at the same time. If this - * happens, the host that is locked out will not add the job to its - * pending queue for this scan of the input directory, but it will - * be picked up on the next scan. - */ - try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { - if (null != manifestLock) { - AutoIngestJob job = new AutoIngestJob(manifest); - updateCoordinationServiceManifestNode(job); - newPendingJobsList.add(job); - } - } catch (CoordinationServiceException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); - } + private void addNewPendingJob(Manifest manifest) throws AutoIngestJobException, CoordinationServiceException, InterruptedException { + AutoIngestJob job = new AutoIngestJob(manifest); + updateAutoIngestJobData(job); + newPendingJobsList.add(job); } /** - * Does crash recovery for a manifest, if required. The criterion for - * crash recovery is a manifest with coordination service node data - * indicating it is being processed for which an exclusive lock on the - * node can be acquired. If this condition is true, it is probable that - * the node that was processing the job crashed and the processing - * status was not updated. + * Does recovery for an auto ingest job that was left in the processing + * state by an auot ingest node (AIN) that crashed. * - * @param manifest The manifest for upgrading the node. - * @param jobNodeData The auto ingest job node data. + * @param manifest The manifest for the job. + * @param nodeData The data stored in the manifest file lock + * coordination service node for the job. * - * @throws InterruptedException if the thread running the input - * directory scan task is interrupted - * while blocked, i.e., if auto ingest is - * shutting down. - * @throws AutoIngestJobException if there is an issue creating a new - * AutoIngestJob object. + * @throws AutoIngestJobException If there was an error working + * with the node data. + * @throws CoordinationServiceException If there was an error writing + * updated node data by the + * coordination service. + * @throws InterruptedException If the thread running the input + * directory scan task is + * interrupted while blocked, i.e., + * if auto ingest is shutting down. */ - private void doRecoveryIfCrashed(Manifest manifest, AutoIngestJobNodeData jobNodeData) throws InterruptedException, AutoIngestJobException { - /* - * Try to get an exclusive lock on the coordination service node for - * the job. If the lock cannot be obtained, another host in the auto - * ingest cluster is already doing the recovery, so there is nothing - * to do. - */ + private void doCrashRecovery(Manifest manifest, AutoIngestJobNodeData jobNodeData) throws AutoIngestJobException, CoordinationServiceException, InterruptedException { String manifestPath = manifest.getFilePath().toString(); - try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath)) { - if (null != manifestLock) { - sysLogger.log(Level.SEVERE, "Attempting crash recovery for {0}", manifestPath); - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); + sysLogger.log(Level.SEVERE, "Attempting crash recovery for {0}", manifestPath); + AutoIngestJob job = new AutoIngestJob(jobNodeData); - /* - * Create the recovery job. - */ - AutoIngestJob job = new AutoIngestJob(jobNodeData); - int numberOfCrashes = job.getNumberOfCrashes(); - if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { - ++numberOfCrashes; - job.setNumberOfCrashes(numberOfCrashes); - if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { - job.setCompletedDate(new Date(0)); - } else { - job.setCompletedDate(Date.from(Instant.now())); - } - } + /* + * Try to set the error flags that indicate incomplete or messy data + * in displays for the job and the case. Note that if the job + * crashed before a case directory was created, the job was a no-op, + * so the data quality flags do not need to be set. + */ + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); + if (null != caseDirectoryPath) { + job.setCaseDirectoryPath(caseDirectoryPath); + job.setErrorsOccurred(true); + try { + setCaseNodeDataErrorsOccurred(caseDirectoryPath); + } catch (IOException ex) { + sysLogger.log(Level.WARNING, String.format("Error attempting to set error flag in case node data for %s", caseDirectoryPath), ex); + } + } else { + job.setErrorsOccurred(false); + } - if (null != caseDirectoryPath) { - job.setCaseDirectoryPath(caseDirectoryPath); - job.setErrorsOccurred(true); - try { - setCaseNodeDataErrorsOccurred(caseDirectoryPath); - } catch (IOException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to set error flag in case node data for %s", caseDirectoryPath), ex); - } - } else { - job.setErrorsOccurred(false); - } - - if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { - job.setProcessingStatus(AutoIngestJob.ProcessingStatus.PENDING); - if (null != caseDirectoryPath) { - try { - new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryWithRetry(); - } catch (AutoIngestJobLoggerException ex) { - sysLogger.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); - } - } - } else { - job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); - if (null != caseDirectoryPath) { - try { - new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryNoRetry(); - } catch (AutoIngestJobLoggerException ex) { - sysLogger.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); - } - } - } - - /* - * Update the coordination service node for the job. If this - * fails, leave the recovery to another host. - */ + /* + * Update the crash count for the job, determine whether or not to + * retry processing its data source, and deal with the job + * accordingly. + */ + int numberOfCrashes = job.getNumberOfCrashes(); + ++numberOfCrashes; + job.setNumberOfCrashes(numberOfCrashes); + if (numberOfCrashes < AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { + job.setProcessingStatus(AutoIngestJob.ProcessingStatus.PENDING); + job.setCompletedDate(new Date(0)); + if (null != caseDirectoryPath) { try { - updateCoordinationServiceManifestNode(job); - } catch (CoordinationServiceException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifestPath), ex); - return; - } - - jobNodeData = new AutoIngestJobNodeData(job); - - if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { - newPendingJobsList.add(job); - } else { - newCompletedJobsList.add(new AutoIngestJob(jobNodeData)); + new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryWithRetry(); + } catch (AutoIngestJobLoggerException ex) { + sysLogger.log(Level.SEVERE, String.format("Error writing case auto ingest log entry for crashed job for %s", manifestPath), ex); } } - } catch (CoordinationServiceException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to get exclusive lock for %s", manifestPath), ex); + updateAutoIngestJobData(job); + newPendingJobsList.add(job); + } else { + job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); + job.setCompletedDate(Date.from(Instant.now())); + if (null != caseDirectoryPath) { + try { + new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryNoRetry(); + } catch (AutoIngestJobLoggerException ex) { + sysLogger.log(Level.SEVERE, String.format("Error writing case auto ingest log entry for crashed job for %s", manifestPath), ex); + } + } + updateAutoIngestJobData(job); + newCompletedJobsList.add(new AutoIngestJob(jobNodeData)); } } /** * Adds a job to process a manifest to the completed jobs list. * - * @param nodeData The data stored in the coordination service node for - * the manifest. - * @param manifest The manifest for upgrading the node. + * @param manifest The manifest for the job. + * @param nodeData The data stored in the manifest file lock + * coordination service node for the job. * - * @throws CoordinationServiceException - * @throws InterruptedException + * @throws AutoIngestJobException If there was an error working + * with the node data. + * @throws CoordinationServiceException If there was an error writing + * updated node data by the + * coordination service. + * @throws InterruptedException If the thread running the input + * directory scan task is + * interrupted while blocked, i.e., + * if auto ingest is shutting down. */ - private void addCompletedJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws CoordinationServiceException, InterruptedException, AutoIngestJobException { + private void addCompletedJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws AutoIngestJobException, CoordinationServiceException, InterruptedException { Path caseDirectoryPath = nodeData.getCaseDirectoryPath(); if (!caseDirectoryPath.toFile().exists()) { sysLogger.log(Level.WARNING, String.format("Job completed for %s, but cannot find case directory %s, ignoring job", nodeData.getManifestFilePath(), caseDirectoryPath.toString())); @@ -1493,21 +1459,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen job.setProcessingStage(AutoIngestJob.Stage.COMPLETED, nodeData.getCompletedDate()); job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); - /* - * Try to upgrade/update the coordination service manifest node - * data for the job. It is possible that two hosts will both try - * to obtain the lock to do the upgrade operation at the same - * time. If this happens, the host that is holding the lock will - * complete the upgrade operation. - */ - try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { - if (null != manifestLock) { - updateCoordinationServiceManifestNode(job); - } - } catch (CoordinationServiceException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); - } + updateAutoIngestJobData(job); } + newCompletedJobsList.add(job); } @@ -1536,17 +1490,17 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } /** - * Invoked for an input directory after entries in the directory are + * Invoked for an input directory after the files in the directory are * visited. Checks if the task thread has been interrupted because auto * ingest is shutting down and terminates the scan if that is the case. * * @param dirPath The directory about to be visited. * @param unused Unused. * - * @return TERMINATE if the task thread has been interrupted, CONTINUE - * if it has not. + * @return FileVisitResult.TERMINATE if the task thread has been + * interrupted, FileVisitResult.CONTINUE if it has not. * - * @throws IOException if an I/O error occurs, but this implementation + * @throws IOException If an I/O error occurs, but this implementation * does not throw. */ @Override @@ -2005,11 +1959,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen try { AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); if (!nodeData.getProcessingStatus().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. - */ + iterator.remove(); + continue; + } + + File manifestFile = nodeData.getManifestFilePath().toFile(); + if (!manifestFile.exists()) { iterator.remove(); continue; } @@ -2027,11 +1982,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen continue; } } + iterator.remove(); currentJob = job; break; + } catch (AutoIngestJobNodeData.InvalidDataException ex) { - sysLogger.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); // JCTODO: Is this right? + sysLogger.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); } } } @@ -2102,7 +2059,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen currentJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.PROCESSING); currentJob.setProcessingStage(AutoIngestJob.Stage.STARTING, Date.from(Instant.now())); currentJob.setProcessingHostName(AutoIngestManager.LOCAL_HOST_NAME); - updateCoordinationServiceManifestNode(currentJob); + updateAutoIngestJobData(currentJob); setChanged(); notifyObservers(Event.JOB_STARTED); eventPublisher.publishRemotely(new AutoIngestJobStartedEvent(currentJob)); @@ -2126,7 +2083,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen currentJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.PENDING); } currentJob.setProcessingHostName(""); - updateCoordinationServiceManifestNode(currentJob); + updateAutoIngestJobData(currentJob); boolean retry = (!currentJob.isCanceled() && !currentJob.isCompleted()); sysLogger.log(Level.INFO, "Completed processing of {0}, retry = {1}", new Object[]{manifestPath, retry}); @@ -2322,13 +2279,16 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } currentJob.setCaseDirectoryPath(caseDirectoryPath); - updateCoordinationServiceManifestNode(currentJob); // update case directory path + updateAutoIngestJobData(currentJob); + recordManifest(caseDirectoryPath, manifest.getFilePath()); Case caseForJob = Case.getCurrentCase(); sysLogger.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); return caseForJob; } catch (KeywordSearchModuleException ex) { throw new CaseManagementException(String.format("Error creating solr settings file for case %s for %s", caseName, manifest.getFilePath()), ex); + } catch (IOException ex) { + throw new CaseManagementException(String.format("Error recording manifest file path for case %s for %s", caseName, manifest.getFilePath()), ex); } catch (CaseActionException ex) { throw new CaseManagementException(String.format("Error creating or opening case %s for %s", caseName, manifest.getFilePath()), ex); } @@ -2338,6 +2298,22 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } + /** + * Writes the path of the manifest file for the current job to a list of + * manifest file paths for the case in file in the case directory. + * + * @param caseDirectoryPath The case directory path. + * + * @throws IOException If the file cannot be created or opened and + * updated. + */ + private void recordManifest(Path caseDirectoryPath, Path manifestFilePath) throws IOException { + final Path manifestsListFilePath = Paths.get(caseDirectoryPath.toString(), AutoIngestManager.getCaseManifestsListFileName()); + try (FileWriter fileWriter = new FileWriter(manifestsListFilePath.toString(), true)) { + fileWriter.write(manifestFilePath.toString() + "\n"); + } + } + /** * Runs the ingest process for the current job. * @@ -2978,7 +2954,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen currentJob.setModuleRuntimesSnapshot(IngestManager.getInstance().getModuleRunTimes()); setChanged(); notifyObservers(Event.JOB_STATUS_UPDATED); - updateCoordinationServiceManifestNode(currentJob); + updateAutoIngestJobData(currentJob); eventPublisher.publishRemotely(new AutoIngestJobStatusEvent(currentJob)); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java index 7a2444cc32..56545625a9 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java @@ -59,6 +59,11 @@ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer { properties.add(Column.CREATE_DATE); properties.add(Column.LAST_ACCESS_DATE); properties.add(Column.DIRECTORY); + properties.add(Column.MANIFEST_FILE_ZNODES_DELETE_STATUS); + properties.add(Column.DATA_SOURCES_DELETE_STATUS); + properties.add(Column.TEXT_INDEX_DELETE_STATUS); + properties.add(Column.CASE_DB_DELETE_STATUS); + properties.add(Column.CASE_DIR_DELETE_STATUS); return properties; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java index 06aebb21dd..fdf1bdfe47 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java @@ -62,8 +62,6 @@ public final class CasesDashboardTopComponent extends TopComponent implements Ex * for multi-user cases. The top component is docked into the "dashboard * mode" defined by the auto ingest jobs top component. */ - // RJCTODO: Consider moving all of the dashboard code into its own - // autoingest.dashboard package. public static void openTopComponent() { CasesDashboardTopComponent topComponent = (CasesDashboardTopComponent) WindowManager.getDefault().findTopComponent("CasesDashboardTopComponent"); // NON-NLS if (topComponent == null) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java index 25bdf612ab..5ea4c5cc02 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.util.concurrent.ExecutorService; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; /** @@ -53,7 +54,7 @@ final class DeleteCaseInputAction extends DeleteCaseAction { @Override DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - return new DeleteCaseInputTask(caseNodeData, progress); + return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT, progress); } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java index cb92d3b9d6..7a721ed947 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; /** @@ -47,7 +48,7 @@ final class DeleteCaseInputAndOutputAction extends DeleteCaseAction { @Override DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - return new DeleteCaseInputAndOutputTask(caseNodeData, progress); + return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress); } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index bfcae6b39b..5b09758d4f 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; /** @@ -51,7 +52,7 @@ final class DeleteCaseOutputAction extends DeleteCaseAction { @Override DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - return new DeleteCaseOutputTask(caseNodeData, progress); + return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress); } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 295bd0dc3e..0451c906cd 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -23,10 +23,12 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Scanner; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -36,73 +38,111 @@ import org.sleuthkit.autopsy.casemodule.multiusercases.CaseCoordinationServiceUt import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.progress.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException; /** - * A base class for tasks that delete part or all of a given case. + * A task that deletes part or all of a given case. Note that all logging done + * by this task is directed to the dedicated auto ingest dashboard log instead + * of to the general application log. */ -abstract class DeleteCaseTask implements Runnable { +// RJCTODO: +// 1. Expand case type in case metadata to include auto ingest cases. +// Disable the delete menu item in the main app menu for auto ingest cases, +// and possibly also use this to delete the add data source capability. Could use +// this to limit the display of nodes in the in the auto ingest cases dashboard. +// 2. When an instance of this class finishes, publish an event via event bus +// so that the case browser can refresh. +// 3. Add code to file deletion utilities such that on Wimdows, for paths +// exceeding 255 chars, robocopy is invoked for the deletion. Make the new file +// deletion utility throw exceptions instead of return a boolean result code. +// 4. Make other dashbaord use the dashboard logger. +// 5. Consider moving all of the dashboard code into its own autoingest.dashboard package. +// 6. AutoIngestManager.addCompletedJob node data version updating might be out of date. +// 7. Deal with cancellation during lock releases. Look at using +// https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Uninterruptibles.html +// getUninterruptibly to do os. +// 8. With the removal of the auto ingest control panel, we can eliminate the +// completed jobs list and the processing list from AutoIngestManager. +final class DeleteCaseTask implements Runnable { + private static final int MANIFEST_FILE_LOCKING_TIMEOUT_MINS = 5; private static final Logger logger = AutoIngestDashboardLogger.getLogger(); private final CaseNodeData caseNodeData; - private final String caseDisplayName; - private final String caseUniqueName; - private final Path caseDirectoryPath; + private final DeleteOptions deleteOption; private final ProgressIndicator progress; - private final List nodeDataForAutoIngestJobs; - private final Map manifestFileLocks; + private final List manifestFileLocks; private CoordinationService coordinationService; + /* + * Options to support implementing differnet case deletion uses cases. + */ + public enum DeleteOptions { + /** + * Delete the auto ingest job manifests and corresponding data sources, + * if any, while leaving the manifest file coordination service nodes + * and the rest of the case intact. The use case is freeing auto ingest + * input directory space while retaining the option to restore the data + * sources, effectively restoring the case. + */ + DELETE_INPUT, + /** + * Delete the auto ingest job coordination service nodes, if any, and + * the output for a case produced via auto ingest, while leaving the + * auto ingest job input directories intact. The use case is auto ingest + * reprocessing of a case with a clean slate without having to restore + * the input directories. + */ + DELETE_OUTPUT, + /** + * Delete everything. + */ + DELETE_ALL + } + /** - * Constructs the base class part of a task that deletes part or all of a - * given case. + * Constructs a task that deletes part or all of a given case. Note that all + * logging is directed to the dedicated auto ingest dashboard log instead of + * to the general application log. * - * @param caseNodeData The case directory lock coordination service node - * data for the case. + * @param caseNodeData The case directory coordination service node data for + * the case. + * @param deleteOption The deletion option for the task. * @param progress A progress indicator. */ - DeleteCaseTask(CaseNodeData caseNodeData, ProgressIndicator progress) { + DeleteCaseTask(CaseNodeData caseNodeData, DeleteOptions deleteOption, ProgressIndicator progress) { this.caseNodeData = caseNodeData; + this.deleteOption = deleteOption; this.progress = progress; - /* - * Design Decision Note: It was decided to add the following state to - * instances of this class make it easier to access given that the class - * design favors instance methods over static methods. - */ - this.caseDisplayName = caseNodeData.getDisplayName(); - this.caseUniqueName = caseNodeData.getName(); - this.caseDirectoryPath = caseNodeData.getDirectory(); - this.nodeDataForAutoIngestJobs = new ArrayList<>(); - this.manifestFileLocks = new HashMap<>(); + this.manifestFileLocks = new ArrayList<>(); } @Override @NbBundle.Messages({ - "DeleteCaseTask.progress.startMessage=Preparing for deletion..." + "DeleteCaseTask.progress.startMessage=Starting deletion..." }) public void run() { try { progress.start(Bundle.DeleteCaseTask_progress_startMessage()); - logger.log(Level.INFO, String.format("Beginning deletion of %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Starting attempt to delete %s (%s)", caseNodeData.getDisplayName(), deleteOption)); deleteCase(); - logger.log(Level.SEVERE, String.format("Deletion of %s (%s) in %s completed", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Finished attempt to delete %s (%s)", caseNodeData.getDisplayName(), deleteOption)); } catch (Throwable ex) { /* - * Unexpected runtime exceptions firewall. This task is designed to - * be able to be run in an executor service thread pool without - * calling get() on the task's Future, so this ensures that - * such errors do get ignored. + * This is an unexpected runtime exceptions firewall. It is here + * because this task is designed to be able to be run in scenarios + * where there is no call to get() on a Future associated with + * the task, so this ensures that any such errors get logged. */ - logger.log(Level.INFO, String.format("Unexpected error deleting %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + logger.log(Level.SEVERE, String.format("Unexpected error deleting %s", caseNodeData.getDisplayName()), ex); } finally { progress.finish(); } - } /** @@ -110,426 +150,487 @@ abstract class DeleteCaseTask implements Runnable { */ @NbBundle.Messages({ "DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...", - "DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock...", - "DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring an exclusive case directory lock...", - "DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs...", - "DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks..." + "DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...", + "DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...", + "DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...", + "DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node...", + "DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock coordination service node..." }) private void deleteCase() { progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc()); - logger.log(Level.INFO, String.format("Connecting to coordination service for deletion of %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Connecting to the coordination service for deletion of %s", caseNodeData.getDisplayName())); try { coordinationService = CoordinationService.getInstance(); } catch (CoordinationService.CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to connect to the coordination service, cannot delete %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred connecting to the coordination service", caseNodeData.getDisplayName()), ex); return; } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); return; } /* - * Acquire an exclusive case name lock. This is the lock that auto - * ingest nodes acquire exclusively when creating or opening a case - * specified in an auto ingest job manifest file to ensure that only one - * auto ingest node at a time can search the auto ingest output - * directory for an existing case matching the one in the manifest file. - * Acquiring this lock effectively locks auto ingest node job processing - * tasks out of the case to be deleted. + * Acquire an exclusive case name lock. The case name lock is the lock + * that auto ingest node (AIN) job processing tasks acquire exclusively + * when creating or opening a case specified in an auto ingest job + * manifest file. The reason AINs do this is to ensure that only one of + * them at a time can search the auto ingest output directory for an + * existing case matching the one in the manifest file. If a matching + * case is found, it is opened, otherwise the case is created. Acquiring + * this lock effectively disables this AIN job processing task behavior + * while the case is being deleted. */ progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock()); - logger.log(Level.INFO, String.format("Acquiring an exclusive case name lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); - String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath); - try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockNodeName)) { + logger.log(Level.INFO, String.format("Acquiring an exclusive case name lock for %s", caseNodeData.getDisplayName())); + String caseNameLockName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) { if (nameLock == null) { - logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case name lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName())); return; } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); return; } /* * Acquire an exclusive case directory lock. A shared case directory - * lock is acquired by any node (auto ingest or examiner) when it - * opens a case and is held by the node for as long as the case is - * open. Acquiring this lock exclusively ensures that no other node - * currently has the case to be deleted open and prevents another - * node from trying to open the case while it is being deleted. + * lock is acquired by each auto ingest node (AIN) and examiner node + * (EIN) when it opens a case. The shared locks are held by the AINs + * and EINs for as long as they have the case open. Acquiring this + * lock exclusively ensures that no AIN or EIN has the case to be + * deleted open and prevents another node from trying to open the + * case while it is being deleted. */ + boolean success = true; // RJCTODO: Instead of having this flag, read the casenodedata instead progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock()); - logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); - String caseDirLockNodeName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseDirectoryPath); - try (CoordinationService.Lock caseDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirLockNodeName)) { + logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName())); + String caseDirLockName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); + try (CoordinationService.Lock caseDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirLockName)) { if (caseDirLock == null) { - logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName())); return; } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); return; } - progress.progress(Bundle.DeleteCaseTask_progress_gettingJobNodeData()); - logger.log(Level.INFO, String.format("Fetching auto ingest job node data for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + /* + * Acquire exclusive locks for the auto ingest job manifest + * files for the case, if any. Manifest file locks are acquired + * by the auto ingest node (AIN) input directory scanning tasks + * when they look for auto ingest jobs to enqueue, and by the + * AIN job processing tasks when they execute a job. Acquiring + * these locks here ensures that the scanning tasks and job + * processing tasks cannot do anything with the auto ingest jobs + * for a case during case deletion. + */ + progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); + logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName())); try { - getAutoIngestJobNodeData(); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error fetching auto ingest job node data for %s (%s) in %s, cannot delete case", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - return; - } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - return; - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); - return; - } - - if (!nodeDataForAutoIngestJobs.isEmpty()) { - progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); - logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); - getManifestFileLocks(); - if (manifestFileLocks.isEmpty()) { - logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath)); + if (!acquireManifestFileLocks()) { + logger.log(Level.INFO, String.format("Could not delete %s because a manifest file lock was already held by another host", caseNodeData.getDisplayName())); return; } - } else { - logger.log(Level.INFO, String.format("No auto ingest job node data found for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex); + return; + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + return; } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + releaseManifestFileLocks(); + return; + } + + if (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL) { + try { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + deleteAutoIngestInput(); + } catch (IOException ex) { + // RJCTODO: + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + releaseManifestFileLocks(); + return; + } + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + releaseManifestFileLocks(); + return; + } + + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + try { + success = deleteCaseOutput(); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + releaseManifestFileLocks(); + return; + } + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); releaseManifestFileLocks(); return; } try { - deleteWhileHoldingAllLocks(); + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + success = deleteManifestFileNodes(); + } else { + releaseManifestFileLocks(); + } } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - return; - } - - releaseManifestFileLocks(); - - try { - deleteAfterManifestLocksReleased(); - } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); return; } } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error acquiring exclusive case directory lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the case directory lock", caseNodeData.getDisplayName()), ex); + return; } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); return; } - try { - deleteAfterCaseDirectoryLockReleased(); - } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - return; + /* + * Now that the case directory lock has been released, the + * coordination service node for it can be deleted if the use case + * requires it. However, if something to ge deleted was not deleted, + * leave the node so that what was and was not deleted can be + * inspected. + */ + if (success && (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL)) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode()); + try { + Case.deleteCaseDirectoryLockNode(caseNodeData, progress); + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + return; + } } } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error acquiring exclusive case name lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - } - - try { - deleteAfterCaseNameLockReleased(); - } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the case name lock", caseNodeData.getDisplayName()), ex); return; } - } - /** - * Deletes the parts of the case that need to be deleted while holding all - * of the exclusive locks: the case name lock, the case directory lock, amd - * the manifest file locks. Note that the locks are acquired in that order - * and released in the opposite order. - */ - abstract void deleteWhileHoldingAllLocks() throws InterruptedException; + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + return; + } - /** - * Deletes the parts of the case that need to be deleted after the release - * of the exclusive manifest file locks, while still holding the exclusive - * case name and case directory locks; the manifest file locks are the first - * locks released. - */ - abstract void deleteAfterManifestLocksReleased() throws InterruptedException; - - /** - * Deletes the parts of the case that need to be deleted after the release - * of the exclusive manifest file locks and case directory lock, while still - * holding the exclusive case name; the case name lock is the last lock - * released. - */ - abstract void deleteAfterCaseDirectoryLockReleased() throws InterruptedException; - - /** - * Deletes the parts of the case that need to be deleted after the release - * of all of the exclusive locks; the case name lock is the last lock - * released. - */ - abstract void deleteAfterCaseNameLockReleased() throws InterruptedException; - - /** - * Deletes the auto ingest job input directories for the case. Intended to - * be called by subclasses, if required, in their customization of the - * deleteWhileHoldingAllLocks step of the case deletion algorithm. - */ - @NbBundle.Messages({ - "# {0} - input directory name", "DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}..." - }) - protected void deleteInputDirectories() { - boolean allInputDirsDeleted = true; - for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) { - Path inputDirPath = jobNodeData.getManifestFilePath().getParent(); - File inputDir = inputDirPath.toFile(); - if (inputDir.exists()) { - progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDir(inputDirPath)); - logger.log(Level.INFO, String.format("Deleting input directory %s for %s (%s) in %s", inputDirPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); - if (!FileUtil.deleteDir(new File(inputDirPath.toString()))) { - logger.log(Level.WARNING, String.format("Failed to delete the input directory %s for %s (%s) in %s", inputDirPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); - allInputDirsDeleted = false; - } + /* + * Now that the case name lock has been released, the coordination + * service node for it can be deleted if the use case requires it. + */ + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode()); + try { + String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); // RJCTODO: Should this be a Case method? + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex); + } catch (InterruptedException ex) { + logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); } } - if (allInputDirsDeleted) { - setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES); + } + + /** + * Acquires either all or none of the auto ingest job manifest file locks + * for a case. + * + * @return True if all of the locks were acquired; false otherwise. + * + * @throws CoordinationServiceException If there is an error completing a + * coordination service operation. + * @throws InterruptedException If the thread in which this task is + * running is interrupted while blocked + * waiting for a coordination service + * operation to complete. + */ + @NbBundle.Messages({ + "# {0} - manifest file path", "DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}..." + }) + private boolean acquireManifestFileLocks() throws CoordinationServiceException, InterruptedException { + /* + * Get the "original" case name that from the case directory. This is + * necessary because the case display name can be changed and the case + * name may have a time stamp added to make it unique, depending on how + * the case was created. An alternative aproach would be to strip the + * time stamp from the case name in the case node data instead, but the + * code for that is already in the utility method called here. + */ + String caseName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + try { + boolean allLocksAcquired = true; + // RJCTODO: Read in the list of manifests for the case instead of + // inspecting the nodes this way, once the recording of the + // manifests is in place. + final List nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); + for (String manifestPath : nodeNames) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath); + if (nodeBytes == null || nodeBytes.length <= 0) { + logger.log(Level.WARNING, String.format("Empty coordination service node data found for %s", manifestPath)); + continue; + } + + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + AutoIngestJobNodeData nodeData; + try { + nodeData = new AutoIngestJobNodeData(nodeBytes); + } catch (InvalidDataException ex) { + logger.log(Level.WARNING, String.format("Invalid coordination service node data found for %s", manifestPath), ex); + continue; + } + + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + if (caseName.equals(nodeData.getCaseName())) { + /* + * When acquiring manifest file locks, it is reasonable to + * block while acquiring this lock since the auto ingest + * node (AIN) input directory scanning tasks do a lot of + * short-term acquiring and releasing of manifest file + * locks. The assumption here is that the originator of this + * case deletion task is not asking for deletion of a case + * that has a job an auto ingest node (AIN) job processing + * task is working on and that + * MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long, + * anyway, so we can and should wait a bit. + */ + logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName())); + progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath)); + CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath, MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES); + if (null != manifestLock) { + manifestFileLocks.add(manifestLock); + } else { + allLocksAcquired = false; + logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName())); + releaseManifestFileLocks(); + break; + } + } + } + return allLocksAcquired; + + } catch (CoordinationServiceException | InterruptedException ex) { + releaseManifestFileLocks(); + throw ex; } } /** - * Deletes the case database, the text index, and the case directory for the - * case. Intended to be called by subclasses, if required, in their - * customization of the deleteWhileHoldingAllLocks step of the case deletion - * algorithm. + * Deletes the auto ingest job input manifests for the case along with the + * corresponding data sources. + * + * @throws IOException If there is an error opening the case + * manifests list file. + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + @NbBundle.Messages({ + "# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}..." + }) + private void deleteAutoIngestInput() throws IOException, InterruptedException { + boolean allInputDeleted = true; + final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName()); + final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath); + while (manifestsListFileScanner.hasNext()) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + final String manifestFilePath = manifestsListFileScanner.next(); + final File manifestFile = new File(manifestFilePath); + if (manifestFile.exists()) { + // RJCTODO: Parse file, open case database, delete data sources + // before deleting manifest file + progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath)); + logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + if (manifestFile.delete()) { + logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + allInputDeleted = false; + } + } + if (allInputDeleted) { + setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES); + } + } + } + + /** + * Deletes the case database, the text index, the case directory, and the + * case resources and auto ingest log coordination service lock nodes for + * the case. + * + * @return If true if all of the case output that was found was deleted, + * false otherwise. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. */ @NbBundle.Messages({ "DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file...", - "DeleteCaseTask.progress.deletingCaseOutput=Deleting case database, text index, and directory...", - "DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job log lock node..." + "DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources coordination service node...", + "DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job coordination service node..." }) - protected void deleteCaseOutput() { + private boolean deleteCaseOutput() throws InterruptedException { + boolean errorsOccurred = false; progress.progress(Bundle.DeleteCaseTask_progress_locatingCaseMetadataFile()); - logger.log(Level.INFO, String.format("Locating metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + logger.log(Level.INFO, String.format("Locating metadata file for %s", caseNodeData.getDisplayName())); CaseMetadata caseMetadata = null; - final File caseDirectory = caseDirectoryPath.toFile(); - final File[] filesInDirectory = caseDirectory.listFiles(); - if (filesInDirectory != null) { - for (File file : filesInDirectory) { - if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { - try { - caseMetadata = new CaseMetadata(Paths.get(file.getPath())); - } catch (CaseMetadata.CaseMetadataException ex) { - logger.log(Level.WARNING, String.format("Error getting opening case metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + final File caseDirectory = caseNodeData.getDirectory().toFile(); + if (caseDirectory.exists()) { + final File[] filesInDirectory = caseDirectory.listFiles(); + if (filesInDirectory != null) { + for (File file : filesInDirectory) { + if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { + try { + caseMetadata = new CaseMetadata(Paths.get(file.getPath())); + } catch (CaseMetadata.CaseMetadataException ex) { + logger.log(Level.WARNING, String.format("Error getting opening case metadata file for %s", caseNodeData.getDisplayName()), ex); + } + break; } - break; } } + + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + if (caseMetadata != null) { + logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName())); + errorsOccurred = Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); // RJCTODO: CHeck for errors occurred? + } else { + logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s", caseNodeData.getDisplayName())); + } } - if (caseMetadata != null) { - progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseOutput()); - logger.log(Level.INFO, String.format("Deleting output for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); - Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress); // RJCTODO: Make this method throw the interrupted exception. - } else { - logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); } + progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); + try { + Case.deleteCaseResourcesLockNode(caseNodeData, progress); + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Error deleting case resources coordiation service node for %s", caseNodeData.getDisplayName()), ex); + } + + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + // RJCTODO: Check to see if getNodeData return null if the node does not exist; + // if so, make use of it progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); - logger.log(Level.INFO, String.format("Deleting case auto ingest job log lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); - Path logFilePath = AutoIngestJobLogger.getLogPath(caseDirectoryPath); //RJCTODO: USe util here + logger.log(Level.INFO, String.format("Deleting case auto ingest job log coordiation service node for %s", caseNodeData.getDisplayName())); + String logFilePath = CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseNodeData.getDirectory()); try { - coordinationService.deleteNode(CategoryNode.CASES, logFilePath.toString()); + coordinationService.deleteNode(CategoryNode.CASES, logFilePath); } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case auto ingest job log lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + logger.log(Level.WARNING, String.format("Error deleting case auto ingest job log coordiation service node for %s", caseNodeData.getDisplayName()), ex); } + + return errorsOccurred; } /** - * Deletes the manifest file lock coordination service nodes for the case. - * Intended to be called by subclasses, if required, in their customization - * of the deleteAfterManifestLocksReleased step of the case deletion - * algorithm. - */ - @Messages({ - "DeleteCaseTask.progress.deletingManifestFileLockNodes=Deleting manifest file lock nodes..." - }) - protected void deleteManifestFileLockNodes() throws InterruptedException { - boolean allInputDirsDeleted = true; - progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileLockNodes()); - logger.log(Level.INFO, String.format("Deleting manifest file lock nodes for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath)); - for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) { - try { - logger.log(Level.INFO, String.format("Deleting manifest file lock node for %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseDisplayName, caseUniqueName, caseDirectoryPath)); - coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, jobNodeData.getManifestFilePath().toString()); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting manifest file lock node %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - allInputDirsDeleted = false; - } - } - if (allInputDirsDeleted) { - setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_LOCK_NODES); - } - // RJCTODO: Expand case type in case metadata to include auto ingest cases. - // Disable delete menu item for auto ingest cases, and possibly also add data source - // capability. - } - - /** - * Deletes the case directory coordination service lock node for the case. - * Intended to be called by subclasses, if required, in their customization - * of the deleteAfterCaseDirectoryLockReleased step of the case deletion - * algorithm. - */ - @Messages({ - "DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node..." - }) - protected void deleteCaseDirectoryLockNode() throws InterruptedException { - progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode()); - try { - Case.deleteCaseDirectoryLockNode(caseNodeData, progress); // RJCTODO: Case does not need to expose this? - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - } - } - - /** - * Deletes the case name coordination service lock node for the case. - * Intended to be called by subclasses, if required, in their customization - * of the deleteAfterCaseNameLockReleased step of the case deletion - * algorithm. - * - * @throws InterruptedException - */ - @Messages({ - "DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node..." // RJCTODO: Use consistent terminology - }) - protected void deleteCaseNameLockNode() throws InterruptedException { - progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode()); - try { - String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath); - coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - } - } - - /** - * Fetches the auto ingest job data from the manifest file lock coordination - * service nodes for a case. - * - * @throws CoordinationServiceException If there is an error interacting - * with the coordination service. - * @throws InterruptedException If the current thread is interrupted - * while waiting for the coordination - * service. - */ - private void getAutoIngestJobNodeData() throws CoordinationServiceException, InterruptedException { - String caseName = caseDisplayName; - final List nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); - for (String nodeName : nodeNames) { - try { - byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); - if (nodeBytes == null || nodeBytes.length <= 0) { - logger.log(Level.WARNING, String.format("Missing auto ingest job node data for manifest file lock node %s, deleting node", nodeName)); - try { - coordinationService.deleteNode(CategoryNode.MANIFESTS, nodeName); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Failed to delete empty manifest file lock node %s", nodeName)); - } - continue; - } - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes); - if (caseName.equals(nodeData.getCaseName())) { - nodeDataForAutoIngestJobs.add(nodeData); - } - } catch (CoordinationService.CoordinationServiceException | InvalidDataException ex) { - logger.log(Level.WARNING, String.format("Failed to get auto ingest job node data for %s", nodeName), ex); - } - } - } - - /** - * Acquires either all or none of the manifest file locks for a case. + * Releases all of the manifest file locks that have been acquired by this + * task. */ @NbBundle.Messages({ - "# {0} - manifest file name", "DeleteCaseTask.progress.lockingManifestFile=Acquiring exclusive lock on manifest {0}..." - }) - private void getManifestFileLocks() { - for (AutoIngestJobNodeData autoIngestJobNodeData : nodeDataForAutoIngestJobs) { - String manifestPath = autoIngestJobNodeData.getManifestFilePath().toString(); - try { - progress.progress(Bundle.DeleteCaseTask_progress_lockingManifestFile(manifestPath)); - logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); - CoordinationService.Lock inputDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath); - if (null != inputDirLock) { - manifestFileLocks.put(manifestPath, inputDirLock); - } else { - logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath)); - releaseManifestFileLocks(); - manifestFileLocks.clear(); - break; - } - } catch (CoordinationService.CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error exclusively locking the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - releaseManifestFileLocks(); - manifestFileLocks.clear(); - break; - } - } - } - - /** - * Releases any manifest file coordination service locks that were acquired - * for the case. - */ - @NbBundle.Messages({ - "# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}..." + "# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive coordination service lock on the manifest file {0}..." }) private void releaseManifestFileLocks() { - if (!manifestFileLocks.isEmpty()) { - for (Map.Entry entry : manifestFileLocks.entrySet()) { - String manifestFilePath = entry.getKey(); - CoordinationService.Lock manifestFileLock = entry.getValue(); - try { - progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); - logger.log(Level.INFO, String.format("Releasing the exclusive lock on the manifest file %s for %s (%s) in %s", manifestFilePath, caseDisplayName, caseUniqueName, caseDirectoryPath)); - manifestFileLock.release(); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error releasing exclusive lock on the manifest file %s for %s (%s) in %s", manifestFilePath, caseDisplayName, caseUniqueName, caseDirectoryPath), ex); - } + for (Lock manifestFileLock : manifestFileLocks) { + String manifestFilePath = manifestFileLock.getNodePath(); + try { + progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); + logger.log(Level.INFO, String.format("Releasing the exclusive coordination service lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + manifestFileLock.release(); + } catch (CoordinationServiceException ex) { + logger.log(Level.WARNING, String.format("Error releasing the exclusive coordination service lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); } } + manifestFileLocks.clear(); } /** - * Sets a deleted item flag for the case. + * Releases all of the manifest file locks that have been acquired by this + * task and attempts to delete the corresponding coordination service nodes. + * + * @return True if all of the manifest file coordianiton service nodes have + * been deleted, false otherwise. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + @Messages({ + "# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file coordination service node for {0}..." + }) + private boolean deleteManifestFileNodes() throws InterruptedException { + boolean allINodesDeleted = true; + for (Lock manifestFileLock : manifestFileLocks) { + String manifestFilePath = manifestFileLock.getNodePath(); + try { + progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); + logger.log(Level.INFO, String.format("Releasing the exclusive coordination service lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + manifestFileLock.release(); + progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileNode(manifestFilePath)); + logger.log(Level.INFO, String.format("Deleting the manifest file coordination service node for %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath); + } catch (CoordinationServiceException ex) { + allINodesDeleted = false; + logger.log(Level.WARNING, String.format("Error deleting the manifest file coordination service node for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); + } + } + manifestFileLocks.clear(); + return allINodesDeleted; + } + + /** + * Sets a deleted item flag in the coordination service node data for the + * case. * * @param flag The flag to set. */ private void setDeletedItemFlag(CaseNodeData.DeletedFlags flag) { try { caseNodeData.setDeletedFlag(flag); - coordinationService.setNodeData(CategoryNode.CASES, caseDirectoryPath.toString(), caseNodeData.toArray()); + coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray()); } catch (IOException | CoordinationServiceException | InterruptedException ex) { - logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseDisplayName, caseUniqueName, caseDirectoryPath), ex); + logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s", flag.name(), caseNodeData.getDisplayName()), ex); } } From db296e64a046b91eee364ca45207b482eb7e347c Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 19 Mar 2019 15:22:48 -0400 Subject: [PATCH 08/32] Delete case deletion task subclasses --- .../DeleteCaseInputAndOutputTask.java | 61 ----------------- .../autoingest/DeleteCaseInputTask.java | 65 ------------------ .../autoingest/DeleteCaseOutputTask.java | 66 ------------------- 3 files changed, 192 deletions(-) delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java deleted file mode 100755 index 518e92265c..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputTask.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 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 org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; -import org.sleuthkit.autopsy.progress.ProgressIndicator; - -/** - * A task that deletes a case produced via auto ingest. - */ -final class DeleteCaseInputAndOutputTask extends DeleteCaseTask { - - /** - * Constructs a task that deletes a case produced via auto ingest. - * - * @param caseNodeData The case directory lock coordination service node - * data for the case. - * @param progress A progress indicator. - */ - DeleteCaseInputAndOutputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - super(caseNodeData, progress); - } - - @Override - void deleteWhileHoldingAllLocks() throws InterruptedException { - deleteInputDirectories(); - deleteCaseOutput(); - } - - @Override - void deleteAfterManifestLocksReleased() throws InterruptedException { - deleteManifestFileLockNodes(); - } - - @Override - void deleteAfterCaseDirectoryLockReleased() throws InterruptedException { - this.deleteCaseDirectoryLockNode(); - } - - @Override - void deleteAfterCaseNameLockReleased() throws InterruptedException { - this.deleteCaseNameLockNode(); - } - -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java deleted file mode 100755 index 38bb494235..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputTask.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 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 org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; -import org.sleuthkit.autopsy.progress.ProgressIndicator; - -/** - * A task to delete the auto ingest job input directories for a case produced - * via auto ingest, while leaving the auto ingest job coordination service nodes - * (manifest file lock nodes) and the rest of the case intact. The use case is - * freeing space while retaining the option to restore the input directories, - * effectively restoring the case. - */ -final class DeleteCaseInputTask extends DeleteCaseTask { - - /** - * Constructs task to delete the auto ingest job input directories for a - * case produced via auto ingest, while leaving the auto ingest job - * coordination service nodes (manifest file lock nodes) and the rest of the - * case intact. The use case is freeing space while retaining the option to - * restore the input directories, effectively restoring the case. - * - * @param caseNodeData The case directory lock coordination service node - * data for the case. - * @param progress A progress indicator. - */ - DeleteCaseInputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - super(caseNodeData, progress); - } - - @Override - void deleteWhileHoldingAllLocks() throws InterruptedException { - } - - @Override - void deleteAfterCaseDirectoryLockReleased() throws InterruptedException { - } - - @Override - void deleteAfterCaseNameLockReleased() throws InterruptedException { - deleteInputDirectories(); - } - - @Override - void deleteAfterManifestLocksReleased() throws InterruptedException { - } - -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java deleted file mode 100755 index 7b20dea7f8..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputTask.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 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 org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; -import org.sleuthkit.autopsy.progress.ProgressIndicator; - -/** - * A task to delete the auto ingest job coordination service nodes and the - * output for a case produced via auto ingest, while leaving the auto ingest job - * input directories intact. The use case is reprocessing a case with a clean - * slate without having to restore the input directories. - */ -final class DeleteCaseOutputTask extends DeleteCaseTask { - - /** - * Constructs a task to delete the auto ingest job coordination service - * nodes and the output for a case produced via auto ingest, while leaving - * the auto ingest job input directories intact. The use case is - * reprocessing a case with a clean slate without having to restore the - * input directories. - * - * @param caseNodeData The case directory lock coordination service node - * data for the case. - * @param progress A progress indicator. - */ - DeleteCaseOutputTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - super(caseNodeData, progress); - } - - @Override - void deleteWhileHoldingAllLocks() throws InterruptedException { - deleteCaseOutput(); - } - - @Override - void deleteAfterCaseDirectoryLockReleased() throws InterruptedException { - } - - @Override - void deleteAfterManifestLocksReleased() throws InterruptedException { - deleteManifestFileLockNodes(); - } - - @Override - void deleteAfterCaseNameLockReleased() throws InterruptedException { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - -} From 25d45542708b7560c210f7b957d15e46bd9ac49f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 22 Mar 2019 14:47:50 -0400 Subject: [PATCH 09/32] Interim check in of improved case deletion --- .../casemodule/Bundle.properties-MERGED | 2 + .../sleuthkit/autopsy/casemodule/Case.java | 44 +- .../autopsy/casemodule/CaseMetadata.java | 84 ++- .../multiusercases/CaseNodeData.java | 4 +- .../Bundle.properties-MERGED | 4 +- .../MultiUserCaseNode.java | 2 +- .../sleuthkit/autopsy/coreutils/ExecUtil.java | 19 +- .../autoingest/AutoIngestManager.java | 12 + .../autoingest/Bundle.properties-MERGED | 56 +- .../autoingest/DeleteCaseInputAction.java | 16 +- .../DeleteCaseInputAndOutputAction.java | 15 +- .../autoingest/DeleteCaseOutputAction.java | 12 + .../autoingest/DeleteCaseTask.java | 709 ++++++++++++------ 13 files changed, 638 insertions(+), 341 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index dbd5215554..5033adb0ad 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -32,6 +32,8 @@ Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0 Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}. # {0} - exception message Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}. +# {0} - case display name +Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled. Case.exceptionMessage.emptyCaseDir=Must specify a case directory path. Case.exceptionMessage.emptyCaseName=Must specify a case name. Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index efadae5fb7..c60d3df8b3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -81,6 +81,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseCoordinationServiceUtils; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeSearchAction; import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction; @@ -140,8 +141,7 @@ public class Case { private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS private static final String CASE_ACTION_THREAD_NAME = "%s-case-action"; private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources"; - private static final String RESOURCES_LOCK_SUFFIX = "_resources"; //NON-NLS - private static final String AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; + private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode"; private static final Logger logger = Logger.getLogger(Case.class.getName()); private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); private static final Object caseActionSerializationLock = new Object(); @@ -1022,13 +1022,16 @@ public class Case { try { deleteCaseResourcesLockNode(caseNodeData, progressIndicator); - } catch (CoordinationServiceException | InterruptedException ex) { - errorsOccurred = true; + } catch (CoordinationServiceException ex) { + if (!isNoNodeException(ex)) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + } + } catch (InterruptedException ex) { logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); - } + } // RJCTODO: Is this behavior implemented correctly? - } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); @@ -1071,7 +1074,8 @@ public class Case { * during a wait. */ @Beta - public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { + public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, + ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { boolean errorsOccurred = false; try { deleteCaseDatabase(caseNodeData, metadata, progressIndicator, logger); @@ -1201,9 +1205,6 @@ public class Case { "Case.progressMessage.deletingCaseDirectory=Deleting case directory..." }) private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { - // RJCTODO: Update FileUtil.deleteDir to use robocopy on Windows - // when the path is >= 255 chars. Actually, deprecate this method and - // replace it with one that throws instead of returning a boolean value. progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); logger.log(Level.INFO, String.format("Deleting case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { @@ -1273,7 +1274,7 @@ public class Case { @Beta public static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException { progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode()); - String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX;//RJCTODO: Use utility + String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); CoordinationService coordinationService = CoordinationService.getInstance(); coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); } @@ -1306,6 +1307,24 @@ public class Case { coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath); } + /** + * Examines a coordination service exception to try to determine if it is a + * no node exception. + * + * @param ex A coordination service exception. + * + * @return True or false. + */ + private static boolean isNoNodeException(CoordinationServiceException ex) { + boolean isNodeNodeEx = false; + Throwable cause = ex.getCause(); + if (cause != null) { + String causeMessage = cause.getMessage(); + isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT); + } + return isNodeNodeEx; + } + /** * Sets a deleted item flag in the coordination service node data for a * multi-user case. @@ -1342,7 +1361,8 @@ public class Case { }) private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException { try { - String resourcesNodeName = caseDir + RESOURCES_LOCK_SUFFIX; + Path caseDirPath = Paths.get(caseDir); + String resourcesNodeName = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseDirPath); Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); return lock; } catch (InterruptedException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index abd2edbccf..c84fcc2cee 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -95,13 +95,13 @@ public final class CaseMetadata { private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS - + /* * Fields from schema version 5 */ private static final String SCHEMA_VERSION_FIVE = "5.0"; private final static String ORIGINAL_CASE_ELEMENT_NAME = "OriginalCase"; //NON-NLS - + /* * Unread fields, regenerated on save. */ @@ -138,16 +138,39 @@ public final class CaseMetadata { public static DateFormat getDateFormat() { return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US); } + + /** + * Locate the case meta data file in the supplied directory. If the file + * does not exist, null is returned. + * + * @param directoryPath Directory path to search + * + * @return case meta data file path or null + */ + // RJCTODO: Perhaps this should return a CaseMetadata object + // RJCTODO: It should say get...path + public static Path getCaseMetadataFile(Path directoryPath) { + final File[] files = directoryPath.toFile().listFiles(); + if (files != null) { + for (File file : files) { + final String fileName = file.getName().toLowerCase(); + if (fileName.endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { + return file.toPath(); + } + } + } + return null; + } /** * Constructs a CaseMetadata object for a new case. The metadata is not * persisted to the case metadata file until writeFile or a setX method is * called. * - * @param caseType The type of case. - * @param caseDirectory The case directory. - * @param caseName The immutable name of the case. - * @param caseDetails The details for the case + * @param caseType The type of case. + * @param caseDirectory The case directory. + * @param caseName The immutable name of the case. + * @param caseDetails The details for the case */ CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) { this(caseType, caseDirectory, caseName, caseDetails, null); @@ -158,11 +181,11 @@ public final class CaseMetadata { * persisted to the case metadata file until writeFile or a setX method is * called. * - * @param caseType The type of case. - * @param caseDirectory The case directory. - * @param caseName The immutable name of the case. - * @param caseDetails The details for the case - * @param originalMetadata The metadata object from the original case + * @param caseType The type of case. + * @param caseDirectory The case directory. + * @param caseName The immutable name of the case. + * @param caseDetails The details for the case + * @param originalMetadata The metadata object from the original case */ CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails, CaseMetadata originalMetadata) { metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION); @@ -190,27 +213,6 @@ public final class CaseMetadata { this.metadataFilePath = metadataFilePath; readFromFile(); } - - /** - * Locate the case meta data file in the supplied directory. If the file does - * not exist, null is returned. - * - * @param directoryPath Directory path to search - * @return case meta data file path or null - */ - public static Path getCaseMetadataFile(Path directoryPath) { - final File[] caseFiles = directoryPath.toFile().listFiles(); - if(caseFiles != null) { - for (File file : caseFiles) { - final String fileName = file.getName().toLowerCase(); - if (fileName.endsWith(CaseMetadata.getFileExtension())) { - return file.toPath(); - } - } - } - - return null; - } /** * Gets the full path to the case metadata file. @@ -460,7 +462,7 @@ public final class CaseMetadata { * Create the children of the case element. */ createCaseElements(doc, caseElement, this); - + /* * Add original case element */ @@ -472,15 +474,15 @@ public final class CaseMetadata { originalCaseElement.appendChild(originalCaseDetailsElement); createCaseElements(doc, originalCaseDetailsElement, originalMetadata); } - + } - + /** * Write the case element children for the given metadata object - * - * @param doc The document. - * @param caseElement The case element parent - * @param metadataToWrite The CaseMetadata object to read from + * + * @param doc The document. + * @param caseElement The case element parent + * @param metadataToWrite The CaseMetadata object to read from */ private void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite) { CaseDetails caseDetailsToWrite = metadataToWrite.caseDetails; @@ -572,8 +574,8 @@ public final class CaseMetadata { examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false); caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false); } - - this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, + + this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes); this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true)); if (null == this.caseType) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java index 35414ed629..4cffe6a0a2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java @@ -289,8 +289,8 @@ public final class CaseNodeData { CASE_DB(2), CASE_DIR(4), DATA_SOURCES(8), - MANIFEST_FILE_LOCK_NODES(16); - + MANIFEST_FILE_NODES(16); + private final short value; private DeletedFlags(int value) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED index 6f24fde4f6..c4801e56ed 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/Bundle.properties-MERGED @@ -5,6 +5,8 @@ MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Delet MultiUserCaseBrowserCustomizer.column.directory=Directory MultiUserCaseBrowserCustomizer.column.displayName=Name MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time -MultiUserCaseBrowserCustomizer.column.manifestCoordSvcNodesDeleteStatus=Manifest ZooKeeper Node Deleted +MultiUserCaseBrowserCustomizer.column.manifestFileZNodesDeleteStatus=Manifest Znodes Deleted MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted +MultiUserCaseNode.column.createTime=False +MultiUserCaseNode.columnValue.true=True MultiUserCasesBrowserPanel.waitNode.message=Please Wait... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java index a9237aa7a1..11e73cfb12 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCaseNode.java @@ -78,7 +78,7 @@ final class MultiUserCaseNode extends AbstractNode { sheetSet.put(new NodeProperty<>(propName, propName, propName, caseNodeData.getLastAccessDate())); break; case MANIFEST_FILE_ZNODES_DELETE_STATUS: - sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_LOCK_NODES))); + sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_NODES))); break; case DATA_SOURCES_DELETE_STATUS: sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.DATA_SOURCES))); diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java index 4c7c022871..0a76b2a7fa 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,7 +59,19 @@ public final class ExecUtil { } /** - * Process terminator that can be used to kill a processes after it exceeds + * A process terminator that can be used to kill a process spawned by a + * thread that has been interrupted. + */ + public static class InterruptedThreadProcessTerminator implements ProcessTerminator { + + @Override + public boolean shouldTerminateProcess() { + return Thread.currentThread().isInterrupted(); + } + } + + /** + * A process terminator that can be used to kill a process after it exceeds * a maximum allowable run time. */ public static class TimedProcessTerminator implements ProcessTerminator { @@ -212,9 +224,6 @@ public final class ExecUtil { } } - /** - * EVERYTHING FOLLOWING THIS LINE IS DEPRECATED AND SLATED FOR REMOVAL - */ private static final Logger logger = Logger.getLogger(ExecUtil.class.getName()); private Process proc = null; private ExecUtil.StreamToStringRedirect errorStringRedirect = null; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index d24fb46edf..a96e77f623 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1211,6 +1211,18 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen */ try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), INPUT_SCAN_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES)) { if (null != manifestLock) { + + /* + * Now that the lock has been acquired, make sure the + * manifest is still here. This is a way to resolve the + * race condition between this task and case deletion + * tasks without resorting to a protocol using locking + * of the input directory. + */ + if (!filePath.toFile().exists()) { + return CONTINUE; + } + byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString()); if (null != rawData && rawData.length > 0) { AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 189e72954a..251412f530 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -8,9 +8,6 @@ AinStatusNode.status.shuttingdown=Shutting Down AinStatusNode.status.startingup=Starting Up AinStatusNode.status.title=Status AinStatusNode.status.unknown=Unknown -ArchiveDSP.dsType.text=Archive file -ArchiveFilePanel.moduleErr=Module Error -ArchiveFilePanel.moduleErr.msg=A module caused an error listening to ArchiveFilePanel updates. See log to determine which module. Some data could be incomplete.\n AutoIngestAdminActions.cancelJobAction.title=Cancel Job AutoIngestAdminActions.cancelModuleAction.title=Cancel Module AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case. @@ -169,43 +166,44 @@ CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs CTL_CasesDashboardAction=Multi-User Cases Dashboard CTL_CasesDashboardTopComponent=Cases -DataSourceOnCDriveError.noOpenCase.errMsg=Warning: Exception while getting open case. -DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on "C:" drive +DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty) DeleteCaseInputAction.menuItemText=Delete Input DeleteCaseInputAction.progressDisplayName=Delete Input DeleteCaseInputAction.taskName=input +DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output DeleteCaseInputAndOutputAction.taskName=input-and-output +DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes DeleteCaseOutputAction.menuItemText=Delete Output DeleteCaseOutputAction.progressDisplayName=Delete Output DeleteCaseOutputAction.taskName=output -DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring an exclusive case directory lock... -DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock... -DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks -DeleteCaseTask.progress.acquiringManifestFileLocks=Acquiring exclusive manifest file locks... +DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock... +DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock... DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks... DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service... -DeleteCaseTask.progress.deletingCaseOutput=Deleting case database, text index, and directory... +DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode... +DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode... DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node... -# {0} - input directory name -DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}... -DeleteCaseTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes -DeleteCaseTask.progress.deletingInputDirs=Deleting input directory... -DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job log lock node... -DeleteCaseTask.progress.deletingManifestFileLockNodes=Deleting manifest file lock nodes... -DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node... -DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources lock node -DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs... -DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file... -DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on manifest {0} -# {0} - manifest file name -DeleteCaseTask.progress.lockingManifestFile=Acquiring exclusive lock on manifest {0}... +DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode... # {0} - manifest file path -DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}... -DeleteCaseTask.progress.releasingManifestLocks=Releasing exclusive manifest file locks... -DeleteCaseTask.progress.startMessage=Preparing for deletion... -GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz) +DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}... +# {0} - manifest file path +DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}... +DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock coordination service node... +DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode... +DeleteCaseTask.Progress.gettingManifestPaths=Getting manifest file paths... +DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths... +DeleteCaseTask.progress.locatingCaseMetadataFile=Opening case metadata file... +# {0} - manifest file path +DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}... +DeleteCaseTask.progress.openingCaseDatabase=Opening the case database... +DeleteCaseTask.progress.openingCaseMetadataFile=Opening case metadata file... +# {0} - manifest file path +DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}... +# {0} - manifest file path +DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}... +DeleteCaseTask.progress.startMessage=Starting deletion... HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. @@ -406,10 +404,6 @@ AutoIngestMetricsDialog.reportTextArea.text= AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report AutoIngestMetricsDialog.closeButton.text=Close AutoIngestMetricsDialog.datePicker.toolTipText=Choose a date -ArchiveFilePanel.pathLabel.text=Browse for an archive file: -ArchiveFilePanel.browseButton.text=Browse -ArchiveFilePanel.pathTextField.text= -ArchiveFilePanel.errorLabel.text=Error Label AutoIngestMetricsDialog.startingDataLabel.text=Starting Date: AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java index 5ea4c5cc02..64a8a4c811 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java @@ -18,9 +18,11 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.event.ActionEvent; import java.util.concurrent.ExecutorService; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; @@ -46,17 +48,27 @@ final class DeleteCaseInputAction extends DeleteCaseAction { @NbBundle.Messages({ "DeleteCaseInputAction.menuItemText=Delete Input", "DeleteCaseInputAction.progressDisplayName=Delete Input", - "DeleteCaseInputAction.taskName=input" + "DeleteCaseInputAction.taskName=input" }) DeleteCaseInputAction() { super(Bundle.DeleteCaseInputAction_menuItemText(), Bundle.DeleteCaseInputAction_progressDisplayName(), Bundle.DeleteCaseInputAction_taskName()); } + @NbBundle.Messages({ + "DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)" + }) + @Override + public void actionPerformed(ActionEvent event) { + if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseInputAction_confirmationText())) { + super.actionPerformed(event); + } + } + @Override DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT, progress); } - + @Override public DeleteCaseInputAction clone() throws CloneNotSupportedException { super.clone(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java index 7a721ed947..bcfb973ff3 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java @@ -18,8 +18,11 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.event.ActionEvent; +import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; @@ -46,9 +49,19 @@ final class DeleteCaseInputAndOutputAction extends DeleteCaseAction { super(Bundle.DeleteCaseInputAndOutputAction_menuItemText(), Bundle.DeleteCaseInputAndOutputAction_progressDisplayName(), Bundle.DeleteCaseInputAndOutputAction_taskName()); } + @NbBundle.Messages({ + "DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes" + }) + @Override + public void actionPerformed(ActionEvent event) { + if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseInputAndOutputAction_confirmationText())) { + super.actionPerformed(event); + } + } + @Override DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress); + return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_ALL, progress); } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index 5b09758d4f..8435addec0 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -18,8 +18,10 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.event.ActionEvent; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; @@ -50,6 +52,16 @@ final class DeleteCaseOutputAction extends DeleteCaseAction { super(Bundle.DeleteCaseOutputAction_menuItemText(), Bundle.DeleteCaseOutputAction_progressDisplayName(), Bundle.DeleteCaseOutputAction_taskName()); } + @NbBundle.Messages({ + "DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes" + }) + @Override + public void actionPerformed(ActionEvent event) { + if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseOutputAction_confirmationText())) { + super.actionPerformed(event); + } + } + @Override DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 0451c906cd..d456f9d64c 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -23,14 +23,17 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Scanner; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; @@ -39,62 +42,53 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.progress.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.TskCoreException; /** - * A task that deletes part or all of a given case. Note that all logging done - * by this task is directed to the dedicated auto ingest dashboard log instead - * of to the general application log. + * A task that deletes part or all of a given case. Note that all logging is + * directed to the dedicated auto ingest dashboard log instead of to the general + * application log. */ -// RJCTODO: -// 1. Expand case type in case metadata to include auto ingest cases. -// Disable the delete menu item in the main app menu for auto ingest cases, -// and possibly also use this to delete the add data source capability. Could use -// this to limit the display of nodes in the in the auto ingest cases dashboard. -// 2. When an instance of this class finishes, publish an event via event bus -// so that the case browser can refresh. -// 3. Add code to file deletion utilities such that on Wimdows, for paths -// exceeding 255 chars, robocopy is invoked for the deletion. Make the new file -// deletion utility throw exceptions instead of return a boolean result code. -// 4. Make other dashbaord use the dashboard logger. -// 5. Consider moving all of the dashboard code into its own autoingest.dashboard package. -// 6. AutoIngestManager.addCompletedJob node data version updating might be out of date. -// 7. Deal with cancellation during lock releases. Look at using -// https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Uninterruptibles.html -// getUninterruptibly to do os. -// 8. With the removal of the auto ingest control panel, we can eliminate the -// completed jobs list and the processing list from AutoIngestManager. final class DeleteCaseTask implements Runnable { private static final int MANIFEST_FILE_LOCKING_TIMEOUT_MINS = 5; + private static final int MANIFEST_DELETE_TRIES = 3; + private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode"; private static final Logger logger = AutoIngestDashboardLogger.getLogger(); private final CaseNodeData caseNodeData; private final DeleteOptions deleteOption; private final ProgressIndicator progress; + private final List manifestFilePaths; private final List manifestFileLocks; private CoordinationService coordinationService; + private CaseMetadata caseMetadata; /* - * Options to support implementing differnet case deletion uses cases. + * Options to support implementing different case deletion use cases. */ - public enum DeleteOptions { + enum DeleteOptions { /** * Delete the auto ingest job manifests and corresponding data sources, - * if any, while leaving the manifest file coordination service nodes - * and the rest of the case intact. The use case is freeing auto ingest - * input directory space while retaining the option to restore the data + * while leaving the manifest file coordination service nodes and the + * rest of the case intact. The use case is freeing auto ingest input + * directory space while retaining the option to restore the data * sources, effectively restoring the case. */ DELETE_INPUT, /** - * Delete the auto ingest job coordination service nodes, if any, and - * the output for a case produced via auto ingest, while leaving the - * auto ingest job input directories intact. The use case is auto ingest + * Delete the manifest file coordination service nodes and the output + * for a case, while leaving the auto ingest job manifests and + * corresponding data sources intact. The use case is auto ingest * reprocessing of a case with a clean slate without having to restore - * the input directories. + * the manifests and data sources. */ DELETE_OUTPUT, /** @@ -117,7 +111,8 @@ final class DeleteCaseTask implements Runnable { this.caseNodeData = caseNodeData; this.deleteOption = deleteOption; this.progress = progress; - this.manifestFileLocks = new ArrayList<>(); + manifestFilePaths = new ArrayList<>(); + manifestFileLocks = new ArrayList<>(); } @Override @@ -127,9 +122,9 @@ final class DeleteCaseTask implements Runnable { public void run() { try { progress.start(Bundle.DeleteCaseTask_progress_startMessage()); - logger.log(Level.INFO, String.format("Starting attempt to delete %s (%s)", caseNodeData.getDisplayName(), deleteOption)); + logger.log(Level.INFO, String.format("Starting deleting %s (%s)", caseNodeData.getDisplayName(), deleteOption)); deleteCase(); - logger.log(Level.INFO, String.format("Finished attempt to delete %s (%s)", caseNodeData.getDisplayName(), deleteOption)); + logger.log(Level.INFO, String.format("Finished deleting %s (%s)", caseNodeData.getDisplayName(), deleteOption)); } catch (Throwable ex) { /* @@ -139,6 +134,7 @@ final class DeleteCaseTask implements Runnable { * the task, so this ensures that any such errors get logged. */ logger.log(Level.SEVERE, String.format("Unexpected error deleting %s", caseNodeData.getDisplayName()), ex); + throw ex; } finally { progress.finish(); @@ -152,9 +148,13 @@ final class DeleteCaseTask implements Runnable { "DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...", "DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...", "DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...", + "DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths...", "DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...", - "DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node...", - "DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock coordination service node..." + "DeleteCaseTask.progress.openingCaseMetadataFile=Opening case metadata file...", + "DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode...", + "DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode...", + "DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode...", + "DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode..." }) private void deleteCase() { progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc()); @@ -165,9 +165,10 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred connecting to the coordination service", caseNodeData.getDisplayName()), ex); return; } + logger.log(Level.INFO, String.format("Connected to the coordination service for deletion of %s", caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); return; } @@ -190,9 +191,10 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName())); return; } + logger.log(Level.INFO, String.format("Acquired an exclusive case name lock for %s", caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); return; } @@ -205,7 +207,6 @@ final class DeleteCaseTask implements Runnable { * deleted open and prevents another node from trying to open the * case while it is being deleted. */ - boolean success = true; // RJCTODO: Instead of having this flag, read the casenodedata instead progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock()); logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName())); String caseDirLockName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); @@ -214,88 +215,183 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName())); return; } + logger.log(Level.INFO, String.format("Acquired an exclusive case directory lock for %s", caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); return; } - /* - * Acquire exclusive locks for the auto ingest job manifest - * files for the case, if any. Manifest file locks are acquired - * by the auto ingest node (AIN) input directory scanning tasks - * when they look for auto ingest jobs to enqueue, and by the - * AIN job processing tasks when they execute a job. Acquiring - * these locks here ensures that the scanning tasks and job - * processing tasks cannot do anything with the auto ingest jobs - * for a case during case deletion. - */ - progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); - logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName())); + progress.progress(Bundle.DeleteCaseTask_progress_gettingManifestPaths()); + logger.log(Level.INFO, String.format("Getting manifest file paths for %s", caseNodeData.getDisplayName())); try { - if (!acquireManifestFileLocks()) { - logger.log(Level.INFO, String.format("Could not delete %s because a manifest file lock was already held by another host", caseNodeData.getDisplayName())); - return; - } - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex); + getManifestFilePaths(); + } catch (IOException | CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("An error occurred getting the manifest file paths", caseNodeData.getDisplayName()), ex); return; } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); return; } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); + return; + } + + if (!manifestFilePaths.isEmpty()) { + logger.log(Level.INFO, String.format("Obtained manifest file paths for %s", caseNodeData.getDisplayName())); + /* + * Acquire exclusive locks for the auto ingest job manifest + * files for the case, if any. Manifest file locks are + * acquired by the auto ingest node (AIN) input directory + * scanning tasks when they look for auto ingest jobs to + * enqueue, and by the AIN job execution tasks when they do + * a job. Acquiring these locks here ensures that the + * scanning tasks and job execution tasks cannot do anything + * with the auto ingest jobs for a case during case + * deletion. + */ + progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); + logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName())); + try { + if (!acquireManifestFileLocks()) { + logger.log(Level.INFO, String.format("Could not delete %s because at least one manifest file lock was already held by another host", caseNodeData.getDisplayName())); + return; + } + } catch (IOException | CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex); + return; + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); + return; + } + logger.log(Level.INFO, String.format("Acquired exclusive manifest file locks for %s", caseNodeData.getDisplayName())); + + } else { + logger.log(Level.INFO, String.format("No manifest file paths found for %s", caseNodeData.getDisplayName())); + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); releaseManifestFileLocks(); return; } - if (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL) { + final File caseDirectory = caseNodeData.getDirectory().toFile(); + if (caseDirectory.exists()) { + progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile()); + logger.log(Level.INFO, String.format("Locating case metadata file for %s", caseNodeData.getDisplayName())); + Path caseMetadataPath = CaseMetadata.getCaseMetadataFile(caseNodeData.getDirectory()); + if (caseMetadataPath != null) { + logger.log(Level.INFO, String.format("Found case metadata file for %s", caseNodeData.getDisplayName())); + try { + caseMetadata = new CaseMetadata(caseMetadataPath); + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); + releaseManifestFileLocks(); + return; + } + + if (!manifestFilePaths.isEmpty() && (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL)) { + try { + deleteAutoIngestInput(); + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); + releaseManifestFileLocks(); + return; + } + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); + releaseManifestFileLocks(); + return; + } + + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + try { + logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName())); + Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); + logger.log(Level.INFO, String.format("Deleted output for %s", caseNodeData.getDisplayName())); + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); + releaseManifestFileLocks(); + return; + } + } + + } catch (CaseMetadata.CaseMetadataException ex) { + logger.log(Level.SEVERE, String.format("Error reading metadata file for %s", caseNodeData.getDisplayName()), ex); + } + + } else { + logger.log(Level.WARNING, String.format("No case metadata file found for %s", caseNodeData.getDisplayName())); + } + + } else { + setDeletedItemFlag(CaseNodeData.DeletedFlags.CASE_DIR); + logger.log(Level.INFO, String.format("No case directory found for %s", caseNodeData.getDisplayName())); + } + + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); + logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); try { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); - deleteAutoIngestInput(); - } catch (IOException ex) { - // RJCTODO: + Case.deleteCaseResourcesLockNode(caseNodeData, progress); + logger.log(Level.INFO, String.format("Deleted case resources znode for %s", caseNodeData.getDisplayName())); + } catch (CoordinationServiceException ex) { + if (!isNoNodeException(ex)) { + logger.log(Level.SEVERE, String.format("Error deleting case resources znode for %s", caseNodeData.getDisplayName()), ex); + } } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); + releaseManifestFileLocks(); + return; + } + + if (Thread.currentThread().isInterrupted()) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); + releaseManifestFileLocks(); + return; + } + + progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); + logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName())); + String logFilePath = CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseNodeData.getDirectory()); + try { + coordinationService.deleteNode(CategoryNode.CASES, logFilePath); + logger.log(Level.INFO, String.format("Deleted case auto ingest job log znode for %s", caseNodeData.getDisplayName())); + } catch (CoordinationServiceException ex) { + if (!isNoNodeException(ex)) { + logger.log(Level.SEVERE, String.format("Error deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()), ex); + } + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); releaseManifestFileLocks(); return; } } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); releaseManifestFileLocks(); return; } if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { try { - success = deleteCaseOutput(); + logger.log(Level.INFO, String.format("Deleting manifest file znodes for %s", caseNodeData.getDisplayName())); + deleteManifestFileNodes(); + logger.log(Level.INFO, String.format("Deleted manifest file znodes for %s", caseNodeData.getDisplayName())); } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); - releaseManifestFileLocks(); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); return; } } - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); - releaseManifestFileLocks(); - return; - } - - try { - if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { - success = deleteManifestFileNodes(); - } else { - releaseManifestFileLocks(); - } - } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); - return; - } + releaseManifestFileLocks(); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the case directory lock", caseNodeData.getDisplayName()), ex); @@ -303,7 +399,7 @@ final class DeleteCaseTask implements Runnable { } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); return; } @@ -314,14 +410,20 @@ final class DeleteCaseTask implements Runnable { * leave the node so that what was and was not deleted can be * inspected. */ - if (success && (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL)) { - progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode()); + if ((deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.DATA_SOURCES) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES)) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode()); + logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); try { Case.deleteCaseDirectoryLockNode(caseNodeData, progress); + logger.log(Level.INFO, String.format("Deleted case directory znode for %s", caseNodeData.getDisplayName())); } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex); + logger.log(Level.SEVERE, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex); } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); return; } } @@ -332,7 +434,7 @@ final class DeleteCaseTask implements Runnable { } if (Thread.currentThread().isInterrupted()) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName())); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); return; } @@ -341,14 +443,109 @@ final class DeleteCaseTask implements Runnable { * service node for it can be deleted if the use case requires it. */ if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { - progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode()); + progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseNameCoordSvcNode()); + logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName())); try { String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); - coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); // RJCTODO: Should this be a Case method? + coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); + logger.log(Level.INFO, String.format("Deleted case name znode for %s", caseNodeData.getDisplayName())); } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex); + logger.log(Level.SEVERE, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex); } catch (InterruptedException ex) { - logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex); + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); + } + } + } + + /** + * Gets the manifest file paths for the case, if there are any. + * + * @throws CoordinationServiceException If there is an error completing a + * coordination service operation. + * @throws InterruptedException If the thread in which this task is + * running is interrupted while blocked + * waiting for a coordination service + * operation to complete. + * @throws IOException If there is an error reading the + * manifests list file. + */ + private void getManifestFilePaths() throws IOException, CoordinationServiceException, InterruptedException { + final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName()); + final File manifestListsFile = manifestsListFilePath.toFile(); + if (manifestListsFile.exists()) { + getManifestPathsFromFile(manifestsListFilePath); + } else { + getManifestPathsFromNodes(); + } + if (manifestFilePaths.isEmpty()) { + setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES); + } + } + + /** + * Gets a list of the manifest file paths for the case by reading them from + * the manifests list file for the case. + * + * @param manifestsListFilePath The path of the manifests list file. + * + * @throws IOException If there is an error reading the manifests + * list file. + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + private void getManifestPathsFromFile(Path manifestsListFilePath) throws IOException, InterruptedException { + try (final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath)) { + while (manifestsListFileScanner.hasNextLine()) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + final Path manifestFilePath = Paths.get(manifestsListFileScanner.nextLine()); + if (manifestFilePath.toFile().exists()) { + manifestFilePaths.add(manifestFilePath); + } + } + } + } + + /** + * Gets a list of the manifest file paths for the case by sifting through + * the node data of the manifest file coordination service nodes and + * matching on case name. + * + * @throws CoordinationServiceException If there is an error completing a + * coordination service operation. + * @throws InterruptedException If the thread in which this task is + * running is interrupted while blocked + * waiting for a coordination service + * operation to complete. + */ + private void getManifestPathsFromNodes() throws CoordinationServiceException, InterruptedException { + /* + * Get the original, undecorated case name from the case directory. This + * is necessary because the case display name can be changed and the + * original case name may have a time stamp added to make it unique, + * depending on how the case was created. An alternative aproach would + * be to strip off any time stamp from the case name in the case node + * data. + */ + String caseName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + final List nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); + for (String manifestNodeName : nodeNames) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + try { + final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodeName); + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes); + if (caseName.equals(nodeData.getCaseName())) { + Path manifestFilePath = nodeData.getManifestFilePath(); + if (manifestFilePath.toFile().exists()) { + manifestFilePaths.add(manifestFilePath); + } + } + } catch (CoordinationServiceException | InvalidDataException ex) { + logger.log(Level.WARNING, String.format("Error getting coordination service node data from %s", manifestNodeName), ex); } } } @@ -369,199 +566,217 @@ final class DeleteCaseTask implements Runnable { @NbBundle.Messages({ "# {0} - manifest file path", "DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}..." }) - private boolean acquireManifestFileLocks() throws CoordinationServiceException, InterruptedException { + private boolean acquireManifestFileLocks() throws IOException, CoordinationServiceException, InterruptedException { /* - * Get the "original" case name that from the case directory. This is - * necessary because the case display name can be changed and the case - * name may have a time stamp added to make it unique, depending on how - * the case was created. An alternative aproach would be to strip the - * time stamp from the case name in the case node data instead, but the - * code for that is already in the utility method called here. + * When acquiring the locks, it is reasonable to block briefly, since + * the auto ingest node (AIN) input directory scanning tasks do a lot of + * short-term acquiring and releasing of the same locks. The assumption + * here is that the originator of this case deletion task is not asking + * for deletion of a case that has a job that an auto ingest node (AIN) + * job execution task is working on and that + * MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long anyway, so + * waiting a bit should be fine. + * */ - String caseName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + boolean allLocksAcquired = true; try { - boolean allLocksAcquired = true; - // RJCTODO: Read in the list of manifests for the case instead of - // inspecting the nodes this way, once the recording of the - // manifests is in place. - final List nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); - for (String manifestPath : nodeNames) { + for (Path manifestPath : manifestFilePaths) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } - byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath); - if (nodeBytes == null || nodeBytes.length <= 0) { - logger.log(Level.WARNING, String.format("Empty coordination service node data found for %s", manifestPath)); - continue; - } - - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - - AutoIngestJobNodeData nodeData; - try { - nodeData = new AutoIngestJobNodeData(nodeBytes); - } catch (InvalidDataException ex) { - logger.log(Level.WARNING, String.format("Invalid coordination service node data found for %s", manifestPath), ex); - continue; - } - - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - - if (caseName.equals(nodeData.getCaseName())) { - /* - * When acquiring manifest file locks, it is reasonable to - * block while acquiring this lock since the auto ingest - * node (AIN) input directory scanning tasks do a lot of - * short-term acquiring and releasing of manifest file - * locks. The assumption here is that the originator of this - * case deletion task is not asking for deletion of a case - * that has a job an auto ingest node (AIN) job processing - * task is working on and that - * MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long, - * anyway, so we can and should wait a bit. - */ - logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName())); - progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath)); - CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath, MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES); - if (null != manifestLock) { - manifestFileLocks.add(manifestLock); - } else { - allLocksAcquired = false; - logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName())); - releaseManifestFileLocks(); - break; - } + progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath.toString())); + logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName())); + CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES); + if (null != manifestLock) { + manifestFileLocks.add(manifestLock); + } else { + logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName())); + allLocksAcquired = false; + releaseManifestFileLocks(); + break; } } - return allLocksAcquired; - } catch (CoordinationServiceException | InterruptedException ex) { releaseManifestFileLocks(); throw ex; } + return allLocksAcquired; } /** * Deletes the auto ingest job input manifests for the case along with the * corresponding data sources. * - * @throws IOException If there is an error opening the case - * manifests list file. * @throws InterruptedException If the thread in which this task is running * is interrupted while blocked waiting for a * coordination service operation to complete. */ @NbBundle.Messages({ + "DeleteCaseTask.progress.openingCaseDatabase=Opening the case database...", + "# {0} - manifest file path", "DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}...", "# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}..." }) - private void deleteAutoIngestInput() throws IOException, InterruptedException { - boolean allInputDeleted = true; - final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName()); - final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath); - while (manifestsListFileScanner.hasNext()) { + private void deleteAutoIngestInput() throws InterruptedException { + SleuthkitCase caseDb = null; + try { + progress.progress(Bundle.DeleteCaseTask_progress_openingCaseDatabase()); + logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName())); + caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory()); + List dataSources = caseDb.getDataSources(); + if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } - final String manifestFilePath = manifestsListFileScanner.next(); - final File manifestFile = new File(manifestFilePath); - if (manifestFile.exists()) { - // RJCTODO: Parse file, open case database, delete data sources - // before deleting manifest file - progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath)); - logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); - if (manifestFile.delete()) { - logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); - allInputDeleted = false; + + boolean allInputDeleted = true; + for (Path manifestFilePath : manifestFilePaths) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + final File manifestFile = manifestFilePath.toFile(); + if (manifestFile.exists()) { + progress.progress(Bundle.DeleteCaseTask_progress_parsingManifest(manifestFilePath)); + logger.log(Level.INFO, String.format("Parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + Manifest manifest = null; + for (ManifestFileParser parser : Lookup.getDefault().lookupAll(ManifestFileParser.class)) { + if (parser.fileIsManifest(manifestFilePath)) { + try { + manifest = parser.parse(manifestFilePath); + break; + } catch (ManifestFileParser.ManifestFileParserException ex) { + logger.log(Level.WARNING, String.format("Error parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); + } + } + } + if (manifest != null) { + if (deleteDataSources(manifest, dataSources)) { + /* + * Delete the manifest file, allowing a few retries. + * This is a way to resolve the race condition + * between this task and auto ingest node (AIN) + * input directory scanning tasks, which parse + * manifests (actually all files) before getting a + * coordination service lock, without resorting to a + * protocol using locking of the input directory. + */ + progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath)); + logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + int tries = 0; + boolean deleted = false; + while (!deleted && tries < MANIFEST_DELETE_TRIES) { + deleted = manifestFile.delete(); + if (!deleted) { + ++tries; + Thread.sleep(1000); + } + } + if (deleted) { + /* + * Delete the input directory if it is empty. + */ + final Path inputDirectoryPath = manifestFilePath.getParent(); + final File inputDirectory = inputDirectoryPath.toFile(); + File[] files = inputDirectory.listFiles(); + if (files == null || files.length == 0) { + if (!inputDirectory.delete()) { + logger.log(Level.WARNING, String.format("Failed to delete empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); + } + } + + } else { + logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + allInputDeleted = false; + } + } + } else { + logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + } } } if (allInputDeleted) { setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES); } + + } catch (TskCoreException | UserPreferencesException ex) { + logger.log(Level.INFO, String.format("Failed to open the case database for %s", caseNodeData.getDisplayName()), ex); + + } finally { + if (caseDb != null) { + caseDb.close(); + } } } /** - * Deletes the case database, the text index, the case directory, and the - * case resources and auto ingest log coordination service lock nodes for - * the case. + * Locates and deletes the data source files referenced by a manifest. * - * @return If true if all of the case output that was found was deleted, - * false otherwise. + * @param manifest A manifest. + * @param dataSources The data sources in the case as obtained from the case + * database. * - * @throws InterruptedException If the thread in which this task is running - * is interrupted while blocked waiting for a - * coordination service operation to complete. + * @return True if all of the data source files werre deleted, false + * otherwise. */ - @NbBundle.Messages({ - "DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file...", - "DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources coordination service node...", - "DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job coordination service node..." - }) - private boolean deleteCaseOutput() throws InterruptedException { - boolean errorsOccurred = false; - progress.progress(Bundle.DeleteCaseTask_progress_locatingCaseMetadataFile()); - logger.log(Level.INFO, String.format("Locating metadata file for %s", caseNodeData.getDisplayName())); - CaseMetadata caseMetadata = null; - final File caseDirectory = caseNodeData.getDirectory().toFile(); - if (caseDirectory.exists()) { - final File[] filesInDirectory = caseDirectory.listFiles(); - if (filesInDirectory != null) { - for (File file : filesInDirectory) { - if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { - try { - caseMetadata = new CaseMetadata(Paths.get(file.getPath())); - } catch (CaseMetadata.CaseMetadataException ex) { - logger.log(Level.WARNING, String.format("Error getting opening case metadata file for %s", caseNodeData.getDisplayName()), ex); - } - break; + private boolean deleteDataSources(Manifest manifest, List dataSources) { + /* + * There are two possibilities here. The data source may be an image, + * and if so, it may be split into multiple files. In this case, all of + * the files for the image need to be deleted. Otherwise, the data + * source is a single directory or file (a logical file, logical file + * set, report file, archive file, etc.). In this case, just the file + * referenced by the manifest will be deleted. + */ + boolean allFilesDeleted = true; // RJCTODO: add progress messages + Set filesToDelete = new HashSet<>(); + final String dataSourceFileName = manifest.getDataSourceFileName(); + final String dataSourceDeviceId = manifest.getDeviceId(); + for (DataSource dataSource : dataSources) { + if (dataSource instanceof Image) { + Image image = (Image) dataSource; + if (image.getName().equals(dataSourceFileName) && image.getDeviceId().equals(dataSourceDeviceId)) { + String[] imageFilePaths = image.getPaths(); + for (String path : imageFilePaths) { + Path imageFilePath = Paths.get(path); + filesToDelete.add(imageFilePath); } + break; } } + } + if (filesToDelete.isEmpty()) { + final Path dataSourcePath = manifest.getDataSourcePath(); + filesToDelete.add(dataSourcePath); + } - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - - if (caseMetadata != null) { - logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName())); - errorsOccurred = Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); // RJCTODO: CHeck for errors occurred? - } else { - logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s", caseNodeData.getDisplayName())); + for (Path path : filesToDelete) { + File fileOrDir = path.toFile(); + if (fileOrDir.exists() && !FileUtil.deleteFileDir(fileOrDir)) { + allFilesDeleted = false; + logger.log(Level.INFO, String.format("Failed to delete data source file at %s for %s", path, caseNodeData.getDisplayName())); } } - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } + return allFilesDeleted; + } - progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); - try { - Case.deleteCaseResourcesLockNode(caseNodeData, progress); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case resources coordiation service node for %s", caseNodeData.getDisplayName()), ex); + /** + * Examines a coordination service exception to try to determine if it is a + * no node exception. + * + * @param ex A coordination service exception. + * + * @return True or false. + */ + private boolean isNoNodeException(CoordinationServiceException ex) { + boolean isNodeNodeEx = false; + Throwable cause = ex.getCause(); + if (cause != null) { + String causeMessage = cause.getMessage(); + isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT); } - - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - - // RJCTODO: Check to see if getNodeData return null if the node does not exist; - // if so, make use of it - progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); - logger.log(Level.INFO, String.format("Deleting case auto ingest job log coordiation service node for %s", caseNodeData.getDisplayName())); - String logFilePath = CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseNodeData.getDirectory()); - try { - coordinationService.deleteNode(CategoryNode.CASES, logFilePath); - } catch (CoordinationServiceException ex) { - logger.log(Level.WARNING, String.format("Error deleting case auto ingest job log coordiation service node for %s", caseNodeData.getDisplayName()), ex); - } - - return errorsOccurred; + return isNodeNodeEx; } /** @@ -569,7 +784,7 @@ final class DeleteCaseTask implements Runnable { * task. */ @NbBundle.Messages({ - "# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive coordination service lock on the manifest file {0}..." + "# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}..." }) private void releaseManifestFileLocks() { for (Lock manifestFileLock : manifestFileLocks) { @@ -597,11 +812,13 @@ final class DeleteCaseTask implements Runnable { * coordination service operation to complete. */ @Messages({ - "# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file coordination service node for {0}..." + "# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}..." }) - private boolean deleteManifestFileNodes() throws InterruptedException { + private void deleteManifestFileNodes() throws InterruptedException { boolean allINodesDeleted = true; - for (Lock manifestFileLock : manifestFileLocks) { + Iterator iterator = manifestFileLocks.iterator(); + while (iterator.hasNext()) { + Lock manifestFileLock = iterator.next(); String manifestFilePath = manifestFileLock.getNodePath(); try { progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); @@ -614,9 +831,11 @@ final class DeleteCaseTask implements Runnable { allINodesDeleted = false; logger.log(Level.WARNING, String.format("Error deleting the manifest file coordination service node for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); } + iterator.remove(); + } + if (allINodesDeleted) { + setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES); } - manifestFileLocks.clear(); - return allINodesDeleted; } /** From 643aaf092374283992b0578e67e658f9e1e41e8d Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 22 Mar 2019 18:59:04 -0400 Subject: [PATCH 10/32] Admin case deletion --- .../sleuthkit/autopsy/casemodule/Case.java | 7 +- .../autoingest/AutoIngestAdminActions.java | 69 --------------- .../autoingest/AutoIngestControlPanel.form | 18 ++-- .../autoingest/AutoIngestControlPanel.java | 15 ++-- .../autoingest/AutoIngestJobsNode.java | 1 - .../autoingest/AutoIngestManager.java | 6 -- .../autoingest/AutoIngestMonitor.java | 88 ------------------- .../autoingest/Bundle.properties-MERGED | 10 +-- .../CasesDashboardTopComponent.java | 2 +- .../autoingest/DeleteCaseAction.java | 1 - .../autoingest/DeleteCaseInputAction.java | 2 +- .../DeleteCaseInputAndOutputAction.java | 2 +- .../autoingest/DeleteCaseOutputAction.java | 2 +- .../autoingest/DeleteCaseTask.java | 28 ++---- .../recentactivity/Bundle.properties-MERGED | 4 +- 15 files changed, 39 insertions(+), 216 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index c60d3df8b3..8c7bf94913 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1074,11 +1074,10 @@ public class Case { * during a wait. */ @Beta - public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, - ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { + public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { boolean errorsOccurred = false; try { - deleteCaseDatabase(caseNodeData, metadata, progressIndicator, logger); + deleteCaseDatabase(caseNodeData, metadata, progressIndicator); } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { errorsOccurred = true; logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); @@ -1132,7 +1131,7 @@ public class Case { @Messages({ "Case.progressMessage.deletingCaseDatabase=Deleting case database..." }) - private static void deleteCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { + private static void deleteCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index 1e1b82d8e7..7a31ab6573 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -293,75 +293,6 @@ final class AutoIngestAdminActions { } } - @NbBundle.Messages({"AutoIngestAdminActions.deleteCaseAction.title=Delete Case", - "AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case."}) - static final class DeleteCaseAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - private final AutoIngestJob job; - - DeleteCaseAction(AutoIngestJob selectedJob) { - super(Bundle.AutoIngestAdminActions_deleteCaseAction_title()); - this.job = selectedJob; - } - - @Override - public void actionPerformed(ActionEvent e) { - if (job == null) { - return; - } - - final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); - if (tc == null) { - return; - } - - AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); - if (dashboard != null) { - String caseName = job.getManifest().getCaseName(); - - Object[] options = { - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.Delete"), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DoNotDelete") - }; - Object[] msgContent = {org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DeleteAreYouSure") + "\"" + caseName + "\"?"}; - int reply = JOptionPane.showOptionDialog(dashboard, - msgContent, - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmDeletionHeader"), - JOptionPane.DEFAULT_OPTION, - JOptionPane.WARNING_MESSAGE, - null, - options, - options[JOptionPane.NO_OPTION]); - if (reply == JOptionPane.YES_OPTION) { - EventQueue.invokeLater(() -> { - dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job); - - dashboard.getCompletedJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshChildrenEvent(dashboard.getMonitor())); - dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - if (AutoIngestManager.CaseDeletionResult.FAILED == result) { - JOptionPane.showMessageDialog(dashboard, - String.format("Could not delete case %s. It may be in use.", caseName), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), - JOptionPane.INFORMATION_MESSAGE); - } else if (AutoIngestManager.CaseDeletionResult.PARTIALLY_DELETED == result) { - JOptionPane.showMessageDialog(dashboard, - String.format("Could not fully delete case %s. See log for details.", caseName), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), - JOptionPane.INFORMATION_MESSAGE); - } - }); - } - } - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); //To change body of generated methods, choose Tools | Templates. - } - } - @NbBundle.Messages({"AutoIngestAdminActions.showCaseLogAction.title=Show Case Log", "AutoIngestAdminActions.showCaseLogActionFailed.title=Unable to display case log", "AutoIngestAdminActions.showCaseLogActionFailed.message=Case log file does not exist", diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form index db37699f2d..62855a7bdb 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form @@ -57,13 +57,15 @@ - - - - - - - + + + + + + + + + @@ -133,7 +135,7 @@ - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index 068a856a20..78d8853d7b 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -1532,12 +1532,13 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { .addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 1021, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 1021, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(bnShowCaseLog, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(bnReprocessJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnReprocessJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(bnShowCaseLog, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addGroup(layout.createSequentialGroup() .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 1021, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) @@ -1595,7 +1596,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { .addGroup(layout.createSequentialGroup() .addGap(68, 68, 68) .addComponent(bnReprocessJob, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(35, 35, 35) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(bnShowCaseLog, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 1cde409622..5541ac6789 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -380,7 +380,6 @@ final class AutoIngestJobsNode extends AbstractNode { break; case COMPLETED_JOB: actions.add(new AutoIngestAdminActions.ReprocessJobAction(jobWrapper.getJob())); - actions.add(new AutoIngestAdminActions.DeleteCaseAction(jobWrapper.getJob())); actions.add(new AutoIngestAdminActions.ShowCaseLogAction(jobWrapper.getJob())); break; default: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index a96e77f623..a85a4993ae 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -3129,12 +3129,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } - enum CaseDeletionResult { - FAILED, - PARTIALLY_DELETED, - FULLY_DELETED - } - static final class AutoIngestManagerException extends Exception { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 1251155100..9fbd222cad 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.experimental.autoingest; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.nio.file.Path; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -38,20 +37,15 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; -import org.sleuthkit.autopsy.casemodule.Case; -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.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.coreutils.StopWatch; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus; import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.DELETED; import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.PENDING; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.CaseDeletionResult; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType; @@ -659,88 +653,6 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } } - /** - * Deletes a case. This includes deleting the case directory, the text - * index, and the case database. This does not include the directories - * containing the data sources and their manifests. - * - * @param job The job whose case you want to delete - * - * @return A result code indicating success, partial success, or failure. - */ - CaseDeletionResult deleteCase(AutoIngestJob job) { - String caseName = job.getManifest().getCaseName(); - Path caseDirectoryPath = job.getCaseDirectoryPath(); - Path metadataFilePath = caseDirectoryPath.resolve(caseName + CaseMetadata.getFileExtension()); - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - synchronized (jobsLock) { - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to acquire jobsLock (Java monitor in AutoIngestMonitor class) for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath)); - stopWatch.reset(); - stopWatch.start(); - try { - CaseMetadata metadata = new CaseMetadata(metadataFilePath); - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to read case metadata for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath)); - stopWatch.reset(); - stopWatch.start(); - Case.deleteCase(metadata); - } catch (CaseMetadata.CaseMetadataException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to read case metadata file %s for case %s at %s", metadataFilePath, caseName, caseDirectoryPath), ex); - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to fail to read case metadata file %s for case %s at %s", stopWatch.getElapsedTimeSecs(), metadataFilePath, caseName, caseDirectoryPath)); - return CaseDeletionResult.FAILED; - } catch (CaseActionException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to delete case %s at %s", caseName, caseDirectoryPath), ex); - return CaseDeletionResult.FAILED; - } - - // Update the state of completed jobs associated with this case to indicate - // that the case has been deleted - stopWatch.reset(); - stopWatch.start(); - List completedJobs = getCompletedJobs(); - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to get completed jobs listing for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath)); - stopWatch.reset(); - stopWatch.start(); - for (AutoIngestJob completedJob : completedJobs) { - if (caseName.equals(completedJob.getManifest().getCaseName())) { - try { - completedJob.setProcessingStatus(DELETED); - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(completedJob); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, completedJob.getManifest().getFilePath().toString(), nodeData.toArray()); - } catch (CoordinationServiceException | InterruptedException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to update completed job node data for %s when deleting case %s at %s", completedJob.getManifest().getFilePath(), caseName, caseDirectoryPath), ex); - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to fail to update job node data for completed jobs for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath)); - return CaseDeletionResult.PARTIALLY_DELETED; - } - } - } - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to update job node data for completed jobs for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath)); - - // Remove jobs associated with this case from the completed jobs collection. - stopWatch.reset(); - stopWatch.start(); - completedJobs.removeIf((AutoIngestJob completedJob) - -> completedJob.getManifest().getCaseName().equals(caseName)); - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to remove completed jobs for case %s at %s from current jobs snapshot", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath)); - - // Publish a message to update auto ingest nodes. - stopWatch.reset(); - stopWatch.start(); - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); - stopWatch.stop(); - LOGGER.log(Level.INFO, String.format("Used %d s to publish job deletion event for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath)); - } - - return CaseDeletionResult.FULLY_DELETED; - } - /** * Send the given control event to the given node. * diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 251412f530..076dbc9f42 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -166,15 +166,15 @@ CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs CTL_CasesDashboardAction=Multi-User Cases Dashboard CTL_CasesDashboardTopComponent=Cases -DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty) +DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty) DeleteCaseInputAction.menuItemText=Delete Input DeleteCaseInputAction.progressDisplayName=Delete Input DeleteCaseInputAction.taskName=input -DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes +DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty)\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output DeleteCaseInputAndOutputAction.taskName=input-and-output -DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes +DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes DeleteCaseOutputAction.menuItemText=Delete Output DeleteCaseOutputAction.progressDisplayName=Delete Output DeleteCaseOutputAction.taskName=output @@ -184,17 +184,13 @@ DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service... DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode... DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode... -DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node... DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode... # {0} - manifest file path DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}... # {0} - manifest file path DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}... -DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock coordination service node... DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode... -DeleteCaseTask.Progress.gettingManifestPaths=Getting manifest file paths... DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths... -DeleteCaseTask.progress.locatingCaseMetadataFile=Opening case metadata file... # {0} - manifest file path DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}... DeleteCaseTask.progress.openingCaseDatabase=Opening the case database... diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java index fdf1bdfe47..ec2d1c0c17 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardTopComponent.java @@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ @TopComponent.Description( preferredID = "CasesDashboardTopComponent", - persistenceType = TopComponent.PERSISTENCE_ALWAYS + persistenceType = TopComponent.PERSISTENCE_NEVER ) @TopComponent.Registration( mode = "dashboard", diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java index 39eb32d6f0..176ced102a 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java @@ -83,7 +83,6 @@ abstract class DeleteCaseAction extends AbstractAction { Future future = executor.submit(getTask(nodeData, progress)); taskCanceller.setFuture(future); } - // RJCTODO: FIre events, remove case deletion from jobs dashboard } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java index 64a8a4c811..38202b9d0e 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java @@ -55,7 +55,7 @@ final class DeleteCaseInputAction extends DeleteCaseAction { } @NbBundle.Messages({ - "DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)" + "DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty)" }) @Override public void actionPerformed(ActionEvent event) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java index bcfb973ff3..09cd6eb4e1 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java @@ -50,7 +50,7 @@ final class DeleteCaseInputAndOutputAction extends DeleteCaseAction { } @NbBundle.Messages({ - "DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes" + "DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty)\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes" }) @Override public void actionPerformed(ActionEvent event) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index 8435addec0..4e9d34fb06 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -53,7 +53,7 @@ final class DeleteCaseOutputAction extends DeleteCaseAction { } @NbBundle.Messages({ - "DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes" + "DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes" }) @Override public void actionPerformed(ActionEvent event) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index d456f9d64c..484ad933a6 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -165,7 +165,6 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred connecting to the coordination service", caseNodeData.getDisplayName()), ex); return; } - logger.log(Level.INFO, String.format("Connected to the coordination service for deletion of %s", caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); @@ -191,7 +190,6 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName())); return; } - logger.log(Level.INFO, String.format("Acquired an exclusive case name lock for %s", caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); @@ -215,7 +213,6 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName())); return; } - logger.log(Level.INFO, String.format("Acquired an exclusive case directory lock for %s", caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); @@ -233,6 +230,7 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); return; } + logger.log(Level.INFO, String.format("Found %d manifest file paths for %s", manifestFilePaths.size(), caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); @@ -240,7 +238,6 @@ final class DeleteCaseTask implements Runnable { } if (!manifestFilePaths.isEmpty()) { - logger.log(Level.INFO, String.format("Obtained manifest file paths for %s", caseNodeData.getDisplayName())); /* * Acquire exclusive locks for the auto ingest job manifest * files for the case, if any. Manifest file locks are @@ -266,10 +263,6 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); return; } - logger.log(Level.INFO, String.format("Acquired exclusive manifest file locks for %s", caseNodeData.getDisplayName())); - - } else { - logger.log(Level.INFO, String.format("No manifest file paths found for %s", caseNodeData.getDisplayName())); } if (Thread.currentThread().isInterrupted()) { @@ -281,10 +274,9 @@ final class DeleteCaseTask implements Runnable { final File caseDirectory = caseNodeData.getDirectory().toFile(); if (caseDirectory.exists()) { progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile()); - logger.log(Level.INFO, String.format("Locating case metadata file for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Opening case metadata file for %s", caseNodeData.getDisplayName())); Path caseMetadataPath = CaseMetadata.getCaseMetadataFile(caseNodeData.getDirectory()); if (caseMetadataPath != null) { - logger.log(Level.INFO, String.format("Found case metadata file for %s", caseNodeData.getDisplayName())); try { caseMetadata = new CaseMetadata(caseMetadataPath); @@ -295,6 +287,7 @@ final class DeleteCaseTask implements Runnable { } if (!manifestFilePaths.isEmpty() && (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL)) { + logger.log(Level.INFO, String.format("Starting deletion of case output for %s", caseNodeData.getDisplayName())); try { deleteAutoIngestInput(); } catch (InterruptedException ex) { @@ -314,7 +307,6 @@ final class DeleteCaseTask implements Runnable { try { logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName())); Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); - logger.log(Level.INFO, String.format("Deleted output for %s", caseNodeData.getDisplayName())); } catch (InterruptedException ex) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); releaseManifestFileLocks(); @@ -340,7 +332,6 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); try { Case.deleteCaseResourcesLockNode(caseNodeData, progress); - logger.log(Level.INFO, String.format("Deleted case resources znode for %s", caseNodeData.getDisplayName())); } catch (CoordinationServiceException ex) { if (!isNoNodeException(ex)) { logger.log(Level.SEVERE, String.format("Error deleting case resources znode for %s", caseNodeData.getDisplayName()), ex); @@ -362,7 +353,6 @@ final class DeleteCaseTask implements Runnable { String logFilePath = CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseNodeData.getDirectory()); try { coordinationService.deleteNode(CategoryNode.CASES, logFilePath); - logger.log(Level.INFO, String.format("Deleted case auto ingest job log znode for %s", caseNodeData.getDisplayName())); } catch (CoordinationServiceException ex) { if (!isNoNodeException(ex)) { logger.log(Level.SEVERE, String.format("Error deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()), ex); @@ -384,7 +374,6 @@ final class DeleteCaseTask implements Runnable { try { logger.log(Level.INFO, String.format("Deleting manifest file znodes for %s", caseNodeData.getDisplayName())); deleteManifestFileNodes(); - logger.log(Level.INFO, String.format("Deleted manifest file znodes for %s", caseNodeData.getDisplayName())); } catch (InterruptedException ex) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); return; @@ -419,7 +408,6 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); try { Case.deleteCaseDirectoryLockNode(caseNodeData, progress); - logger.log(Level.INFO, String.format("Deleted case directory znode for %s", caseNodeData.getDisplayName())); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex); } catch (InterruptedException ex) { @@ -448,7 +436,6 @@ final class DeleteCaseTask implements Runnable { try { String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); - logger.log(Level.INFO, String.format("Deleted case name znode for %s", caseNodeData.getDisplayName())); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex); } catch (InterruptedException ex) { @@ -679,6 +666,7 @@ final class DeleteCaseTask implements Runnable { final Path inputDirectoryPath = manifestFilePath.getParent(); final File inputDirectory = inputDirectoryPath.toFile(); File[] files = inputDirectory.listFiles(); + logger.log(Level.INFO, String.format("Deleting empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); if (files == null || files.length == 0) { if (!inputDirectory.delete()) { logger.log(Level.WARNING, String.format("Failed to delete empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); @@ -693,6 +681,8 @@ final class DeleteCaseTask implements Runnable { } else { logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); } + } else { + logger.log(Level.WARNING, String.format("Did not find manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); } } if (allInputDeleted) { @@ -822,14 +812,14 @@ final class DeleteCaseTask implements Runnable { String manifestFilePath = manifestFileLock.getNodePath(); try { progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); - logger.log(Level.INFO, String.format("Releasing the exclusive coordination service lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Releasing the lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); manifestFileLock.release(); progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileNode(manifestFilePath)); - logger.log(Level.INFO, String.format("Deleting the manifest file coordination service node for %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName())); coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath); } catch (CoordinationServiceException ex) { allINodesDeleted = false; - logger.log(Level.WARNING, String.format("Error deleting the manifest file coordination service node for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); + logger.log(Level.WARNING, String.format("Error deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); } iterator.remove(); } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 66df1c457f..bac135a643 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -64,7 +64,7 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome @@ -194,7 +194,7 @@ SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\n\count: {2}\nSplit Tokens: \n{3} +SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\ncount: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity UsbDeviceIdMapper.parseAndLookup.text=Product: {0} From 1cfe114fcdfd6ec7c4c808bfc2cc8872652ec423 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 13:17:41 -0400 Subject: [PATCH 11/32] Remove non-existent package from Core/nbproject/project.xml --- Core/nbproject/project.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 71db27d297..e91c3e7ff9 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -311,7 +311,6 @@ net.sf.sevenzipjbinding.simple.impl org.sleuthkit.autopsy.actions org.sleuthkit.autopsy.appservices - org.sleuthkit.autopsy.autoingest org.sleuthkit.autopsy.casemodule org.sleuthkit.autopsy.casemodule.events org.sleuthkit.autopsy.casemodule.multiusercases From ea2b7cdaf2c3e114c80f8b590fe7ff0d1ba85085 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 13:18:57 -0400 Subject: [PATCH 12/32] Clean/polish Case class for improved case deletion --- .../sleuthkit/autopsy/casemodule/Case.java | 238 +++++++----------- 1 file changed, 97 insertions(+), 141 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 8c7bf94913..3dd5764f32 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -690,9 +690,6 @@ public class Case { * and may be a wrapper for a lower-level * exception. */ - @Messages({ - "Case.progressIndicatorTitle.deletingCase=Deleting Case" - }) public static void deleteCurrentCase() throws CaseActionException { synchronized (caseActionSerializationLock) { if (null == currentCase) { @@ -706,6 +703,7 @@ public class Case { /** * Deletes a case. The case to be deleted must not be the "current case." + * deleting the current case must be done by calling Case.deleteCurrentCase. * * @param metadata The case metadata. * @@ -715,6 +713,7 @@ public class Case { * lower-level exception. */ @Messages({ + "Case.progressIndicatorTitle.deletingCase=Deleting Case", "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.", "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled." }) @@ -740,9 +739,9 @@ public class Case { deleteMultiUserCase(metadata, progressIndicator); } catch (InterruptedException ex) { /* - * Task cancellation is not currently supported for this - * code path, so this catch block is not expected to be - * executed. + * Note that task cancellation is not currently supported + * for this code path, so this catch block is not expected + * to be executed. */ throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex); } @@ -949,14 +948,14 @@ public class Case { deleteTextIndex(metadata, progressIndicator); } catch (KeywordSearchServiceException ex) { errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS } try { deleteCaseDirectory(metadata, progressIndicator); } catch (CaseActionException ex) { errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS } deleteFromRecentCases(metadata, progressIndicator); @@ -967,24 +966,30 @@ public class Case { } /** - * Deletes a multi-user case. + * Deletes a multi-user case. This method does so after acquiring the case + * directory coordination service lock and is intended to be used for + * deleting simple multi-user cases without auto ingest input. Note that the + * case directory coordination service node for the case is only deleted if + * no errors occurred. * - * @param metadata The case metadata. - * @param deleteCaseLockNodes Whether or not to delete the coordination - * service case directory and case name lock - * nodes for the case. - * @param progressIndicator A progress indicator. + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. * - * @throws CaseActionException If there were one or more errors deleting the - * case. The exception will have a user-friendly - * message and may be a wrapper for a - * lower-level exception. + * @throws CaseActionException If there were one or more errors deleting + * the case. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + * @throws InterruptedException If the thread this code is running in is + * interrupted while blocked, i.e., if + * cancellation of the opersation is detected + * during a wait. */ @Messages({ "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", - "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.", - "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case..." + "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", + "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources lock node...", + "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory lock coordination service node..." }) private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException { progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc()); @@ -992,7 +997,7 @@ public class Case { try { coordinationService = CoordinationService.getInstance(); } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } @@ -1000,48 +1005,46 @@ public class Case { boolean errorsOccurred = false; try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { if (dirLock == null) { - logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); //NON-NLS throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); } progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData()); try { byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); - if (nodeBytes != null && nodeBytes.length > 0) { - caseNodeData = new CaseNodeData(nodeBytes); - } else { - logger.log(Level.WARNING, String.format("Missing coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); - } + caseNodeData = new CaseNodeData(nodeBytes); } catch (CoordinationServiceException | InterruptedException | IOException ex) { - logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger); + progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode()); try { - deleteCaseResourcesLockNode(caseNodeData, progressIndicator); + String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); + coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); } catch (CoordinationServiceException ex) { if (!isNoNodeException(ex)) { errorsOccurred = true; - logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS } } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS } - // RJCTODO: Is this behavior implemented correctly? } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } if (!errorsOccurred) { + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode()); try { - deleteCaseDirectoryLockNode(caseNodeData, progressIndicator); + String casDirNodePath = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); + coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath); } catch (CoordinationServiceException | InterruptedException ex) { - logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS errorsOccurred = true; } } @@ -1055,10 +1058,12 @@ public class Case { * IMPORTANT: This is a "beta" method and is subject to change or removal * without notice! * - * Attempts to delete the case database, the text index, the case directory, - * and the case resources coordination service lock code for a case and - * removes the case from the recent cases menu of the main application - * window. + * Deletes a mulit-user case by attempting to delete the case database, the + * text index, the case directory, and the case resources coordination + * service node for a case, and removes the case from the recent cases menu + * of the main application window. Callers of this method MUST acquire and + * release the case directory lock for the case and are responsible for + * deleting the corresponding coordination service nodes, if desired. * * @param caseNodeData The coordination service node data for the case. * @param metadata The case metadata. @@ -1077,31 +1082,34 @@ public class Case { public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { boolean errorsOccurred = false; try { - deleteCaseDatabase(caseNodeData, metadata, progressIndicator); + deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger); } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS Thread.currentThread().interrupt(); return errorsOccurred; } try { - deleteTextIndex(caseNodeData, metadata, progressIndicator, logger); + deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger); } catch (KeywordSearchServiceException ex) { errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS Thread.currentThread().interrupt(); return errorsOccurred; } try { - deleteCaseDirectory(caseNodeData, metadata, progressIndicator, logger); + deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger); } catch (CaseActionException ex) { errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS Thread.currentThread().interrupt(); return errorsOccurred; } @@ -1115,6 +1123,7 @@ public class Case { * @param caseNodeData The coordination service node data for the case. * @param metadata The case metadata. * @param progressIndicator A progress indicator. + * @param logger A logger. * * @throws UserPreferencesException if there is an error getting the * database server connection info. @@ -1131,15 +1140,15 @@ public class Case { @Messages({ "Case.progressMessage.deletingCaseDatabase=Deleting case database..." }) - private static void deleteCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { + private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); - logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); + logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo(); String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS Class.forName("org.postgresql.Driver"); //NON-NLS - try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { - String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; + try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { //NON-NLS + String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS ResultSet queryResult = statement.executeQuery(dbExistsQuery); if (queryResult.next()) { String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS @@ -1150,6 +1159,29 @@ public class Case { } } + /** + * Attempts to delete the text index for a multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * @param logger A logger. + * + * @throws KeywordSearchServiceException If there is an error deleting the + * text index. + * @throws InterruptedException If interrupted while blocked + * waiting for coordination service + * data to be written to the + * coordination service node database. + */ + private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) { + logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS + deleteTextIndex(metadata, progressIndicator); + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX); + } + } + /** * Attempts to delete the text index for a case. * @@ -1170,24 +1202,24 @@ public class Case { } /** - * Attempts to delete the text index for a multi-user case. + * Attempts to delete the case directory for a multi-user case. * * @param caseNodeData The coordination service node data for the case. * @param metadata The case mnetadata. * @param progressIndicator A progress indicator. + * @param logger A logger. * - * @throws KeywordSearchServiceException If there is an error deleting the - * text index. - * @throws InterruptedException If interrupted while blocked - * waiting for coordination service - * data to be written to the - * coordination service node database. + * @throws CaseActionException if there is an error deleting the case + * directory. + * @throws InterruptedException If interrupted while blocked waiting for + * coordination service data to be written to + * the coordination service node database. */ - private static void deleteTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException { - if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) { - logger.log(Level.INFO, String.format("Deleting text index for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); - deleteTextIndex(metadata, progressIndicator); - setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX); + private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) { + logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS + deleteCaseDirectory(metadata, progressIndicator); + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR); } } @@ -1205,29 +1237,8 @@ public class Case { }) private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); - logger.log(Level.INFO, String.format("Deleting case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { - throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); - } - } - - /** - * Attempts to delete the case directory for a multi-user case. - * - * @param caseNodeData The coordination service node data for the case. - * @param metadata The case mnetadata. - * @param progressIndicator A progress indicator. - * - * @throws CaseActionException if there is an error deleting the case - * directory. - * @throws InterruptedException If interrupted while blocked waiting for - * coordination service data to be written to - * the coordination service node database. - */ - private static void deleteCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException { - if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) { - deleteCaseDirectory(metadata, progressIndicator); - setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR); + throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS } } @@ -1250,65 +1261,10 @@ public class Case { } } - /** - * IMPORTANT: This is a "beta" method and is subject to change or removal - * without notice! - * - * Deletes the case resources lock coordination service node for a - * multi-user case. - * - * @param caseNodeData The coordination service node data for the case. - * @param progressIndicator The progress indicator for the deletion - * operation. - * - * @throws CoordinationServiceException If there is an error deleting the - * coordination service node. - * @throws InterruptedException If a thread interrupt occurs while - * blocked waiting for the operation to - * complete. - */ - @Messages({ - "Case.progressMessage.deletingResourcesLockNode=Deleting case resources lock node..." - }) - @Beta - public static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException { - progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode()); - String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); - CoordinationService coordinationService = CoordinationService.getInstance(); - coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); - } - - /** - * IMPORTANT: This is a "beta" method and is subject to change or removal - * without notice! - * - * Deletes the case directory lock coordination service node for a - * multi-user case. - * - * @param caseNodeData The coordination service node data for the case. - * @param progressIndicator The progress indicator for the deletion - * operation. - * - * @throws CoordinationServiceException If there is an error deleting the - * coordination service node. - * @throws InterruptedException If a thread interrupt occurs while - * blocked waiting for the operation to - * complete. - */ - @Beta - @Messages({ - "Case.progressMessage.deletingCaseDirLockNode=Deleting case directory lock coordination service node..." - }) - public static void deleteCaseDirectoryLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException { - progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirLockNode()); - String caseDirectoryLockNodePath = caseNodeData.getDirectory().toString(); - CoordinationService coordinationService = CoordinationService.getInstance(); - coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath); - } - /** * Examines a coordination service exception to try to determine if it is a - * no node exception. + * "no node" exception, i.e., an operation was attempted on a node that does + * not exist. * * @param ex A coordination service exception. * From 1c72ebfcde58249cf13da70fbda83587dc995e34 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 13:35:27 -0400 Subject: [PATCH 13/32] Update casemodule/Bundle.properties-MERGED for improved case deletion --- .../casemodule/Bundle.properties-MERGED | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index 5033adb0ad..a5ce191229 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -51,7 +51,6 @@ Case.progressIndicatorTitle.creatingCase=Creating Case Case.progressIndicatorTitle.deletingCase=Deleting Case Case.progressIndicatorTitle.openingCase=Opening Case Case.progressMessage.cancelling=Cancelling... -Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open... Case.progressMessage.clearingTempDirectory=Clearing case temp directory... Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources... Case.progressMessage.closingCaseDatabase=Closing case database... @@ -61,9 +60,9 @@ Case.progressMessage.creatingCaseDatabase=Creating case database... Case.progressMessage.creatingCaseDirectory=Creating case directory... Case.progressMessage.creatingCaseNodeData=Creating coordination service node data... Case.progressMessage.deletingCaseDatabase=Deleting case database... +Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory lock coordination service node... Case.progressMessage.deletingCaseDirectory=Deleting case directory... -Case.progressMessage.deletingCaseDirLockNode=Deleting case directory lock coordination service node... -Case.progressMessage.deletingResourcesLockNode=Deleting case resources lock node... +Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources lock node... Case.progressMessage.deletingTextIndex=Deleting text index... Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case... Case.progressMessage.openingApplicationServiceResources=Opening application service case resources... @@ -225,15 +224,10 @@ AddImageWizardIngestConfigPanel.dsProcDone.errs.text=*Errors encountered in addi 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.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open! 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.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\ -this case are missing. Would you like to search for them now?\n\ -Previously, the image was located at:\n\ -{0}\n\ -Please note that you will still be able to browse directories and generate reports\n\ -if you choose No, but you will not be able to view file content or run the ingest process. +Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \nthis case are missing. Would you like to search for them now?\nPreviously, the image was located at:\n{0}\nPlease note that you will still be able to browse directories and generate reports\nif 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.updateCaseName.exception.msg=Error while trying to update the case name. @@ -252,12 +246,9 @@ 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} -CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\ - Case Name: {0}\n\ - Case Directory: {1} +CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \nCase Name: {0}\nCase Directory: {1} CaseDeleteAction.closeConfMsg.title=Warning: Closing the Current Case -CaseDeleteAction.msgDlg.fileInUse.msg=The delete action cannot be fully completed because the folder or file in it is open by another program.\n\n\ -Close the folder and file and try again or you can delete the case manually. +CaseDeleteAction.msgDlg.fileInUse.msg=The delete action cannot be fully completed because the folder or file in it is open by another program.\n\nClose the folder and file and try again or you can delete the case manually. CaseDeleteAction.msgDlg.fileInUse.title=Error: Folder In Use CaseDeleteAction.msgDlg.caseDelete.msg=Case {0} has been deleted. CaseOpenAction.autFilter.title={0} Case File ( {1}) @@ -289,8 +280,7 @@ NewCaseWizardAction.databaseProblem1.text=Cannot open database. Cancelling case NewCaseWizardAction.databaseProblem2.text=Error NewCaseWizardPanel1.validate.errMsg.invalidSymbols=The Case Name cannot contain any of the following symbols: \\ / : * ? " < > | NewCaseWizardPanel1.validate.errMsg.dirExists=Case directory ''{0}'' already exists. -NewCaseWizardPanel1.validate.confMsg.createDir.msg=The base directory "{0}" does not exist. \n\n\ - Do you want to create that directory? +NewCaseWizardPanel1.validate.confMsg.createDir.msg=The base directory "{0}" does not exist. \n\nDo you want to create that directory? NewCaseWizardPanel1.validate.confMsg.createDir.title=Create directory NewCaseWizardPanel1.validate.errMsg.cantCreateParDir.msg=Error: Could not create case parent directory {0} NewCaseWizardPanel1.validate.errMsg.prevCreateBaseDir.msg=Prevented from creating base directory {0} @@ -326,15 +316,15 @@ StartupWindow.title.text=Welcome UpdateRecentCases.menuItem.clearRecentCases.text=Clear Recent Cases UpdateRecentCases.menuItem.empty=-Empty- AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=Cancel -NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on \"C:\" drive -NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on \"C:\" drive. Case folder is created on the target system +NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on "C:" drive +NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on "C:" drive. Case folder is created on the target system NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text=Warning: Path to case folder is on the target system. Create case folder in mounted drive. CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1} MissingImageDialog.lbWarning.text= MissingImageDialog.lbWarning.toolTipText= NewCaseVisualPanel1.caseParentDirWarningLabel.text= -NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user +NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user\t\t NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user NewCaseVisualPanel1.caseTypeLabel.text=Case Type: SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! From 323496fb2c16575da5cab7dc41a1d83d8f5764e5 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 13:49:39 -0400 Subject: [PATCH 14/32] Improved case deletion polish/cleanup --- .../casemodule/Bundle.properties-MERGED | 31 ++++--- .../sleuthkit/autopsy/casemodule/Case.java | 85 ++++++------------- 2 files changed, 44 insertions(+), 72 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index a5ce191229..1ab571a74a 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -36,7 +36,7 @@ Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination s Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled. Case.exceptionMessage.emptyCaseDir=Must specify a case directory path. Case.exceptionMessage.emptyCaseName=Must specify a case name. -Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details. +Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details. # {0} - exception message Case.exceptionMessage.execExceptionWrapperMessage={0} # {0} - exception message @@ -60,9 +60,9 @@ Case.progressMessage.creatingCaseDatabase=Creating case database... Case.progressMessage.creatingCaseDirectory=Creating case directory... Case.progressMessage.creatingCaseNodeData=Creating coordination service node data... Case.progressMessage.deletingCaseDatabase=Deleting case database... -Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory lock coordination service node... +Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node... Case.progressMessage.deletingCaseDirectory=Deleting case directory... -Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources lock node... +Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node... Case.progressMessage.deletingTextIndex=Deleting text index... Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case... Case.progressMessage.openingApplicationServiceResources=Opening application service case resources... @@ -224,10 +224,15 @@ AddImageWizardIngestConfigPanel.dsProcDone.errs.text=*Errors encountered in addi 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.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open\! 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.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \nthis case are missing. Would you like to search for them now?\nPreviously, the image was located at:\n{0}\nPlease note that you will still be able to browse directories and generate reports\nif you choose No, but you will not be able to view file content or run the ingest process. +Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\ +this case are missing. Would you like to search for them now?\n\ +Previously, the image was located at:\n\ +{0}\n\ +Please note that you will still be able to browse directories and generate reports\n\ +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.updateCaseName.exception.msg=Error while trying to update the case name. @@ -246,9 +251,12 @@ 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} -CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \nCase Name: {0}\nCase Directory: {1} +CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\ + Case Name: {0}\n\ + Case Directory: {1} CaseDeleteAction.closeConfMsg.title=Warning: Closing the Current Case -CaseDeleteAction.msgDlg.fileInUse.msg=The delete action cannot be fully completed because the folder or file in it is open by another program.\n\nClose the folder and file and try again or you can delete the case manually. +CaseDeleteAction.msgDlg.fileInUse.msg=The delete action cannot be fully completed because the folder or file in it is open by another program.\n\n\ +Close the folder and file and try again or you can delete the case manually. CaseDeleteAction.msgDlg.fileInUse.title=Error: Folder In Use CaseDeleteAction.msgDlg.caseDelete.msg=Case {0} has been deleted. CaseOpenAction.autFilter.title={0} Case File ( {1}) @@ -280,7 +288,8 @@ NewCaseWizardAction.databaseProblem1.text=Cannot open database. Cancelling case NewCaseWizardAction.databaseProblem2.text=Error NewCaseWizardPanel1.validate.errMsg.invalidSymbols=The Case Name cannot contain any of the following symbols: \\ / : * ? " < > | NewCaseWizardPanel1.validate.errMsg.dirExists=Case directory ''{0}'' already exists. -NewCaseWizardPanel1.validate.confMsg.createDir.msg=The base directory "{0}" does not exist. \n\nDo you want to create that directory? +NewCaseWizardPanel1.validate.confMsg.createDir.msg=The base directory "{0}" does not exist. \n\n\ + Do you want to create that directory? NewCaseWizardPanel1.validate.confMsg.createDir.title=Create directory NewCaseWizardPanel1.validate.errMsg.cantCreateParDir.msg=Error: Could not create case parent directory {0} NewCaseWizardPanel1.validate.errMsg.prevCreateBaseDir.msg=Prevented from creating base directory {0} @@ -316,15 +325,15 @@ StartupWindow.title.text=Welcome UpdateRecentCases.menuItem.clearRecentCases.text=Clear Recent Cases UpdateRecentCases.menuItem.empty=-Empty- AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=Cancel -NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on "C:" drive -NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on "C:" drive. Case folder is created on the target system +NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on \"C:\" drive +NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on \"C:\" drive. Case folder is created on the target system NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text=Warning: Path to case folder is on the target system. Create case folder in mounted drive. CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1} MissingImageDialog.lbWarning.text= MissingImageDialog.lbWarning.toolTipText= NewCaseVisualPanel1.caseParentDirWarningLabel.text= -NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user\t\t +NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user NewCaseVisualPanel1.caseTypeLabel.text=Case Type: SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 3dd5764f32..b1bfcd7131 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -703,7 +703,7 @@ public class Case { /** * Deletes a case. The case to be deleted must not be the "current case." - * deleting the current case must be done by calling Case.deleteCurrentCase. + * Deleting the current case must be done by calling Case.deleteCurrentCase. * * @param metadata The case metadata. * @@ -940,7 +940,7 @@ public class Case { * lower-level exception. */ @Messages({ - "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details." + "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details." }) private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { boolean errorsOccurred = false; @@ -988,8 +988,8 @@ public class Case { "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.", "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", - "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources lock node...", - "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory lock coordination service node..." + "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...", + "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..." }) private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException { progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc()); @@ -1373,10 +1373,8 @@ public class Case { if (!fileExists) { int response = JOptionPane.showConfirmDialog( mainFrame, - NbBundle.getMessage(Case.class, - "Case.checkImgExist.confDlg.doesntExist.msg", path), - NbBundle.getMessage(Case.class, - "Case.checkImgExist.confDlg.doesntExist.title"), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), JOptionPane.YES_NO_OPTION); if (response == JOptionPane.YES_OPTION) { MissingImageDialog.makeDialog(obj_id, caseDb); @@ -1390,32 +1388,15 @@ public class Case { /* * Enable the case-specific actions. */ - CallableSystemAction.get(AddImageAction.class - ).setEnabled(true); - CallableSystemAction - .get(CaseCloseAction.class - ).setEnabled(true); - CallableSystemAction - .get(CaseDetailsAction.class - ).setEnabled(true); - CallableSystemAction - .get(DataSourceSummaryAction.class - ).setEnabled(true); - CallableSystemAction - .get(CaseDeleteAction.class - ).setEnabled(true); - CallableSystemAction - .get(OpenTimelineAction.class - ).setEnabled(true); - CallableSystemAction - .get(OpenCommVisualizationToolAction.class - ).setEnabled(true); - CallableSystemAction - .get(CommonAttributeSearchAction.class - ).setEnabled(true); - CallableSystemAction - .get(OpenOutputFolderAction.class - ).setEnabled(false); + CallableSystemAction.get(AddImageAction.class).setEnabled(true); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); + CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true); + CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); + CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true); + CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true); + CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); /* * Add the case to the recent cases tracker that supplies a list @@ -1461,33 +1442,15 @@ public class Case { /* * Disable the case-specific menu items. */ - CallableSystemAction - .get(AddImageAction.class - ).setEnabled(false); - CallableSystemAction - .get(CaseCloseAction.class - ).setEnabled(false); - CallableSystemAction - .get(CaseDetailsAction.class - ).setEnabled(false); - CallableSystemAction - .get(DataSourceSummaryAction.class - ).setEnabled(false); - CallableSystemAction - .get(CaseDeleteAction.class - ).setEnabled(false); - CallableSystemAction - .get(OpenTimelineAction.class - ).setEnabled(false); - CallableSystemAction - .get(OpenCommVisualizationToolAction.class - ).setEnabled(false); - CallableSystemAction - .get(OpenOutputFolderAction.class - ).setEnabled(false); - CallableSystemAction - .get(CommonAttributeSearchAction.class - ).setEnabled(false); + CallableSystemAction.get(AddImageAction.class).setEnabled(false); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); + CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false); + CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); + CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false); + CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); + CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false); /* * Clear the notifications in the notfier component in the lower From 4b73179b7ba3afb58e4dc0e63bff73a6aec407fa Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 13:57:01 -0400 Subject: [PATCH 15/32] Improved case deletion polish/cleanup --- .../autopsy/experimental/autoingest/Bundle.properties-MERGED | 2 -- .../sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED | 2 +- .../org/sleuthkit/autopsy/recentactivity/Bundle.properties | 2 +- .../sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 076dbc9f42..78671b4c0f 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -10,8 +10,6 @@ AinStatusNode.status.title=Status AinStatusNode.status.unknown=Unknown AutoIngestAdminActions.cancelJobAction.title=Cancel Job AutoIngestAdminActions.cancelModuleAction.title=Cancel Module -AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case. -AutoIngestAdminActions.deleteCaseAction.title=Delete Case AutoIngestAdminActions.pause.title=Pause Node AutoIngestAdminActions.progressDialogAction.title=Ingest Progress AutoIngestAdminActions.reprocessJobAction.error=Failed to reprocess job diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index 6f95dfc82f..ab7e11a07f 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -35,7 +35,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties index 7c75aeb9d0..f0f8e0341f 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties @@ -93,7 +93,7 @@ RecentDocumentsByLnk.parentModuleName=Recent Activity SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\n\count: {2}\nSplit Tokens: \n{3} +SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\nCount: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity UsbDeviceIdMapper.parseAndLookup.text=Product: {0} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index bac135a643..4a2b6e06f8 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -64,7 +64,7 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome @@ -194,7 +194,7 @@ SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\ncount: {2}\nSplit Tokens: \n{3} +SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\nCount: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity UsbDeviceIdMapper.parseAndLookup.text=Product: {0} From 721c08da0525748fd728a7ace5c44009d58b1818 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:01:37 -0400 Subject: [PATCH 16/32] Improved case deletion polish/cleanup --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 6 ++---- .../autopsy/experimental/autoingest/DeleteCaseTask.java | 6 ++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index b1bfcd7131..375454b4cb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1352,10 +1352,8 @@ public class Case { if (null != backupDbPath) { JOptionPane.showMessageDialog( mainFrame, - NbBundle.getMessage(Case.class, - "Case.open.msgDlg.updated.msg", backupDbPath), - NbBundle.getMessage(Case.class, - "Case.open.msgDlg.updated.title"), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), JOptionPane.INFORMATION_MESSAGE); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 484ad933a6..40d38f71c3 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -330,8 +330,9 @@ final class DeleteCaseTask implements Runnable { if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); + String resourcesNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); try { - Case.deleteCaseResourcesLockNode(caseNodeData, progress); + coordinationService.deleteNode(CategoryNode.CASES, resourcesNodePath); } catch (CoordinationServiceException ex) { if (!isNoNodeException(ex)) { logger.log(Level.SEVERE, String.format("Error deleting case resources znode for %s", caseNodeData.getDisplayName()), ex); @@ -406,8 +407,9 @@ final class DeleteCaseTask implements Runnable { && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES)) { progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode()); logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); + String caseDirNodePath = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); try { - Case.deleteCaseDirectoryLockNode(caseNodeData, progress); + coordinationService.deleteNode(CategoryNode.CASES, caseDirNodePath); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex); } catch (InterruptedException ex) { From a27a030210865c4541286847a5c8d714f4a17d80 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:05:44 -0400 Subject: [PATCH 17/32] Improved case deletion polish/cleanup --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 375454b4cb..8d33643d53 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2557,8 +2557,7 @@ public class Case { * one starts by awaiting termination of the executor service. */ - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class - )) { + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { /* * Create a progress indicator for the task and start the task. If * running with a GUI, the progress indicator will be a dialog box @@ -2817,8 +2816,7 @@ public class Case { * Each service gets its own independently cancellable task, and thus * its own task progress indicator. */ - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class - )) { + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { progressIndicator = new ModalDialogProgressIndicator( From f6c1e68f4c454afa164109de3e110a4363c5b1ce Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:12:35 -0400 Subject: [PATCH 18/32] Improved case deletion polish/cleanup --- .../sleuthkit/autopsy/casemodule/Case.java | 746 +++++++++--------- 1 file changed, 373 insertions(+), 373 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 8d33643d53..215e8eda58 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -928,379 +928,6 @@ public class Case { return imgPaths; } - /** - * Deletes a single-user case. - * - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. - * - * @throws CaseActionException If there were one or more errors deleting the - * case. The exception will have a user-friendly - * message and may be a wrapper for a - * lower-level exception. - */ - @Messages({ - "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details." - }) - private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { - boolean errorsOccurred = false; - try { - deleteTextIndex(metadata, progressIndicator); - } catch (KeywordSearchServiceException ex) { - errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } - - try { - deleteCaseDirectory(metadata, progressIndicator); - } catch (CaseActionException ex) { - errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } - - deleteFromRecentCases(metadata, progressIndicator); - - if (errorsOccurred) { - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); - } - } - - /** - * Deletes a multi-user case. This method does so after acquiring the case - * directory coordination service lock and is intended to be used for - * deleting simple multi-user cases without auto ingest input. Note that the - * case directory coordination service node for the case is only deleted if - * no errors occurred. - * - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. - * - * @throws CaseActionException If there were one or more errors deleting - * the case. The exception will have a - * user-friendly message and may be a wrapper - * for a lower-level exception. - * @throws InterruptedException If the thread this code is running in is - * interrupted while blocked, i.e., if - * cancellation of the opersation is detected - * during a wait. - */ - @Messages({ - "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", - "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.", - "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", - "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...", - "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..." - }) - private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException { - progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc()); - CoordinationService coordinationService; - try { - coordinationService = CoordinationService.getInstance(); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); - } - - CaseNodeData caseNodeData; - boolean errorsOccurred = false; - try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { - if (dirLock == null) { - logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); //NON-NLS - throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); - } - - progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData()); - try { - byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); - caseNodeData = new CaseNodeData(nodeBytes); - } catch (CoordinationServiceException | InterruptedException | IOException ex) { - logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); - } - - errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger); - - progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode()); - try { - String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); - coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); - } catch (CoordinationServiceException ex) { - if (!isNoNodeException(ex)) { - errorsOccurred = true; - logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } - - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); - } - - if (!errorsOccurred) { - progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode()); - try { - String casDirNodePath = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); - coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath); - } catch (CoordinationServiceException | InterruptedException ex) { - logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - errorsOccurred = true; - } - } - - if (errorsOccurred) { - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); - } - } - - /** - * IMPORTANT: This is a "beta" method and is subject to change or removal - * without notice! - * - * Deletes a mulit-user case by attempting to delete the case database, the - * text index, the case directory, and the case resources coordination - * service node for a case, and removes the case from the recent cases menu - * of the main application window. Callers of this method MUST acquire and - * release the case directory lock for the case and are responsible for - * deleting the corresponding coordination service nodes, if desired. - * - * @param caseNodeData The coordination service node data for the case. - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. - * @param logger A logger. - * - * @return True if one or more errors occurred (see log for details), false - * otherwise. - * - * @throws InterruptedException If the thread this code is running in is - * interrupted while blocked, i.e., if - * cancellation of the opersation is detected - * during a wait. - */ - @Beta - public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { - boolean errorsOccurred = false; - try { - deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger); - } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { - errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS - Thread.currentThread().interrupt(); - return errorsOccurred; - } - - try { - deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger); - } catch (KeywordSearchServiceException ex) { - errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS - Thread.currentThread().interrupt(); - return errorsOccurred; - } - - try { - deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger); - } catch (CaseActionException ex) { - errorsOccurred = true; - logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS - Thread.currentThread().interrupt(); - return errorsOccurred; - } - - return errorsOccurred; - } - - /** - * Attempts to delete the case database for a multi-user case. - * - * @param caseNodeData The coordination service node data for the case. - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. - * @param logger A logger. - * - * @throws UserPreferencesException if there is an error getting the - * database server connection info. - * @throws ClassNotFoundException if there is an error gettting the - * required JDBC driver. - * @throws SQLException if there is an error executing the SQL - * to drop the database from the database - * server. - * @throws InterruptedException If interrupted while blocked waiting for - * coordination service data to be written - * to the coordination service node - * database. - */ - @Messages({ - "Case.progressMessage.deletingCaseDatabase=Deleting case database..." - }) - private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { - if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { - progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); - logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS - CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo(); - String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS - Class.forName("org.postgresql.Driver"); //NON-NLS - try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { //NON-NLS - String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS - ResultSet queryResult = statement.executeQuery(dbExistsQuery); - if (queryResult.next()) { - String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS - statement.execute(deleteCommand); - } - } - setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DB); - } - } - - /** - * Attempts to delete the text index for a multi-user case. - * - * @param caseNodeData The coordination service node data for the case. - * @param metadata The case mnetadata. - * @param progressIndicator A progress indicator. - * @param logger A logger. - * - * @throws KeywordSearchServiceException If there is an error deleting the - * text index. - * @throws InterruptedException If interrupted while blocked - * waiting for coordination service - * data to be written to the - * coordination service node database. - */ - private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException { - if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) { - logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS - deleteTextIndex(metadata, progressIndicator); - setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX); - } - } - - /** - * Attempts to delete the text index for a case. - * - * @param metadata The case mnetadata. - * @param progressIndicator A progress indicator. - * - * @throws KeywordSearchServiceException If there is an error deleting the - * text index. - */ - @Messages({ - "Case.progressMessage.deletingTextIndex=Deleting text index..." - }) - private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException { - progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex()); - for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) { - searchService.deleteTextIndex(metadata); - } - } - - /** - * Attempts to delete the case directory for a multi-user case. - * - * @param caseNodeData The coordination service node data for the case. - * @param metadata The case mnetadata. - * @param progressIndicator A progress indicator. - * @param logger A logger. - * - * @throws CaseActionException if there is an error deleting the case - * directory. - * @throws InterruptedException If interrupted while blocked waiting for - * coordination service data to be written to - * the coordination service node database. - */ - private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException { - if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) { - logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS - deleteCaseDirectory(metadata, progressIndicator); - setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR); - } - } - - /** - * Attempts to delete the case directory for a case. - * - * @param metadata The case mnetadata. - * @param progressIndicator A progress indicator. - * - * @throws CaseActionException If there is an error deleting the case - * directory. - */ - @Messages({ - "Case.progressMessage.deletingCaseDirectory=Deleting case directory..." - }) - private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { - progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); - if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { - throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS - } - } - - /** - * Attempts to remove a case from the recent cases menu if the main - * application window is present. - * - * @param metadata The case metadata. - * @param progressIndicator A progress indicator. - */ - @Messages({ - "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..." - }) - private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) { - if (RuntimeProperties.runningWithGUI()) { - progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases()); - SwingUtilities.invokeLater(() -> { - RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString()); - }); - } - } - - /** - * Examines a coordination service exception to try to determine if it is a - * "no node" exception, i.e., an operation was attempted on a node that does - * not exist. - * - * @param ex A coordination service exception. - * - * @return True or false. - */ - private static boolean isNoNodeException(CoordinationServiceException ex) { - boolean isNodeNodeEx = false; - Throwable cause = ex.getCause(); - if (cause != null) { - String causeMessage = cause.getMessage(); - isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT); - } - return isNodeNodeEx; - } - - /** - * Sets a deleted item flag in the coordination service node data for a - * multi-user case. - * - * @param caseNodeData The coordination service node data for the case. - * @param flag The flag to set. - * - * @throws InterruptedException If interrupted while blocked waiting for - * coordination service data to be written to - * the coordination service node database. - */ - private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException { - try { - caseNodeData.setDeletedFlag(flag); - CoordinationService coordinationService = CoordinationService.getInstance(); - coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray()); - } catch (IOException | CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); - } - } - /** * Acquires an exclusive case resources lock. * @@ -2906,6 +2533,379 @@ public class Case { } + /** + * Deletes a single-user case. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there were one or more errors deleting the + * case. The exception will have a user-friendly + * message and may be a wrapper for a + * lower-level exception. + */ + @Messages({ + "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details." + }) + private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + boolean errorsOccurred = false; + try { + deleteTextIndex(metadata, progressIndicator); + } catch (KeywordSearchServiceException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + } + + try { + deleteCaseDirectory(metadata, progressIndicator); + } catch (CaseActionException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + } + + deleteFromRecentCases(metadata, progressIndicator); + + if (errorsOccurred) { + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + } + + /** + * Deletes a multi-user case. This method does so after acquiring the case + * directory coordination service lock and is intended to be used for + * deleting simple multi-user cases without auto ingest input. Note that the + * case directory coordination service node for the case is only deleted if + * no errors occurred. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there were one or more errors deleting + * the case. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + * @throws InterruptedException If the thread this code is running in is + * interrupted while blocked, i.e., if + * cancellation of the opersation is detected + * during a wait. + */ + @Messages({ + "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", + "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.", + "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", + "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...", + "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..." + }) + private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException { + progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc()); + CoordinationService coordinationService; + try { + coordinationService = CoordinationService.getInstance(); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + + CaseNodeData caseNodeData; + boolean errorsOccurred = false; + try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { + if (dirLock == null) { + logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); //NON-NLS + throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase()); + } + + progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData()); + try { + byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); + caseNodeData = new CaseNodeData(nodeBytes); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + + errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger); + + progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode()); + try { + String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); + coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); + } catch (CoordinationServiceException ex) { + if (!isNoNodeException(ex)) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + } + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + } + + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + + if (!errorsOccurred) { + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode()); + try { + String casDirNodePath = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); + coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath); + } catch (CoordinationServiceException | InterruptedException ex) { + logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + errorsOccurred = true; + } + } + + if (errorsOccurred) { + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } + } + + /** + * IMPORTANT: This is a "beta" method and is subject to change or removal + * without notice! + * + * Deletes a mulit-user case by attempting to delete the case database, the + * text index, the case directory, and the case resources coordination + * service node for a case, and removes the case from the recent cases menu + * of the main application window. Callers of this method MUST acquire and + * release the case directory lock for the case and are responsible for + * deleting the corresponding coordination service nodes, if desired. + * + * @param caseNodeData The coordination service node data for the case. + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * @param logger A logger. + * + * @return True if one or more errors occurred (see log for details), false + * otherwise. + * + * @throws InterruptedException If the thread this code is running in is + * interrupted while blocked, i.e., if + * cancellation of the opersation is detected + * during a wait. + */ + @Beta + public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException { + boolean errorsOccurred = false; + try { + deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger); + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + try { + deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger); + } catch (KeywordSearchServiceException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + try { + deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger); + } catch (CaseActionException ex) { + errorsOccurred = true; + logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS + Thread.currentThread().interrupt(); + return errorsOccurred; + } + + return errorsOccurred; + } + + /** + * Attempts to delete the case database for a multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + * @param logger A logger. + * + * @throws UserPreferencesException if there is an error getting the + * database server connection info. + * @throws ClassNotFoundException if there is an error gettting the + * required JDBC driver. + * @throws SQLException if there is an error executing the SQL + * to drop the database from the database + * server. + * @throws InterruptedException If interrupted while blocked waiting for + * coordination service data to be written + * to the coordination service node + * database. + */ + @Messages({ + "Case.progressMessage.deletingCaseDatabase=Deleting case database..." + }) + private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) { + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); + logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS + CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo(); + String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { //NON-NLS + String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS + ResultSet queryResult = statement.executeQuery(dbExistsQuery); + if (queryResult.next()) { + String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS + statement.execute(deleteCommand); + } + } + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DB); + } + } + + /** + * Attempts to delete the text index for a multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * @param logger A logger. + * + * @throws KeywordSearchServiceException If there is an error deleting the + * text index. + * @throws InterruptedException If interrupted while blocked + * waiting for coordination service + * data to be written to the + * coordination service node database. + */ + private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) { + logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS + deleteTextIndex(metadata, progressIndicator); + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX); + } + } + + /** + * Attempts to delete the text index for a case. + * + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * + * @throws KeywordSearchServiceException If there is an error deleting the + * text index. + */ + @Messages({ + "Case.progressMessage.deletingTextIndex=Deleting text index..." + }) + private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException { + progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex()); + for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) { + searchService.deleteTextIndex(metadata); + } + } + + /** + * Attempts to delete the case directory for a multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * @param logger A logger. + * + * @throws CaseActionException if there is an error deleting the case + * directory. + * @throws InterruptedException If interrupted while blocked waiting for + * coordination service data to be written to + * the coordination service node database. + */ + private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException { + if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) { + logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS + deleteCaseDirectory(metadata, progressIndicator); + setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR); + } + } + + /** + * Attempts to delete the case directory for a case. + * + * @param metadata The case mnetadata. + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is an error deleting the case + * directory. + */ + @Messages({ + "Case.progressMessage.deletingCaseDirectory=Deleting case directory..." + }) + private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); + if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { + throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS + } + } + + /** + * Attempts to remove a case from the recent cases menu if the main + * application window is present. + * + * @param metadata The case metadata. + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..." + }) + private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) { + if (RuntimeProperties.runningWithGUI()) { + progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases()); + SwingUtilities.invokeLater(() -> { + RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString()); + }); + } + } + + /** + * Examines a coordination service exception to try to determine if it is a + * "no node" exception, i.e., an operation was attempted on a node that does + * not exist. + * + * @param ex A coordination service exception. + * + * @return True or false. + */ + private static boolean isNoNodeException(CoordinationServiceException ex) { + boolean isNodeNodeEx = false; + Throwable cause = ex.getCause(); + if (cause != null) { + String causeMessage = cause.getMessage(); + isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT); + } + return isNodeNodeEx; + } + + /** + * Sets a deleted item flag in the coordination service node data for a + * multi-user case. + * + * @param caseNodeData The coordination service node data for the case. + * @param flag The flag to set. + * + * @throws InterruptedException If interrupted while blocked waiting for + * coordination service data to be written to + * the coordination service node database. + */ + private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException { + try { + caseNodeData.setDeletedFlag(flag); + CoordinationService coordinationService = CoordinationService.getInstance(); + coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray()); + } catch (IOException | CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); + } + } + /** * A case operation Cancel button listener for use with a * ModalDialogProgressIndicator when running with a GUI. From fe576cf87f9026841cd2b0d1ef5a03939d409f18 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:15:54 -0400 Subject: [PATCH 19/32] Improved case deletion polish/cleanup --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 215e8eda58..21e70637d9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -994,7 +994,6 @@ public class Case { long obj_id = entry.getKey(); String path = entry.getValue(); boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); - if (!fileExists) { int response = JOptionPane.showConfirmDialog( mainFrame, From 5dca4b30ed7992dde56731280c6c871707124936 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:17:39 -0400 Subject: [PATCH 20/32] Improved case deletion polish/cleanup --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 21e70637d9..b1d9d0012e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2173,7 +2173,6 @@ public class Case { "# {0} - service name", "Case.servicesException.notificationTitle={0} Error" }) private void openAppServiceCaseResources(ProgressIndicator progressIndicator) throws CaseActionException { - progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); /* * Each service gets its own independently cancellable/interruptible * task, running in a named thread managed by an executor service, with @@ -2182,7 +2181,7 @@ public class Case { * possible to ensure that each service task completes before the next * one starts by awaiting termination of the executor service. */ - + progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { /* * Create a progress indicator for the task and start the task. If @@ -2567,8 +2566,8 @@ public class Case { if (errorsOccurred) { throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); } - } - + } + /** * Deletes a multi-user case. This method does so after acquiring the case * directory coordination service lock and is intended to be used for @@ -2904,7 +2903,7 @@ public class Case { logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); } } - + /** * A case operation Cancel button listener for use with a * ModalDialogProgressIndicator when running with a GUI. From 44f91b9809fd140e2871aac44a28096777e1a863 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:27:32 -0400 Subject: [PATCH 21/32] Improved case deletion polish/cleanup --- .../org/sleuthkit/autopsy/casemodule/CaseMetadata.java | 8 +++----- .../autopsy/experimental/autoingest/DeleteCaseTask.java | 2 +- .../keywordsearch/multicase/MultiCaseSearcher.java | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index c84fcc2cee..89f01bd026 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -143,13 +143,11 @@ public final class CaseMetadata { * Locate the case meta data file in the supplied directory. If the file * does not exist, null is returned. * - * @param directoryPath Directory path to search + * @param directoryPath Directory path to search. * - * @return case meta data file path or null + * @return Case metadata file path or null. */ - // RJCTODO: Perhaps this should return a CaseMetadata object - // RJCTODO: It should say get...path - public static Path getCaseMetadataFile(Path directoryPath) { + public static Path getCaseMetadataFilePath(Path directoryPath) { final File[] files = directoryPath.toFile().listFiles(); if (files != null) { for (File file : files) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 40d38f71c3..d5bef1d11e 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -275,7 +275,7 @@ final class DeleteCaseTask implements Runnable { if (caseDirectory.exists()) { progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile()); logger.log(Level.INFO, String.format("Opening case metadata file for %s", caseNodeData.getDisplayName())); - Path caseMetadataPath = CaseMetadata.getCaseMetadataFile(caseNodeData.getDirectory()); + Path caseMetadataPath = CaseMetadata.getCaseMetadataFilePath(caseNodeData.getDirectory()); if (caseMetadataPath != null) { try { caseMetadata = new CaseMetadata(caseMetadataPath); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseSearcher.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseSearcher.java index b26001a28e..80395a0599 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseSearcher.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseSearcher.java @@ -259,7 +259,7 @@ final class MultiCaseSearcher { }) private static CaseMetadata getCaseMetadata(Path caseDirectoryPath) throws MultiCaseSearcherException { - Path metadataPath = CaseMetadata.getCaseMetadataFile(caseDirectoryPath); + Path metadataPath = CaseMetadata.getCaseMetadataFilePath(caseDirectoryPath); if (metadataPath != null) { try { return new CaseMetadata(metadataPath); From 6eb41faf9d6048fc66589d66474d12369e1e0a2e Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:29:10 -0400 Subject: [PATCH 22/32] Improved case deletion polish/cleanup --- .../autopsy/casemodule/CaseMetadata.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 89f01bd026..031766defb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -139,27 +139,6 @@ public final class CaseMetadata { return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US); } - /** - * Locate the case meta data file in the supplied directory. If the file - * does not exist, null is returned. - * - * @param directoryPath Directory path to search. - * - * @return Case metadata file path or null. - */ - public static Path getCaseMetadataFilePath(Path directoryPath) { - final File[] files = directoryPath.toFile().listFiles(); - if (files != null) { - for (File file : files) { - final String fileName = file.getName().toLowerCase(); - if (fileName.endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { - return file.toPath(); - } - } - } - return null; - } - /** * Constructs a CaseMetadata object for a new case. The metadata is not * persisted to the case metadata file until writeFile or a setX method is @@ -212,6 +191,27 @@ public final class CaseMetadata { readFromFile(); } + /** + * Locate the case meta data file in the supplied directory. If the file + * does not exist, null is returned. + * + * @param directoryPath Directory path to search. + * + * @return Case metadata file path or null. + */ + public static Path getCaseMetadataFilePath(Path directoryPath) { + final File[] files = directoryPath.toFile().listFiles(); + if (files != null) { + for (File file : files) { + final String fileName = file.getName().toLowerCase(); + if (fileName.endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { + return file.toPath(); + } + } + } + return null; + } + /** * Gets the full path to the case metadata file. * From d3fc817ea6da4bd6c68df5bdd5253c0be9cd0f77 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:30:38 -0400 Subject: [PATCH 23/32] Improved case deletion polish/cleanup --- Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 031766defb..20dfabd93c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -573,8 +573,7 @@ public final class CaseMetadata { caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false); } - this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, - caseNotes); + this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes); this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true)); if (null == this.caseType) { throw new CaseMetadataException("Case metadata file corrupted"); From da8d73d87888872743ff169ba586f0e9d64ad922 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 14:51:47 -0400 Subject: [PATCH 24/32] Improved case deletion polish/cleanup --- .../sleuthkit/autopsy/casemodule/Case.java | 8 +- .../CaseCoordinationServiceUtils.java | 74 ---------- .../CoordinationServiceUtils.java | 133 ++++++++++++++++++ .../MultiUserCaseNodeDataCollector.java | 14 +- .../autoingest/DeleteCaseTask.java | 16 +-- .../keywordsearch/Bundle.properties-MERGED | 2 +- 6 files changed, 153 insertions(+), 94 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index b1d9d0012e..a682a30d3d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -81,7 +81,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; -import org.sleuthkit.autopsy.casemodule.multiusercases.CaseCoordinationServiceUtils; +import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeSearchAction; import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction; @@ -944,7 +944,7 @@ public class Case { private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException { try { Path caseDirPath = Paths.get(caseDir); - String resourcesNodeName = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseDirPath); + String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath); Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); return lock; } catch (InterruptedException ex) { @@ -2625,7 +2625,7 @@ public class Case { progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode()); try { - String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); + String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory()); coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath); } catch (CoordinationServiceException ex) { if (!isNoNodeException(ex)) { @@ -2644,7 +2644,7 @@ public class Case { if (!errorsOccurred) { progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode()); try { - String casDirNodePath = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); + String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory()); coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath); } catch (CoordinationServiceException | InterruptedException ex) { logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java deleted file mode 100755 index ae252e1988..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseCoordinationServiceUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 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.multiusercases; - -import java.nio.file.Path; -import java.nio.file.Paths; -import org.sleuthkit.autopsy.coreutils.TimeStampUtils; - -/** - * Utility methods for using the coordination service for multi-user cases. - */ -public class CaseCoordinationServiceUtils { - - private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS - private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS - - public static String getCaseResourcesLockName(Path caseDirectoryPath) { - return caseDirectoryPath + RESOURCES_LOCK_SUFFIX; - } - - public static String getCaseAutoIngestLogLockName(Path caseDirectoryPath) { - return Paths.get(caseDirectoryPath.toString(), CASE_AUTO_INGEST_LOG_NAME).toString(); - } - - public static String getCaseDirectoryLockName(Path caseDirectoryPath) { - return caseDirectoryPath.toString(); - } - - public static String getCaseNameLockName(Path caseDirectoryPath) { - String caseName = caseDirectoryPath.getFileName().toString(); - if (TimeStampUtils.endsWithTimeStamp(caseName)) { - caseName = TimeStampUtils.removeTimeStamp(caseName); - if (caseName.endsWith("_")) { - caseName = caseName.substring(0, caseName.length() - 1); - } - } - return caseName; - } - - public static boolean isCaseAutoIngestLogLockName(String lockName) { - return Paths.get(lockName).getFileName().toString().equals(CASE_AUTO_INGEST_LOG_NAME); - } - - public static boolean isCaseResourcesLockName(String lockName) { - return Paths.get(lockName).getFileName().toString().endsWith(RESOURCES_LOCK_SUFFIX); - } - - public static boolean isCaseLockName(String lockName) { - return !(lockName.contains("\\") || lockName.contains("//")); - } - - /** - * Prevents instantiation of this uitlity class. - */ - private CaseCoordinationServiceUtils() { - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java new file mode 100755 index 0000000000..038764b76e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java @@ -0,0 +1,133 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.multiusercases; + +import java.nio.file.Path; +import java.nio.file.Paths; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; + +/** + * Utility methods for using the coordination service for multi-user cases. + */ +public class CoordinationServiceUtils { + + private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS + private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS + + /** + * Gets the path of the case resources coordination service node for a case. + * This coordiantion service node is used for case resource locking. + * + * @param caseDirectoryPath The case directory path. + * + * @return The case resources coordination service node path. + */ + public static String getCaseResourcesNodePath(Path caseDirectoryPath) { + return caseDirectoryPath + RESOURCES_LOCK_SUFFIX; + } + + /** + * Gets the path of the case auto ingest log coordination service node for a + * case. This coordination service node is used for serializing case auto + * ingest log writes. + * + * @param caseDirectoryPath The case directory path. + * + * @return The case auto ingest log coordination service node path. + */ + public static String getCaseAutoIngestLogNodePath(Path caseDirectoryPath) { + return Paths.get(caseDirectoryPath.toString(), CASE_AUTO_INGEST_LOG_NAME).toString(); + } + + /** + * Gets the path of the case directory coordination service node for a case. + * This coordination service node is used for locking the case directory and + * for storing data about the case. + * + * @param caseDirectoryPath The case directory path. + * + * @return The case directory coordination service node path. + */ + public static String getCaseDirectoryNodePath(Path caseDirectoryPath) { + return caseDirectoryPath.toString(); + } + + /** + * Gets the path of the case name coordination service node for a case. This + * coordination service node is used to lock the case name so that only one + * node at a time can create a case with a particular name. + * + * @param caseDirectoryPath The case directory path. + * + * @return The case name coordination service node path. + */ + public static String getCaseNameNodePath(Path caseDirectoryPath) { + String caseName = caseDirectoryPath.getFileName().toString(); + if (TimeStampUtils.endsWithTimeStamp(caseName)) { + caseName = TimeStampUtils.removeTimeStamp(caseName); + if (caseName.endsWith("_")) { + caseName = caseName.substring(0, caseName.length() - 1); + } + } + return caseName; + } + + /** + * Determines whether or not a coordination service node path is a case auto + * ingest node path. + * + * @param nodePath The node path. + * + * @return True or false. + */ + public static boolean isCaseAutoIngestLogNodePath(String nodePath) { + return Paths.get(nodePath).getFileName().toString().equals(CASE_AUTO_INGEST_LOG_NAME); + } + + /** + * Determines whether or not a coordination service node path is a case + * resources node path. + * + * @param nodePath The node path. + * + * @return True or false. + */ + public static boolean isCaseResourcesNodePath(String nodePath) { + return Paths.get(nodePath).getFileName().toString().endsWith(RESOURCES_LOCK_SUFFIX); + } + + /** + * Determines whether or not a coordination service node path is a case name + * node path. + * + * @param nodePath The node path. + * + * @return True or false. + */ + public static boolean isCaseNameNodePath(String nodePath) { + return !(nodePath.contains("\\") || nodePath.contains("//")); + } + + /** + * Prevents instantiation of this uitlity class. + */ + private CoordinationServiceUtils() { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java index 6f17ec2d70..b9a1b4e6c6 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java @@ -58,9 +58,9 @@ final public class MultiUserCaseNodeDataCollector { // RJCTODO: Shorten name aft final CoordinationService coordinationService = CoordinationService.getInstance(); final List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES); for (String nodeName : nodeList) { - if (CaseCoordinationServiceUtils.isCaseLockName(nodeName) - || CaseCoordinationServiceUtils.isCaseResourcesLockName(nodeName) - || CaseCoordinationServiceUtils.isCaseAutoIngestLogLockName(nodeName)) { + if (CoordinationServiceUtils.isCaseNameNodePath(nodeName) + || CoordinationServiceUtils.isCaseResourcesNodePath(nodeName) + || CoordinationServiceUtils.isCaseAutoIngestLogNodePath(nodeName)) { continue; } @@ -158,10 +158,10 @@ final public class MultiUserCaseNodeDataCollector { // RJCTODO: Shorten name aft * @param caseDirectoryPath The case directory path. */ private static void deleteLockNodes(CoordinationService coordinationService, Path caseDirectoryPath) { - deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseResourcesLockName(caseDirectoryPath)); - deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseDirectoryPath)); - deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseDirectoryPath)); - deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseNameLockName(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseResourcesNodePath(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseDirectoryNodePath(caseDirectoryPath)); + deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseNameNodePath(caseDirectoryPath)); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index d5bef1d11e..6ff211db5d 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -37,7 +37,7 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; -import org.sleuthkit.autopsy.casemodule.multiusercases.CaseCoordinationServiceUtils; +import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; @@ -184,7 +184,7 @@ final class DeleteCaseTask implements Runnable { */ progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock()); logger.log(Level.INFO, String.format("Acquiring an exclusive case name lock for %s", caseNodeData.getDisplayName())); - String caseNameLockName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + String caseNameLockName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory()); try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) { if (nameLock == null) { logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName())); @@ -207,7 +207,7 @@ final class DeleteCaseTask implements Runnable { */ progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock()); logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName())); - String caseDirLockName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); + String caseDirLockName = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory()); try (CoordinationService.Lock caseDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirLockName)) { if (caseDirLock == null) { logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName())); @@ -330,7 +330,7 @@ final class DeleteCaseTask implements Runnable { if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); - String resourcesNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory()); + String resourcesNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory()); try { coordinationService.deleteNode(CategoryNode.CASES, resourcesNodePath); } catch (CoordinationServiceException ex) { @@ -351,7 +351,7 @@ final class DeleteCaseTask implements Runnable { progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName())); - String logFilePath = CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseNodeData.getDirectory()); + String logFilePath = CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseNodeData.getDirectory()); try { coordinationService.deleteNode(CategoryNode.CASES, logFilePath); } catch (CoordinationServiceException ex) { @@ -407,7 +407,7 @@ final class DeleteCaseTask implements Runnable { && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES)) { progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode()); logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); - String caseDirNodePath = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory()); + String caseDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory()); try { coordinationService.deleteNode(CategoryNode.CASES, caseDirNodePath); } catch (CoordinationServiceException ex) { @@ -436,7 +436,7 @@ final class DeleteCaseTask implements Runnable { progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseNameCoordSvcNode()); logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName())); try { - String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + String caseNameLockNodeName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory()); coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex); @@ -518,7 +518,7 @@ final class DeleteCaseTask implements Runnable { * be to strip off any time stamp from the case name in the case node * data. */ - String caseName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory()); + String caseName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory()); final List nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); for (String manifestNodeName : nodeNames) { if (Thread.currentThread().isInterrupted()) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index ab7e11a07f..6f95dfc82f 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -35,7 +35,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search From 1b0b6568002cc420843674c0fadacb02172bdde8 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 15:05:12 -0400 Subject: [PATCH 25/32] Imporved case deletion cleanup/polish --- .../multiusercases/CaseNodeData.java | 23 ++++++++----------- .../autopsy/recentactivity/Bundle.properties | 2 +- .../recentactivity/Bundle.properties-MERGED | 2 +- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java index 4cffe6a0a2..d825d87ee5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeData.java @@ -283,6 +283,9 @@ public final class CaseNodeData { return byteStream.toByteArray(); } + /** + * Flags for the various components of a case that can be deleted. + */ public enum DeletedFlags { TEXT_INDEX(1), @@ -290,9 +293,14 @@ public final class CaseNodeData { CASE_DIR(4), DATA_SOURCES(8), MANIFEST_FILE_NODES(16); - + private final short value; + /** + * Constructs a flag for a case component that can be deleted. + * + * @param value + */ private DeletedFlags(int value) { this.value = (short) value; } @@ -307,18 +315,5 @@ public final class CaseNodeData { } } - - public final static class InvalidDataException extends Exception { - private static final long serialVersionUID = 1L; - - private InvalidDataException(String message) { - super(message); - } - - private InvalidDataException(String message, Throwable cause) { - super(message, cause); - } - } - } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties index f0f8e0341f..6cd8dede00 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties @@ -1,5 +1,5 @@ OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 4a2b6e06f8..7da2ebca7b 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -64,7 +64,7 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome From 0fca244d4c184b7cf66974d6de879da0628d684f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 15:07:57 -0400 Subject: [PATCH 26/32] Improved case deletion cleanup/polish --- ...aseNodeDataCollector.java => CaseNodeDataCollector.java} | 6 +++--- .../multiusercasesbrowser/MultiUserCasesRootNode.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/{MultiUserCaseNodeDataCollector.java => CaseNodeDataCollector.java} (96%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeDataCollector.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java rename to Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeDataCollector.java index b9a1b4e6c6..e447ce7430 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/MultiUserCaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeDataCollector.java @@ -36,9 +36,9 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Queries the coordination service to collect the multi-user case node data * stored in the case directory lock ZooKeeper nodes. */ -final public class MultiUserCaseNodeDataCollector { // RJCTODO: Shorten name after multi-case keyword search code is in. +final public class CaseNodeDataCollector { - private static final Logger logger = Logger.getLogger(MultiUserCaseNodeDataCollector.class.getName()); + private static final Logger logger = Logger.getLogger(CaseNodeDataCollector.class.getName()); /** * Queries the coordination service to collect the multi-user case node data @@ -181,7 +181,7 @@ final public class MultiUserCaseNodeDataCollector { // RJCTODO: Shorten name aft /** * Private constructor to prevent instantiation of this utility class. */ - private MultiUserCaseNodeDataCollector() { + private CaseNodeDataCollector() { } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java index 3287ea53bb..45e8c02be4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesRootNode.java @@ -25,7 +25,7 @@ import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; -import org.sleuthkit.autopsy.casemodule.multiusercases.MultiUserCaseNodeDataCollector; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeDataCollector; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; @@ -63,7 +63,7 @@ final class MultiUserCasesRootNode extends AbstractNode { @Override protected boolean createKeys(List keys) { try { - List caseNodeData = MultiUserCaseNodeDataCollector.getNodeData(); + List caseNodeData = CaseNodeDataCollector.getNodeData(); keys.addAll(caseNodeData); } catch (CoordinationService.CoordinationServiceException | InterruptedException ex) { logger.log(Level.SEVERE, "Failed to get case node data from coodination service", ex); From 3ae147a041b65d6ccdd21ad7169dd797e0f80078 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 15:09:52 -0400 Subject: [PATCH 27/32] Improved case deletion cleanup/polish --- .../casemodule/multiusercases/CaseNodeDataCollector.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeDataCollector.java index e447ce7430..5c148aa58d 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CaseNodeDataCollector.java @@ -98,7 +98,7 @@ final public class CaseNodeDataCollector { * * @param nodeName The coordination service node name, i.e., the case * directory path. - * @param oldNodeData . + * @param oldNodeData The node data to be updated. * * @return A CaseNodedata object or null if the coordination service node is * an "orphan" with no corresponding case directry. @@ -145,8 +145,8 @@ final public class CaseNodeDataCollector { if (nodeData != null) { CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray()); - } - + } + return nodeData; } From 85bac67bc39d5df9d194249759a02155752de5a5 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 25 Mar 2019 17:44:59 -0400 Subject: [PATCH 28/32] Improved case deletion polish/cleanup --- .../CoordinationService.java | 11 ++++++++++- .../autoingest/Bundle.properties-MERGED | 3 +++ .../autoingest/DeleteCaseTask.java | 19 +++++++++++-------- .../recentactivity/Bundle.properties-MERGED | 2 +- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index c63e517b9d..9bd5710980 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -114,7 +114,16 @@ public final class CoordinationService { } try { instance = new CoordinationService(rootNode); - } catch (IOException | InterruptedException | KeeperException | CoordinationServiceException ex) { + } catch (IOException | KeeperException | CoordinationServiceException ex) { + throw new CoordinationServiceException("Failed to create coordination service", ex); + } catch (InterruptedException ex) { + /* + * The interrupted exception should be propagated to support + * task cancellation. To avoid a public API change here, restore + * the interrupted flag and then throw the InterruptedException + * in its wrapper. + */ + Thread.currentThread().interrupt(); throw new CoordinationServiceException("Failed to create coordination service", ex); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 78671b4c0f..5d9eab6ee3 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -182,6 +182,9 @@ DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service... DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode... DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode... +# {0} - data source name +# {1} - device id +DeleteCaseTask.progress.deletingDataSource=Deleting data source {0} with device id {1}... DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode... # {0} - manifest file path DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}... diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 6ff211db5d..75259410e2 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -122,9 +122,9 @@ final class DeleteCaseTask implements Runnable { public void run() { try { progress.start(Bundle.DeleteCaseTask_progress_startMessage()); - logger.log(Level.INFO, String.format("Starting deleting %s (%s)", caseNodeData.getDisplayName(), deleteOption)); + logger.log(Level.INFO, String.format("Starting deletion of %s (%s)", caseNodeData.getDisplayName(), deleteOption)); deleteCase(); - logger.log(Level.INFO, String.format("Finished deleting %s (%s)", caseNodeData.getDisplayName(), deleteOption)); + logger.log(Level.INFO, String.format("Finished deletion of %s (%s)", caseNodeData.getDisplayName(), deleteOption)); } catch (Throwable ex) { /* @@ -230,7 +230,7 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); return; } - logger.log(Level.INFO, String.format("Found %d manifest file paths for %s", manifestFilePaths.size(), caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Found %d manifest file path(s) for %s", manifestFilePaths.size(), caseNodeData.getDisplayName())); if (Thread.currentThread().isInterrupted()) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); @@ -305,7 +305,6 @@ final class DeleteCaseTask implements Runnable { if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { try { - logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName())); Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); } catch (InterruptedException ex) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); @@ -373,7 +372,6 @@ final class DeleteCaseTask implements Runnable { if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { try { - logger.log(Level.INFO, String.format("Deleting manifest file znodes for %s", caseNodeData.getDisplayName())); deleteManifestFileNodes(); } catch (InterruptedException ex) { logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); @@ -711,7 +709,14 @@ final class DeleteCaseTask implements Runnable { * @return True if all of the data source files werre deleted, false * otherwise. */ + @NbBundle.Messages({ + "# {0} - data source name", "# {1} - device id", "DeleteCaseTask.progress.deletingDataSource=Deleting data source {0} with device id {1}...",}) private boolean deleteDataSources(Manifest manifest, List dataSources) { + final String dataSourceFileName = manifest.getDataSourceFileName(); + final String dataSourceDeviceId = manifest.getDeviceId(); + progress.progress(Bundle.DeleteCaseTask_progress_deletingDataSource(dataSourceFileName, dataSourceDeviceId)); + logger.log(Level.INFO, String.format("Deleting data source %s with device id %s from %s", dataSourceFileName, dataSourceDeviceId, caseNodeData.getDisplayName())); + /* * There are two possibilities here. The data source may be an image, * and if so, it may be split into multiple files. In this case, all of @@ -720,10 +725,8 @@ final class DeleteCaseTask implements Runnable { * set, report file, archive file, etc.). In this case, just the file * referenced by the manifest will be deleted. */ - boolean allFilesDeleted = true; // RJCTODO: add progress messages + boolean allFilesDeleted = true; Set filesToDelete = new HashSet<>(); - final String dataSourceFileName = manifest.getDataSourceFileName(); - final String dataSourceDeviceId = manifest.getDeviceId(); for (DataSource dataSource : dataSources) { if (dataSource instanceof Image) { Image image = (Image) dataSource; diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 7da2ebca7b..17d744eee1 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -64,7 +64,7 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome From 4258d32aca8b92b537c021849b84f4c2cf17fd32 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 26 Mar 2019 16:19:49 -0400 Subject: [PATCH 29/32] Improve logging in DeleteCaseTask --- .../autopsy/experimental/autoingest/DeleteCaseTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 75259410e2..5a98fc187f 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -287,7 +287,6 @@ final class DeleteCaseTask implements Runnable { } if (!manifestFilePaths.isEmpty() && (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL)) { - logger.log(Level.INFO, String.format("Starting deletion of case output for %s", caseNodeData.getDisplayName())); try { deleteAutoIngestInput(); } catch (InterruptedException ex) { From 3abbceb68ae8b24b7e861355db5344c7d44718ce Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Mar 2019 14:22:47 -0400 Subject: [PATCH 30/32] Code review fixes for improved case deletion --- .../src/org/sleuthkit/autopsy/casemodule/Case.java | 14 ++++++++------ .../autoingest/AutoIngestJobsNode.java | 10 +--------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index a682a30d3d..d953efa610 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2584,7 +2584,7 @@ public class Case { * for a lower-level exception. * @throws InterruptedException If the thread this code is running in is * interrupted while blocked, i.e., if - * cancellation of the opersation is detected + * cancellation of the operation is detected * during a wait. */ @Messages({ @@ -2678,7 +2678,7 @@ public class Case { * * @throws InterruptedException If the thread this code is running in is * interrupted while blocked, i.e., if - * cancellation of the opersation is detected + * cancellation of the operation is detected * during a wait. */ @Beta @@ -2717,6 +2717,8 @@ public class Case { return errorsOccurred; } + deleteFromRecentCases(metadata, progressIndicator); + return errorsOccurred; } @@ -2766,7 +2768,7 @@ public class Case { * Attempts to delete the text index for a multi-user case. * * @param caseNodeData The coordination service node data for the case. - * @param metadata The case mnetadata. + * @param metadata The case metadata. * @param progressIndicator A progress indicator. * @param logger A logger. * @@ -2788,7 +2790,7 @@ public class Case { /** * Attempts to delete the text index for a case. * - * @param metadata The case mnetadata. + * @param metadata The case metadata. * @param progressIndicator A progress indicator. * * @throws KeywordSearchServiceException If there is an error deleting the @@ -2808,7 +2810,7 @@ public class Case { * Attempts to delete the case directory for a multi-user case. * * @param caseNodeData The coordination service node data for the case. - * @param metadata The case mnetadata. + * @param metadata The case metadata. * @param progressIndicator A progress indicator. * @param logger A logger. * @@ -2829,7 +2831,7 @@ public class Case { /** * Attempts to delete the case directory for a case. * - * @param metadata The case mnetadata. + * @param metadata The case metadata. * @param progressIndicator A progress indicator. * * @throws CaseActionException If there is an error deleting the case diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 5541ac6789..c37b834348 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import javax.swing.Action; @@ -36,13 +35,6 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.Stage; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAdminActions; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboard; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents; -import org.sleuthkit.autopsy.experimental.autoingest.Bundle; -import org.sleuthkit.autopsy.experimental.autoingest.Manifest; -import org.sleuthkit.autopsy.experimental.autoingest.PrioritizationAction; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; import org.sleuthkit.autopsy.ingest.DataSourceIngestJob; From 8bc4f05b828671a8f9693ddfb5875305426e0022 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Mar 2019 17:20:46 -0400 Subject: [PATCH 31/32] Address Codacy flagged issues for DeleteCaseTask --- .../casemodule/Bundle.properties-MERGED | 6 + .../sleuthkit/autopsy/casemodule/Case.java | 46 +- .../CoordinationServiceUtils.java | 2 +- .../autopsy/progress/ProgressIndicator.java | 4 + .../autoingest/AutoIngestDashboardLogger.java | 7 +- .../autoingest/Bundle.properties-MERGED | 5 - .../autoingest/DeleteCaseInputAction.java | 7 - .../DeleteCaseInputAndOutputAction.java | 6 - .../autoingest/DeleteCaseOutputAction.java | 6 - .../autoingest/DeleteCaseTask.java | 687 +++++++++--------- 10 files changed, 359 insertions(+), 417 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index 1ab571a74a..64dfb6481a 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -40,6 +40,12 @@ Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. # {0} - exception message Case.exceptionMessage.execExceptionWrapperMessage={0} # {0} - exception message +Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}. +# {0} - exception message +Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}. +# {0} - exception message +Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}. +# {0} - exception message Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}. Case.exceptionMessage.metadataUpdateError=Failed to update case metadata # {0} - exception message diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index d953efa610..1093824ec4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2589,8 +2589,11 @@ public class Case { */ @Messages({ "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...", + "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.", "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.", + "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.", "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...", + "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.", "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...", "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..." }) @@ -2601,7 +2604,7 @@ public class Case { coordinationService = CoordinationService.getInstance(); } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage())); } CaseNodeData caseNodeData; @@ -2618,7 +2621,7 @@ public class Case { caseNodeData = new CaseNodeData(nodeBytes); } catch (CoordinationServiceException | InterruptedException | IOException ex) { logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage())); } errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger); @@ -2638,7 +2641,7 @@ public class Case { } catch (CoordinationServiceException ex) { logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage())); } if (!errorsOccurred) { @@ -2686,39 +2689,19 @@ public class Case { boolean errorsOccurred = false; try { deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger); + deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger); + deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger); + deleteFromRecentCases(metadata, progressIndicator); } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { errorsOccurred = true; logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS - Thread.currentThread().interrupt(); - return errorsOccurred; - } - - try { - deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger); } catch (KeywordSearchServiceException ex) { errorsOccurred = true; logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS - Thread.currentThread().interrupt(); - return errorsOccurred; - } - - try { - deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger); } catch (CaseActionException ex) { errorsOccurred = true; logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s (%s) in %s cancelled while incomplete", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); //NON-NLS - Thread.currentThread().interrupt(); - return errorsOccurred; } - - deleteFromRecentCases(metadata, progressIndicator); - return errorsOccurred; } @@ -2752,12 +2735,13 @@ public class Case { CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo(); String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS Class.forName("org.postgresql.Driver"); //NON-NLS - try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { //NON-NLS + try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) { String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS - ResultSet queryResult = statement.executeQuery(dbExistsQuery); - if (queryResult.next()) { - String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS - statement.execute(deleteCommand); + try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) { + if (queryResult.next()) { + String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS + statement.execute(deleteCommand); + } } } setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DB); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java index 038764b76e..d4b0399ff8 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercases/CoordinationServiceUtils.java @@ -25,7 +25,7 @@ import org.sleuthkit.autopsy.coreutils.TimeStampUtils; /** * Utility methods for using the coordination service for multi-user cases. */ -public class CoordinationServiceUtils { +public final class CoordinationServiceUtils { private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java index c0637dbc3f..e62d940c08 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java @@ -98,6 +98,10 @@ public interface ProgressIndicator { * @param cancellingMessage The cancelling messages. */ default void setCancelling(String cancellingMessage) { + /* + * The default implementation assumes that cancelling the underlying + * task is not supported. + */ } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardLogger.java index fadeda09ec..61d6032048 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardLogger.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Timestamp; @@ -34,7 +35,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; */ final class AutoIngestDashboardLogger { - private static final int LOG_SIZE = 50000000; // In bytes, zero is unlimited, set to roughly 10mb currently + private static final int LOG_SIZE = 50000000; // In bytes, zero is unlimited. private static final int LOG_FILE_COUNT = 10; private static final Logger logger = Logger.getLogger("AutoIngestDashboardLogger"); //NON-NLS private static final String NEWLINE = System.lineSeparator(); @@ -74,8 +75,8 @@ final class AutoIngestDashboardLogger { }); logger.addHandler(fileHandler); logger.setUseParentHandlers(false); - } catch (SecurityException | IOException ex) { - throw new RuntimeException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS + } catch (IOException ex) { + throw new UncheckedIOException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS } configured = true; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 5d9eab6ee3..b6991a630a 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -89,10 +89,6 @@ AutoIngestControlPanel.runningTable.toolTipText=The Running table displays the c AutoIngestControlPanel.SharedConfigurationDisabled=Shared configuration disabled AutoIngestControlPanel.ShowLogFailed.Message=Case log file does not exist AutoIngestControlPanel.ShowLogFailed.Title=Unable to display case log -# {0} - case db status -# {1} - search svc Status -# {2} - coord svc Status -# {3} - msg broker status AutoIngestControlPanel.tbServicesStatusMessage.Message=Case databases {0}, keyword search {1}, coordination {2}, messaging {3} AutoIngestControlPanel.tbServicesStatusMessage.Message.Down=down AutoIngestControlPanel.tbServicesStatusMessage.Message.Unknown=unknown @@ -205,7 +201,6 @@ HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-use OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File -# {0} - caseErrorMessage OpenCaseAction.errorMsg=Failed to open case: {0} OpenCaseAction.menuItemText=Open OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change.\n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java index 38202b9d0e..4899ea285d 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.event.ActionEvent; -import java.util.concurrent.ExecutorService; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; @@ -69,10 +68,4 @@ final class DeleteCaseInputAction extends DeleteCaseAction { return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT, progress); } - @Override - public DeleteCaseInputAction clone() throws CloneNotSupportedException { - super.clone(); - throw new CloneNotSupportedException(); - } - } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java index 09cd6eb4e1..38c13d01eb 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java @@ -64,10 +64,4 @@ final class DeleteCaseInputAndOutputAction extends DeleteCaseAction { return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_ALL, progress); } - @Override - public DeleteCaseInputAndOutputAction clone() throws CloneNotSupportedException { - super.clone(); - throw new CloneNotSupportedException(); - } - } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index 4e9d34fb06..d32d0fe029 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -66,11 +66,5 @@ final class DeleteCaseOutputAction extends DeleteCaseAction { DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress); } - - @Override - public DeleteCaseOutputAction clone() throws CloneNotSupportedException { - super.clone(); - throw new CloneNotSupportedException(); - } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 5a98fc187f..1c38a0a67d 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -71,7 +71,7 @@ final class DeleteCaseTask implements Runnable { private CoordinationService coordinationService; private CaseMetadata caseMetadata; - /* + /** * Options to support implementing different case deletion use cases. */ enum DeleteOptions { @@ -126,7 +126,14 @@ final class DeleteCaseTask implements Runnable { deleteCase(); logger.log(Level.INFO, String.format("Finished deletion of %s (%s)", caseNodeData.getDisplayName(), deleteOption)); - } catch (Throwable ex) { + } catch (CoordinationServiceException | IOException ex) { + logger.log(Level.SEVERE, String.format("Error deleting %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); + + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); + Thread.currentThread().interrupt(); + + } catch (Exception ex) { /* * This is an unexpected runtime exceptions firewall. It is here * because this task is designed to be able to be run in scenarios @@ -137,12 +144,17 @@ final class DeleteCaseTask implements Runnable { throw ex; } finally { + releaseManifestFileLocks(); progress.finish(); } } /** * Deletes part or all of the given case. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. */ @NbBundle.Messages({ "DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...", @@ -156,20 +168,11 @@ final class DeleteCaseTask implements Runnable { "DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode...", "DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode..." }) - private void deleteCase() { + private void deleteCase() throws CoordinationServiceException, IOException, InterruptedException { progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc()); logger.log(Level.INFO, String.format("Connecting to the coordination service for deletion of %s", caseNodeData.getDisplayName())); - try { - coordinationService = CoordinationService.getInstance(); - } catch (CoordinationService.CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred connecting to the coordination service", caseNodeData.getDisplayName()), ex); - return; - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - return; - } + coordinationService = CoordinationService.getInstance(); + checkForCancellation(); /* * Acquire an exclusive case name lock. The case name lock is the lock @@ -190,11 +193,7 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName())); return; } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - return; - } + checkForCancellation(); /* * Acquire an exclusive case directory lock. A shared case directory @@ -213,234 +212,38 @@ final class DeleteCaseTask implements Runnable { logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName())); return; } + checkForCancellation(); - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); + getManifestFilePaths(); + checkForCancellation(); + /* + * Acquire exclusive locks for the auto ingest job manifest + * files for the case, if any. Manifest file locks are acquired + * by the auto ingest node (AIN) input directory scanning tasks + * when they look for auto ingest jobs to enqueue, and by the + * AIN job execution tasks when they do a job. Acquiring these + * locks here ensures that the scanning tasks and job execution + * tasks cannot do anything with the auto ingest jobs for a case + * during case deletion. + */ + if (!acquireManifestFileLocks()) { + logger.log(Level.INFO, String.format("Could not delete %s because at least one manifest file lock was already held by another host", caseNodeData.getDisplayName())); return; } - - progress.progress(Bundle.DeleteCaseTask_progress_gettingManifestPaths()); - logger.log(Level.INFO, String.format("Getting manifest file paths for %s", caseNodeData.getDisplayName())); - try { - getManifestFilePaths(); - } catch (IOException | CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("An error occurred getting the manifest file paths", caseNodeData.getDisplayName()), ex); - return; - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - return; - } - logger.log(Level.INFO, String.format("Found %d manifest file path(s) for %s", manifestFilePaths.size(), caseNodeData.getDisplayName())); - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - return; - } - - if (!manifestFilePaths.isEmpty()) { - /* - * Acquire exclusive locks for the auto ingest job manifest - * files for the case, if any. Manifest file locks are - * acquired by the auto ingest node (AIN) input directory - * scanning tasks when they look for auto ingest jobs to - * enqueue, and by the AIN job execution tasks when they do - * a job. Acquiring these locks here ensures that the - * scanning tasks and job execution tasks cannot do anything - * with the auto ingest jobs for a case during case - * deletion. - */ - progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); - logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName())); - try { - if (!acquireManifestFileLocks()) { - logger.log(Level.INFO, String.format("Could not delete %s because at least one manifest file lock was already held by another host", caseNodeData.getDisplayName())); - return; - } - } catch (IOException | CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex); - return; - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - return; - } - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - releaseManifestFileLocks(); - return; - } - - final File caseDirectory = caseNodeData.getDirectory().toFile(); - if (caseDirectory.exists()) { - progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile()); - logger.log(Level.INFO, String.format("Opening case metadata file for %s", caseNodeData.getDisplayName())); - Path caseMetadataPath = CaseMetadata.getCaseMetadataFilePath(caseNodeData.getDirectory()); - if (caseMetadataPath != null) { - try { - caseMetadata = new CaseMetadata(caseMetadataPath); - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - releaseManifestFileLocks(); - return; - } - - if (!manifestFilePaths.isEmpty() && (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL)) { - try { - deleteAutoIngestInput(); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - releaseManifestFileLocks(); - return; - } - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - releaseManifestFileLocks(); - return; - } - - if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { - try { - Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - releaseManifestFileLocks(); - return; - } - } - - } catch (CaseMetadata.CaseMetadataException ex) { - logger.log(Level.SEVERE, String.format("Error reading metadata file for %s", caseNodeData.getDisplayName()), ex); - } - - } else { - logger.log(Level.WARNING, String.format("No case metadata file found for %s", caseNodeData.getDisplayName())); - } - - } else { - setDeletedItemFlag(CaseNodeData.DeletedFlags.CASE_DIR); - logger.log(Level.INFO, String.format("No case directory found for %s", caseNodeData.getDisplayName())); - } - - if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { - progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); - logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); - String resourcesNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory()); - try { - coordinationService.deleteNode(CategoryNode.CASES, resourcesNodePath); - } catch (CoordinationServiceException ex) { - if (!isNoNodeException(ex)) { - logger.log(Level.SEVERE, String.format("Error deleting case resources znode for %s", caseNodeData.getDisplayName()), ex); - } - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - releaseManifestFileLocks(); - return; - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - releaseManifestFileLocks(); - return; - } - - progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); - logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName())); - String logFilePath = CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseNodeData.getDirectory()); - try { - coordinationService.deleteNode(CategoryNode.CASES, logFilePath); - } catch (CoordinationServiceException ex) { - if (!isNoNodeException(ex)) { - logger.log(Level.SEVERE, String.format("Error deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()), ex); - } - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - releaseManifestFileLocks(); - return; - } - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - releaseManifestFileLocks(); - return; - } - - if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { - try { - deleteManifestFileNodes(); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - return; - } - } - - releaseManifestFileLocks(); - - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the case directory lock", caseNodeData.getDisplayName()), ex); - return; - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - return; - } - - /* - * Now that the case directory lock has been released, the - * coordination service node for it can be deleted if the use case - * requires it. However, if something to ge deleted was not deleted, - * leave the node so that what was and was not deleted can be - * inspected. - */ - if ((deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) - && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.DATA_SOURCES) - && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB) - && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR) - && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES)) { - progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode()); - logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); - String caseDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory()); - try { - coordinationService.deleteNode(CategoryNode.CASES, caseDirNodePath); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); - return; - } - } - - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the case name lock", caseNodeData.getDisplayName()), ex); - return; - } - - if (Thread.currentThread().isInterrupted()) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName())); - return; - } - - /* - * Now that the case name lock has been released, the coordination - * service node for it can be deleted if the use case requires it. - */ - if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { - progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseNameCoordSvcNode()); - logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName())); - try { - String caseNameLockNodeName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory()); - coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); - } catch (CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex); + checkForCancellation(); + deleteCaseContents(); + checkForCancellation(); + deleteCaseResourcesNode(); + checkForCancellation(); + deleteCaseAutoIngestLogNode(); + checkForCancellation(); + deleteManifestFileNodes(); + checkForCancellation(); } + deleteCaseDirectoryNode(); + checkForCancellation(); } + deleteCaseNameNode(); } /** @@ -456,6 +259,8 @@ final class DeleteCaseTask implements Runnable { * manifests list file. */ private void getManifestFilePaths() throws IOException, CoordinationServiceException, InterruptedException { + progress.progress(Bundle.DeleteCaseTask_progress_gettingManifestPaths()); + logger.log(Level.INFO, String.format("Getting manifest file paths for %s", caseNodeData.getDisplayName())); final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName()); final File manifestListsFile = manifestsListFilePath.toFile(); if (manifestListsFile.exists()) { @@ -466,6 +271,7 @@ final class DeleteCaseTask implements Runnable { if (manifestFilePaths.isEmpty()) { setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES); } + logger.log(Level.INFO, String.format("Found %d manifest file path(s) for %s", manifestFilePaths.size(), caseNodeData.getDisplayName())); } /** @@ -483,9 +289,7 @@ final class DeleteCaseTask implements Runnable { private void getManifestPathsFromFile(Path manifestsListFilePath) throws IOException, InterruptedException { try (final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath)) { while (manifestsListFileScanner.hasNextLine()) { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } + checkForCancellation(); final Path manifestFilePath = Paths.get(manifestsListFileScanner.nextLine()); if (manifestFilePath.toFile().exists()) { manifestFilePaths.add(manifestFilePath); @@ -518,9 +322,7 @@ final class DeleteCaseTask implements Runnable { String caseName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory()); final List nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); for (String manifestNodeName : nodeNames) { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } + checkForCancellation(); try { final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodeName); AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes); @@ -553,43 +355,82 @@ final class DeleteCaseTask implements Runnable { "# {0} - manifest file path", "DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}..." }) private boolean acquireManifestFileLocks() throws IOException, CoordinationServiceException, InterruptedException { - /* - * When acquiring the locks, it is reasonable to block briefly, since - * the auto ingest node (AIN) input directory scanning tasks do a lot of - * short-term acquiring and releasing of the same locks. The assumption - * here is that the originator of this case deletion task is not asking - * for deletion of a case that has a job that an auto ingest node (AIN) - * job execution task is working on and that - * MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long anyway, so - * waiting a bit should be fine. - * - */ boolean allLocksAcquired = true; - try { - for (Path manifestPath : manifestFilePaths) { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - - progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath.toString())); - logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName())); - CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES); - if (null != manifestLock) { - manifestFileLocks.add(manifestLock); - } else { - logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName())); - allLocksAcquired = false; - releaseManifestFileLocks(); - break; + if (!manifestFilePaths.isEmpty()) { + progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); + logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName())); + /* + * When acquiring the locks, it is reasonable to block briefly, + * since the auto ingest node (AIN) input directory scanning tasks + * do a lot of short-term acquiring and releasing of the same locks. + * The assumption here is that the originator of this case deletion + * task is not asking for deletion of a case that has a job that an + * auto ingest node (AIN) job execution task is working on and that + * MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long anyway, so + * waiting a bit should be fine. + */ + try { + for (Path manifestPath : manifestFilePaths) { + checkForCancellation(); + progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath.toString())); + logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName())); + CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES); + if (null != manifestLock) { + manifestFileLocks.add(manifestLock); + } else { + logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName())); + allLocksAcquired = false; + releaseManifestFileLocks(); + break; + } } + } catch (CoordinationServiceException | InterruptedException ex) { + releaseManifestFileLocks(); + throw ex; } - } catch (CoordinationServiceException | InterruptedException ex) { - releaseManifestFileLocks(); - throw ex; } return allLocksAcquired; } + /** + * Deletes case contents, based on the specified deletion option. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + private void deleteCaseContents() throws InterruptedException { + final File caseDirectory = caseNodeData.getDirectory().toFile(); + if (caseDirectory.exists()) { + progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile()); + logger.log(Level.INFO, String.format("Opening case metadata file for %s", caseNodeData.getDisplayName())); + Path caseMetadataPath = CaseMetadata.getCaseMetadataFilePath(caseNodeData.getDirectory()); + if (caseMetadataPath != null) { + try { + caseMetadata = new CaseMetadata(caseMetadataPath); + checkForCancellation(); + if (!manifestFilePaths.isEmpty() && (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL)) { + deleteAutoIngestInput(); + } + checkForCancellation(); + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); + } + + } catch (CaseMetadata.CaseMetadataException ex) { + logger.log(Level.SEVERE, String.format("Error reading metadata file for %s", caseNodeData.getDisplayName()), ex); + } + + } else { + logger.log(Level.WARNING, String.format("No case metadata file found for %s", caseNodeData.getDisplayName())); + } + + } else { + setDeletedItemFlag(CaseNodeData.DeletedFlags.CASE_DIR); + logger.log(Level.INFO, String.format("No case directory found for %s", caseNodeData.getDisplayName())); + } + } + /** * Deletes the auto ingest job input manifests for the case along with the * corresponding data sources. @@ -609,73 +450,27 @@ final class DeleteCaseTask implements Runnable { progress.progress(Bundle.DeleteCaseTask_progress_openingCaseDatabase()); logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName())); caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory()); - List dataSources = caseDb.getDataSources(); - - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } + checkForCancellation(); + /* + * For every manifest file associated with the case, attempt to + * delete the data source referenced by the manifest, the manifest, + * and if the input directory that contains the manifest is empty + * after the data source and manifest are deleted, delete that, too. + */ boolean allInputDeleted = true; for (Path manifestFilePath : manifestFilePaths) { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - + checkForCancellation(); final File manifestFile = manifestFilePath.toFile(); if (manifestFile.exists()) { - progress.progress(Bundle.DeleteCaseTask_progress_parsingManifest(manifestFilePath)); - logger.log(Level.INFO, String.format("Parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); - Manifest manifest = null; - for (ManifestFileParser parser : Lookup.getDefault().lookupAll(ManifestFileParser.class)) { - if (parser.fileIsManifest(manifestFilePath)) { - try { - manifest = parser.parse(manifestFilePath); - break; - } catch (ManifestFileParser.ManifestFileParserException ex) { - logger.log(Level.WARNING, String.format("Error parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); - } - } - } + Manifest manifest = parseManifestFile(manifestFilePath); if (manifest != null) { - if (deleteDataSources(manifest, dataSources)) { - /* - * Delete the manifest file, allowing a few retries. - * This is a way to resolve the race condition - * between this task and auto ingest node (AIN) - * input directory scanning tasks, which parse - * manifests (actually all files) before getting a - * coordination service lock, without resorting to a - * protocol using locking of the input directory. - */ - progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath)); - logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); - int tries = 0; - boolean deleted = false; - while (!deleted && tries < MANIFEST_DELETE_TRIES) { - deleted = manifestFile.delete(); - if (!deleted) { - ++tries; - Thread.sleep(1000); - } - } - if (deleted) { - /* - * Delete the input directory if it is empty. - */ - final Path inputDirectoryPath = manifestFilePath.getParent(); - final File inputDirectory = inputDirectoryPath.toFile(); - File[] files = inputDirectory.listFiles(); - logger.log(Level.INFO, String.format("Deleting empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); - if (files == null || files.length == 0) { - if (!inputDirectory.delete()) { - logger.log(Level.WARNING, String.format("Failed to delete empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); - } - } - - } else { - logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); - allInputDeleted = false; - } + List dataSources = caseDb.getDataSources(); + if (deleteDataSources(manifest, dataSources) && deleteManifestFile(manifestFile)) { + deleteInputDirectoryIfEmpty(manifestFilePath); + } else { + logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + allInputDeleted = false; } } else { logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); @@ -698,6 +493,64 @@ final class DeleteCaseTask implements Runnable { } } + /** + * Parses a manifest file. + * + * @param manifestFilePath The manifest file path. + * + * @return A manifest, if the parsing is successful, null otherwise. + */ + private Manifest parseManifestFile(Path manifestFilePath) { + progress.progress(Bundle.DeleteCaseTask_progress_parsingManifest(manifestFilePath)); + logger.log(Level.INFO, String.format("Parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + Manifest manifest = null; + for (ManifestFileParser parser : Lookup.getDefault().lookupAll(ManifestFileParser.class)) { + if (parser.fileIsManifest(manifestFilePath)) { + try { + manifest = parser.parse(manifestFilePath); + break; + } catch (ManifestFileParser.ManifestFileParserException ex) { + logger.log(Level.WARNING, String.format("Error parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); + } + } + } + return manifest; + } + + /** + * Deletes a manifest file. + * + * @param manifestFile The manifest file. + * + * @return True if the file was deleted, false otherwise. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + private boolean deleteManifestFile(File manifestFile) throws InterruptedException { + /* + * Delete the manifest file, allowing a few retries. This is a way to + * resolve the race condition between this task and auto ingest node + * (AIN) input directory scanning tasks, which parse manifests (actually + * all files) before getting a coordination service lock, without + * resorting to a protocol using locking of the input directory. + */ + Path manifestFilePath = manifestFile.toPath(); + progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath)); + logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + int tries = 0; + boolean deleted = false; + while (!deleted && tries < MANIFEST_DELETE_TRIES) { + deleted = manifestFile.delete(); + if (!deleted) { + ++tries; + Thread.sleep(1000); + } + } + return deleted; + } + /** * Locates and deletes the data source files referenced by a manifest. * @@ -755,6 +608,111 @@ final class DeleteCaseTask implements Runnable { return allFilesDeleted; } + /** + * Deletes the input directory for a manifest, if the directory is empty. + * + * @param manifestFilePath The manifest fiell path. + */ + void deleteInputDirectoryIfEmpty(Path manifestFilePath) { + final Path inputDirectoryPath = manifestFilePath.getParent(); + final File inputDirectory = inputDirectoryPath.toFile(); + File[] files = inputDirectory.listFiles(); + logger.log(Level.INFO, String.format("Deleting empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); + if ((files == null || files.length == 0) && !inputDirectory.delete()) { + logger.log(Level.WARNING, String.format("Failed to delete empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); + } + } + + /** + * Deletes the case resources coordination service node. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + private void deleteCaseResourcesNode() throws InterruptedException { + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); + logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); + String resourcesNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory()); + try { + coordinationService.deleteNode(CategoryNode.CASES, resourcesNodePath); + } catch (CoordinationServiceException ex) { + if (!isNoNodeException(ex)) { + logger.log(Level.SEVERE, String.format("Error deleting case resources znode for %s", caseNodeData.getDisplayName()), ex); + } + } + } + } + + /** + * Deletes the case auto ingest log coordination service node. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + private void deleteCaseAutoIngestLogNode() throws InterruptedException { + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); + logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName())); + String logFilePath = CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseNodeData.getDirectory()); + try { + coordinationService.deleteNode(CategoryNode.CASES, logFilePath); + } catch (CoordinationServiceException ex) { + if (!isNoNodeException(ex)) { + logger.log(Level.SEVERE, String.format("Error deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()), ex); + } + } + } + } + + /** + * Deletes the case directory coordination service node if everything that + * was supposed to be deleted was deleted. Otherwise, leave the node so that + * what was and was not deleted can be inspected. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + private void deleteCaseDirectoryNode() throws InterruptedException { + if ((deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.DATA_SOURCES) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR) + && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES)) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode()); + logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); + String caseDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory()); + try { + coordinationService.deleteNode(CategoryNode.CASES, caseDirNodePath); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex); + } + } + } + + /** + * Deletes the case name coordiation service node. + * + * @throws InterruptedException If the thread in which this task is running + * is interrupted while blocked waiting for a + * coordination service operation to complete. + */ + private void deleteCaseNameNode() throws InterruptedException { + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseNameCoordSvcNode()); + logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName())); + try { + String caseNameLockNodeName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory()); + coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); + } catch (CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex); + } + } + } + /** * Examines a coordination service exception to try to determine if it is a * no node exception. @@ -809,26 +767,28 @@ final class DeleteCaseTask implements Runnable { "# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}..." }) private void deleteManifestFileNodes() throws InterruptedException { - boolean allINodesDeleted = true; - Iterator iterator = manifestFileLocks.iterator(); - while (iterator.hasNext()) { - Lock manifestFileLock = iterator.next(); - String manifestFilePath = manifestFileLock.getNodePath(); - try { - progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); - logger.log(Level.INFO, String.format("Releasing the lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); - manifestFileLock.release(); - progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileNode(manifestFilePath)); - logger.log(Level.INFO, String.format("Deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName())); - coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath); - } catch (CoordinationServiceException ex) { - allINodesDeleted = false; - logger.log(Level.WARNING, String.format("Error deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); + if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) { + boolean allINodesDeleted = true; + Iterator iterator = manifestFileLocks.iterator(); + while (iterator.hasNext()) { + Lock manifestFileLock = iterator.next(); + String manifestFilePath = manifestFileLock.getNodePath(); + try { + progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); + logger.log(Level.INFO, String.format("Releasing the lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + manifestFileLock.release(); + progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileNode(manifestFilePath)); + logger.log(Level.INFO, String.format("Deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath); + } catch (CoordinationServiceException ex) { + allINodesDeleted = false; + logger.log(Level.WARNING, String.format("Error deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex); + } + iterator.remove(); + } + if (allINodesDeleted) { + setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES); } - iterator.remove(); - } - if (allINodesDeleted) { - setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES); } } @@ -847,4 +807,15 @@ final class DeleteCaseTask implements Runnable { } } + /** + * Checks whether the interrupted flag of the current thread is set. + * + * @throws InterruptedException If the interrupted flag is set. + */ + private void checkForCancellation() throws InterruptedException { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException("Interrupt detected"); + } + } + } From 7bbe4d6d962279149481d3d28903366c7baa9591 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 1 Apr 2019 17:33:27 -0400 Subject: [PATCH 32/32] Remove input directory deletion from case deletion --- .../autoingest/Bundle.properties-MERGED | 11 +++++-- .../autoingest/DeleteCaseInputAction.java | 2 +- .../DeleteCaseInputAndOutputAction.java | 2 +- .../autoingest/DeleteCaseOutputAction.java | 2 +- .../autoingest/DeleteCaseTask.java | 32 ++++++------------- 5 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index b6991a630a..9dacb71384 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -89,6 +89,10 @@ AutoIngestControlPanel.runningTable.toolTipText=The Running table displays the c AutoIngestControlPanel.SharedConfigurationDisabled=Shared configuration disabled AutoIngestControlPanel.ShowLogFailed.Message=Case log file does not exist AutoIngestControlPanel.ShowLogFailed.Title=Unable to display case log +# {0} - case db status +# {1} - search svc Status +# {2} - coord svc Status +# {3} - msg broker status AutoIngestControlPanel.tbServicesStatusMessage.Message=Case databases {0}, keyword search {1}, coordination {2}, messaging {3} AutoIngestControlPanel.tbServicesStatusMessage.Message.Down=down AutoIngestControlPanel.tbServicesStatusMessage.Message.Unknown=unknown @@ -160,15 +164,15 @@ CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs CTL_CasesDashboardAction=Multi-User Cases Dashboard CTL_CasesDashboardTopComponent=Cases -DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty) +DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n DeleteCaseInputAction.menuItemText=Delete Input DeleteCaseInputAction.progressDisplayName=Delete Input DeleteCaseInputAction.taskName=input -DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty)\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes +DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output DeleteCaseInputAndOutputAction.taskName=input-and-output -DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes +DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes DeleteCaseOutputAction.menuItemText=Delete Output DeleteCaseOutputAction.progressDisplayName=Delete Output DeleteCaseOutputAction.taskName=output @@ -201,6 +205,7 @@ HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-use OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted. OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details. OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File +# {0} - caseErrorMessage OpenCaseAction.errorMsg=Failed to open case: {0} OpenCaseAction.menuItemText=Open OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change.\n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java index 4899ea285d..689b7311d5 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java @@ -54,7 +54,7 @@ final class DeleteCaseInputAction extends DeleteCaseAction { } @NbBundle.Messages({ - "DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty)" + "DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n" }) @Override public void actionPerformed(ActionEvent event) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java index 38c13d01eb..0e38e85f40 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java @@ -50,7 +50,7 @@ final class DeleteCaseInputAndOutputAction extends DeleteCaseAction { } @NbBundle.Messages({ - "DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest files\n -Data sources\n -Input directories (if empty)\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes" + "DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes" }) @Override public void actionPerformed(ActionEvent event) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index d32d0fe029..ceb2abaa4b 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -53,7 +53,7 @@ final class DeleteCaseOutputAction extends DeleteCaseAction { } @NbBundle.Messages({ - "DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n -Manifest file znodes\n -Case database\n -Core.properties file\n -Case directory\n -Case znodes" + "DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes" }) @Override public void actionPerformed(ActionEvent event) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index 1c38a0a67d..31447ce2a1 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -450,13 +450,13 @@ final class DeleteCaseTask implements Runnable { progress.progress(Bundle.DeleteCaseTask_progress_openingCaseDatabase()); logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName())); caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory()); + List dataSources = caseDb.getDataSources(); checkForCancellation(); /* * For every manifest file associated with the case, attempt to - * delete the data source referenced by the manifest, the manifest, - * and if the input directory that contains the manifest is empty - * after the data source and manifest are deleted, delete that, too. + * delete both the data source referenced by the manifest and the + * manifest. */ boolean allInputDeleted = true; for (Path manifestFilePath : manifestFilePaths) { @@ -465,15 +465,18 @@ final class DeleteCaseTask implements Runnable { if (manifestFile.exists()) { Manifest manifest = parseManifestFile(manifestFilePath); if (manifest != null) { - List dataSources = caseDb.getDataSources(); - if (deleteDataSources(manifest, dataSources) && deleteManifestFile(manifestFile)) { - deleteInputDirectoryIfEmpty(manifestFilePath); + if (deleteDataSources(manifest, dataSources)) { + if (!deleteManifestFile(manifestFile)) { + logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + allInputDeleted = false; + } } else { logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); allInputDeleted = false; } } else { logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + allInputDeleted = false; } } else { logger.log(Level.WARNING, String.format("Did not find manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); @@ -484,7 +487,7 @@ final class DeleteCaseTask implements Runnable { } } catch (TskCoreException | UserPreferencesException ex) { - logger.log(Level.INFO, String.format("Failed to open the case database for %s", caseNodeData.getDisplayName()), ex); + logger.log(Level.INFO, String.format("Failed to open or query the case database for %s", caseNodeData.getDisplayName()), ex); } finally { if (caseDb != null) { @@ -608,21 +611,6 @@ final class DeleteCaseTask implements Runnable { return allFilesDeleted; } - /** - * Deletes the input directory for a manifest, if the directory is empty. - * - * @param manifestFilePath The manifest fiell path. - */ - void deleteInputDirectoryIfEmpty(Path manifestFilePath) { - final Path inputDirectoryPath = manifestFilePath.getParent(); - final File inputDirectory = inputDirectoryPath.toFile(); - File[] files = inputDirectory.listFiles(); - logger.log(Level.INFO, String.format("Deleting empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); - if ((files == null || files.length == 0) && !inputDirectory.delete()) { - logger.log(Level.WARNING, String.format("Failed to delete empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName())); - } - } - /** * Deletes the case resources coordination service node. *