From b4aca79862a84e657149a6e2918991acd0c95cb0 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Feb 2019 16:05:25 -0500 Subject: [PATCH 001/196] 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\: