diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 32e02fdbcac4ebf477893315fae8e2f25b948f0b..57299ab1c67d761f3da6e20ec3909b8ed9095acf 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -78,13 +78,12 @@ Case.updateCaseName.exception.msg=Error while trying to update the case name. Case.updateExaminer.exception.msg=Error while trying to update the examiner. Case.updateCaseNum.exception.msg=Error while trying to update the case number. Case.exception.errGetRootObj=Error getting root objects. -Case.createCaseDir.exception.existNotDir=Cannot create case dir, already exists and is not a directory\: {0} -Case.createCaseDir.exception.existCantRW=Cannot create case dir, already exists and cannot read/write\: {0} +Case.createCaseDir.exception.existNotDir=Cannot create case directory, it already exists and is not a directory\: {0} +Case.createCaseDir.exception.existCantRW=Cannot create case directory, it already exists and cannot read/write\: {0} Case.createCaseDir.exception.cantCreate=Cannot create case directory or it already exists\: {0} Case.createCaseDir.exception.cantCreateCaseDir=Could not create case directory\: {0} Case.createCaseDir.exception.cantCreateModDir=Could not create modules output directory\: {0} Case.createCaseDir.exception.cantCreateReportsDir=Could not create reports output directory\: {0} -Case.createCaseDir.exception.gen=Could not create case directory\: {0} Case.CollaborationSetup.FailNotify.ErrMsg=Failed to connect to any other nodes that may be collaborating on this case. Case.CollaborationSetup.FailNotify.Title=Connection Failure Case.GetCaseTypeGivenPath.Failure=Unable to get case type @@ -190,17 +189,10 @@ ReviewModeCasePanel.StatusIconHeaderText=Status ReviewModeCasePanel.OutputFolderHeaderText=Output Folder ReviewModeCasePanel.LastAccessedTimeHeaderText=Last Accessed Time ReviewModeCasePanel.MetadataFileHeaderText=Metadata File -OpenMultiUserCasePanel.jLabel1.text=Recent Cases -OpenMultiUserCasePanel.openButton.text=Open -OpenMultiUserCasePanel.cancelButton.text=Cancel -MultiUserCasesPanel.bnOpen.text=&Open CueBannerPanel.newCaseLabel.text=New Case CueBannerPanel.openCaseButton.text= CueBannerPanel.openCaseLabel.text=Open Case -MultiUserCasesPanel.bnOpenSingleUserCase.text=Open Single-User Case... CueBannerPanel.newCaseButton.text= -MultiUserCasesPanel.searchLabel.text=Select any case and start typing to search by case name -MultiUserCasesPanel.cancelButton.text=Cancel ImageFilePanel.sectorSizeLabel.text=Sector size: LocalDiskPanel.sectorSizeLabel.text=Sector Size: LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default @@ -240,4 +232,8 @@ ImageFilePanel.sha1HashTextField.text= ImageFilePanel.md5HashTextField.text= ImageFilePanel.errorLabel.text=Error Label ImageFilePanel.hashValuesNoteLabel.text=NOTE: These values will not be validated when the data source is added. -ImageFilePanel.hashValuesLabel.text=Hash Values (optional): \ No newline at end of file +ImageFilePanel.hashValuesLabel.text=Hash Values (optional): +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... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 208c5ed868b7aae14c00bc45b72ce6e717d515cd..b067b28c983b321a4774a7946f0a9983494479eb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -65,7 +65,6 @@ Case.createCaseDir.exception.existNotDir=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u3 Case.createCaseDir.exception.existCantRW=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u65e2\u306b\u5b58\u5728\u3057\u3001\u8aad\u307f\u53d6\u308a\uff0f\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u307e\u305b\u3093\uff1a{0} Case.createCaseDir.exception.cantCreateCaseDir=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a {0} Case.createCaseDir.exception.cantCreateModDir=\u30e2\u30b8\u30e5\u30fc\u30eb\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0} -Case.createCaseDir.exception.gen=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0} CaseDeleteAction.closeConfMsg.text=\u3053\u306e\u30b1\u30fc\u30b9\u3092\u672c\u5f53\u306b\u9589\u3058\u3001\u524a\u9664\u3057\u307e\u3059\u304b\uff1f\n\ \u30b1\u30fc\u30b9\u540d\uff1a {0}\n\ \u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\: {1} @@ -132,7 +131,6 @@ AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=\u30ad\u30e3\u30f3\u30bb\u30e LocalFilesPanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb ImageFilePanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb NewCaseVisualPanel1.caseTypeLabel.text=\u30b1\u30fc\u30b9\u30bf\u30a4\u30d7\uff1a -Case.databaseConnectionInfo.error.msg=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b5\u30fc\u30d0\u30fc\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u624b\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30c4\u30fc\u30eb\u3001\u30aa\u30d7\u30b7\u30e7\u30f3\u3001\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Case.open.exception.multiUserCaseNotEnabled=\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u306e\u30b1\u30fc\u30b9\u304c\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u306a\u3044\u3068\u3001\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u306e\u30b1\u30fc\u30b9\u306f\u958b\u3051\u307e\u305b\u3093\u3002\u30c4\u30fc\u30eb\u3001\u30aa\u30d7\u30b7\u30e7\u30f3\u3001\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Case.createCaseDir.exception.cantCreateReportsDir=\u30ec\u30dd\u30fc\u30c8\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0} Case.CollaborationSetup.FailNotify.ErrMsg=\u3053\u306e\u30b1\u30fc\u30b9\u3067\u4f7f\u308f\u308c\u3066\u3044\u308b\u304b\u3082\u3057\u308c\u306a\u3044\u30ce\u30fc\u30c9\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 @@ -179,9 +177,6 @@ OptionalCasePropertiesPanel.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uf OptionalCasePropertiesPanel.caseDisplayNameLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a CueBannerPanel.openRecentCaseLabel.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f CueBannerPanel.openAutoIngestCaseLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f -OpenMultiUserCasePanel.openButton.text=\u958b\u304f -OpenMultiUserCasePanel.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb -OpenMultiUserCasePanel.jLabel1.text=\u6700\u8fd1\u958b\u3044\u305f\u30d5\u30a1\u30a4\u30eb CueBannerPanel.newCaseLabel.text=\u65b0\u898f\u30b1\u30fc\u30b9\u3092\u4f5c\u6210 CueBannerPanel.openCaseLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f ImageFilePanel.sectorSizeLabel.text=\u30a4\u30f3\u30d7\u30c3\u30c8\u30bf\u30a4\u30e0\u30be\u30fc\u30f3\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044\uff1a @@ -200,4 +195,5 @@ LogicalEvidenceFilePanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb LogicalEvidenceFilePanel.logicalEvidenceFileChooser.dialogTitle=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u307e\u305f\u306f\u30d5\u30a9\u30eb\u30c0\u3092\u9078\u629e LogicalEvidenceFilePanel.logicalEvidenceFileChooser.approveButtonText=\u9078\u629e LocalDiskSelectionDialog.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb -LocalDiskSelectionDialog.selectLocalDiskLabel.text=\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30b9\u30af\u3092\u9078\u629e\uff1a \ No newline at end of file +LocalDiskSelectionDialog.selectLocalDiskLabel.text=\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30b9\u30af\u3092\u9078\u629e\uff1a +OpenMultiUserCasePanel.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 7d4dab493e290727691b9846a2b271225e8879e8..c50f83f357595773afd02ec8012bff0bd6d0b9ee 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -32,6 +33,7 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; @@ -39,7 +41,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.MissingResourceException; import java.util.Set; import java.util.TimeZone; import java.util.UUID; @@ -574,7 +575,7 @@ public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDe * exception. */ @Messages({ - "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.", + "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.", "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case." }) public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException { @@ -582,7 +583,7 @@ public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseAct try { metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex); } if (CaseType.MULTI_USER_CASE == metadata.getCaseType() && !UserPreferences.getIsMultiUserModeEnabled()) { throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings()); @@ -713,7 +714,8 @@ public static void deleteCurrentCase() throws CaseActionException { "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.",}) + "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(); @@ -895,72 +897,71 @@ private static String displayNameToUniqueName(String caseDisplayName) { /** * Creates a case directory and its subdirectories. * - * @param caseDir Path to the case directory (typically base + case name). - * @param caseType The type of case, single-user or multi-user. + * @param caseDirPath Path to the case directory (typically base + case + * name). + * @param caseType The type of case, single-user or multi-user. * * @throws CaseActionException throw if could not create the case dir */ - public static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException { - - File caseDirF = new File(caseDir); - - if (caseDirF.exists()) { - if (caseDirF.isFile()) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir)); - - } else if (!caseDirF.canRead() || !caseDirF.canWrite()) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir)); + public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException { + /* + * Check the case directory path and permissions. The case directory may + * already exist. + */ + File caseDir = new File(caseDirPath); + if (caseDir.exists()) { + if (caseDir.isFile()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath)); + } else if (!caseDir.canRead() || !caseDir.canWrite()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath)); } } - try { - boolean result = (caseDirF).mkdirs(); // create root case Directory - - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir)); - } - - // create the folders inside the case directory - String hostClause = ""; + /* + * Create the case directory, if it does not already exist. + */ + if (!caseDir.mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath)); + } - if (caseType == CaseType.MULTI_USER_CASE) { - hostClause = File.separator + NetworkUtils.getLocalHostName(); - } - result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs() - && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs() - && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs() - && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs(); - - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir)); - } + /* + * Create the subdirectories of the case directory, if they do not + * already exist. Note that multi-user cases get an extra layer of + * subdirectories, one subdirectory per application host machine. + */ + String hostPathComponent = ""; + if (caseType == CaseType.MULTI_USER_CASE) { + hostPathComponent = File.separator + NetworkUtils.getLocalHostName(); + } - final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER; - result = new File(modulesOutDir).mkdir(); + Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER); + if (!exportDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir)); + } - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", - modulesOutDir)); - } + Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER); + if (!logsDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir)); + } - final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER; - result = new File(reportsOutDir).mkdir(); + Path tempDir = Paths.get(caseDirPath, hostPathComponent, TEMP_FOLDER); + if (!tempDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", tempDir)); + } - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", - modulesOutDir)); + Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER); + if (!cacheDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir)); + } - } + Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER); + if (!moduleOutputDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir)); + } - } catch (MissingResourceException | CaseActionException e) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e); + Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER); + if (!reportsDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir)); } } @@ -1588,15 +1589,15 @@ public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) { public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) { eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId)); } - + /** * Notifies case event subscribers that a data source has been added to the * case database. * * This should not be called from the event dispatch thread (EDT) * - * @param dataSource The data source. - * @param newName The new name for the data source + * @param dataSource The data source. + * @param newName The new name for the data source */ public void notifyDataSourceNameChanged(Content dataSource, String newName) { eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName)); @@ -1759,7 +1760,7 @@ CaseMetadata getMetadata() { } /** - * Updates the case display name. + * Updates the case details. * * @param newDisplayName the new display name for the case * @@ -1775,6 +1776,16 @@ void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException { } catch (CaseMetadataException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex); } + if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) { + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setDisplayName(caseDetails.getCaseDisplayName()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + } + } if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) { eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber())); } @@ -1963,38 +1974,50 @@ private void open(boolean isNewCase) throws CaseActionException { * @param isNewCase True for a new case, false otherwise. * @param progressIndicator A progress indicator. * - * @throws CaseActionException if there is a problem creating the case. The + * @throws CaseActionException If there is a problem creating the case. The * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. */ private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException { try { - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + checkForUserCancellation(); + createCaseDirectoryIfDoesNotExist(progressIndicator); + checkForUserCancellation(); + switchLoggingToCaseLogsDirectory(progressIndicator); + checkForUserCancellation(); + if (isNewCase) { + saveCaseMetadataToFile(progressIndicator); } - + checkForUserCancellation(); if (isNewCase) { - createCaseData(progressIndicator); + createCaseNodeData(progressIndicator); } else { - openCaseData(progressIndicator); + updateCaseNodeData(progressIndicator); } - - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + checkForUserCancellation(); + if (!isNewCase) { + deleteTempfilesFromCaseDirectory(progressIndicator); } - - openServices(progressIndicator); - - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + checkForUserCancellation(); + if (isNewCase) { + createCaseDatabase(progressIndicator); + } else { + openCaseDataBase(progressIndicator); } + checkForUserCancellation(); + openCaseLevelServices(progressIndicator); + checkForUserCancellation(); + openAppServiceCaseResources(progressIndicator); + checkForUserCancellation(); + openCommunicationChannels(progressIndicator); + } catch (CaseActionException ex) { /* - * Cancellation or failure. Clean up. The sleep is a little hack to - * clear the interrupted flag for this thread if this is a - * cancellation scenario, so that the clean up can run to completion - * in this thread. + * Cancellation or failure. Clean up by calling the close method. + * The sleep is a little hack to clear the interrupted flag for this + * thread if this is a cancellation scenario, so that the clean up + * can run to completion in the current thread. */ try { Thread.sleep(1); @@ -2006,43 +2029,180 @@ private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws } /** - * Creates the case directory, case database, and case metadata file. + * Checks current thread for an interrupt. Usage: checking for user + * cancellation of a case creation/opening operation, as reflected in the + * exception message. * - * @param progressIndicator A progress indicartor. + * @throws CaseActionCancelledException If the current thread is + * interrupted, assumes interrupt was + * due to a user action. + */ + private static void checkForUserCancellation() throws CaseActionCancelledException { + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + } + + /** + * Creates the case directory, if it does not already exist. + * + * TODO (JIRA-2180): Always create the case directory as part of the case + * creation process. * - * @throws CaseActionException If there is a problem creating the case - * database. The exception will have a + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a * user-friendly message and may be a wrapper * for a lower-level exception. */ @Messages({ - "Case.progressMessage.creatingCaseDirectory=Creating case directory...", - "Case.progressMessage.creatingCaseDatabase=Creating case database...", - "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}", - "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file." + "Case.progressMessage.creatingCaseDirectory=Creating case directory..." }) - private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException { - /* - * Create the case directory, if it does not already exist. - * - * TODO (JIRA-2180): Always create the case directory as part of the - * case creation process. - */ + private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); if (new File(metadata.getCaseDirectory()).exists() == false) { progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType()); } + } + + /** + * Switches from writing log messages to the application logs to the logs + * subdirectory of the case directory. + * + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.switchingLogDirectory=Switching log directory..." + }) + private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) { + progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); + Logger.setLogDirectory(getLogDirectoryPath()); + } + + /** + * Saves teh case metadata to a file.SHould not be called until the case + * directory has been created. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...", + "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}." + }) + private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata()); + try { + this.metadata.writeToFile(); + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex); + } + } + + /** + * Creates the node data for the case directory lock coordination service + * node. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...", + "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}." + }) + private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException { + if (getCaseType() == CaseType.MULTI_USER_CASE) { + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData()); + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(metadata); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex); + } + } + } + + /** + * Updates the node data for the case directory lock coordination service + * node. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...", + "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}." + }) + private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException { + if (getCaseType() == CaseType.MULTI_USER_CASE) { + progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData()); + if (getCaseType() == CaseType.MULTI_USER_CASE) { + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setLastAccessDate(new Date()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + } + } + } + } + /** + * Deletes any files in the temp subdirectory of the case directory. + * + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..." + }) + private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) { /* - * Create the case database. + * Clear the temp subdirectory of the case directory. */ + progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory()); + Case.clearTempSubDir(this.getTempDirectory()); + } + + /** + * Creates the node data for the case directory lock coordination service + * node, the case directory, the case database and the case metadata file. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.creatingCaseDatabase=Creating case database...", + "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}." + }) + private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException { progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase()); try { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { /* * For single-user cases, the case database is a SQLite database - * with a standard name, physically located in the root of the - * case directory. + * with a standard name, physically located in the case + * directory. */ caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString()); metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME); @@ -2050,7 +2210,7 @@ private void createCaseData(ProgressIndicator progressIndicator) throws CaseActi /* * For multi-user cases, the case database is a PostgreSQL * database with a name derived from the case display name, - * physically located on a database server. + * physically located on the PostgreSQL database server. */ caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); metadata.setCaseDatabaseName(caseDb.getDatabaseName()); @@ -2058,143 +2218,81 @@ private void createCaseData(ProgressIndicator progressIndicator) throws CaseActi } catch (TskCoreException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex); } catch (UserPreferencesException ex) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex); } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex); } } /** - * Opens an existing case database. + * Updates the node data for an existing case directory lock coordination + * service node and opens an existing case database. * * @param progressIndicator A progress indicator. * - * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. */ @Messages({ "Case.progressMessage.openingCaseDatabase=Opening case database...", - "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database.", - "Case.unsupportedSchemaVersionMessage=Unsupported DB schema version - see log for details", - "Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-User.", - "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. " - + "See Tools, Options, Multi-user." + "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.", + "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User." }) - private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException { + private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); try { - progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); String databaseName = metadata.getCaseDatabaseName(); if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString()); } else if (UserPreferences.getIsMultiUserModeEnabled()) { - try { - caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); - } catch (UserPreferencesException ex) { - throw new CaseActionException(Case_databaseConnectionInfo_error_msg(), ex); - } + caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); } else { throw new CaseActionException(Case_open_exception_multiUserCaseNotEnabled()); } } catch (TskUnsupportedSchemaVersionException ex) { - throw new CaseActionException(Bundle.Case_unsupportedSchemaVersionMessage(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex); + } catch (UserPreferencesException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex); } catch (TskCoreException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex); } } /** - * Completes the case opening tasks common to both new cases and existing - * cases. + * Opens the case-level services: the files manager, tags manager and + * blackboard. * * @param progressIndicator A progress indicator. - * - * @throws CaseActionException */ @Messages({ - "Case.progressMessage.switchingLogDirectory=Switching log directory...", - "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", - "Case.progressMessage.openingCaseLevelServices=Opening case-level services...", - "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...", - "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",}) - private void openServices(ProgressIndicator progressIndicator) throws CaseActionException { - /* - * Switch to writing to the application logs in the logs subdirectory of - * the case directory. - */ - progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); - Logger.setLogDirectory(getLogDirectoryPath()); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * Clear the temp subdirectory of the case directory. - */ - progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory()); - Case.clearTempSubDir(this.getTempDirectory()); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * Open the case-level services. - */ + "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",}) + private void openCaseLevelServices(ProgressIndicator progressIndicator) { progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices()); this.caseServices = new Services(caseDb); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * Allow any registered application services to open any resources - * specific to this case. - */ - progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); - openAppServiceCaseResources(); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * If this case is a multi-user case, set up for communication with - * other nodes. - */ - if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { - progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications()); - try { - eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName())); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - collaborationMonitor = new CollaborationMonitor(metadata.getCaseName()); - } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) { - /* - * The collaboration monitor and event channel are not - * essential. Log an error and notify the user, but do not - * throw. - */ - logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), - NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"))); - } - } - } } /** * Allows any registered application-level services to open resources * specific to this case. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. */ @NbBundle.Messages({ + "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...", "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources", "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...", "# {0} - service name", "Case.servicesException.notificationTitle={0} Error" }) - private void openAppServiceCaseResources() throws CaseActionException { + private void openAppServiceCaseResources(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); /* * Each service gets its own independently cancellable/interruptible * task, running in a named thread managed by an executor service, with @@ -2210,20 +2308,20 @@ private void openAppServiceCaseResources() throws CaseActionException { * with a Cancel button. */ CancelButtonListener cancelButtonListener = null; - ProgressIndicator progressIndicator; + ProgressIndicator appServiceProgressIndicator; if (RuntimeProperties.runningWithGUI()) { cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName())); - progressIndicator = new ModalDialogProgressIndicator( + appServiceProgressIndicator = new ModalDialogProgressIndicator( mainFrame, Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()), new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), cancelButtonListener); } else { - progressIndicator = new LoggingProgressIndicator(); + appServiceProgressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start(Bundle.Case_progressMessage_preparing()); - AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator); + appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing()); + AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator); String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS threadNameSuffix = threadNameSuffix.toLowerCase(); TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix)); @@ -2279,17 +2377,50 @@ private void openAppServiceCaseResources() throws CaseActionException { * task responded to a cancellation request. */ ThreadUtils.shutDownTaskExecutor(executor); - progressIndicator.finish(); + appServiceProgressIndicator.finish(); } + checkForUserCancellation(); + } + } - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + /** + * If this case is a multi-user case, sets up for communication with other + * application nodes. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...", + "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}." + }) + private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException { + if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { + progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications()); + try { + eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName())); + checkForUserCancellation(); + collaborationMonitor = new CollaborationMonitor(metadata.getCaseName()); + } catch (AutopsyEventException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex); + } catch (CollaborationMonitor.CollaborationMonitorException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex); } } } /** * Closes the case. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. */ private void close() throws CaseActionException { /* diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java deleted file mode 100644 index 402b64d78f44bc93244aa02e26ed39ff5091f812..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2017-2018 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.lang.reflect.InvocationTargetException; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.TableColumnModel; -import org.netbeans.swing.etable.ETableColumn; -import org.netbeans.swing.etable.ETableColumnModel; -import org.netbeans.swing.outline.DefaultOutlineModel; -import org.netbeans.swing.outline.Outline; -import org.openide.nodes.Node; -import java.awt.EventQueue; -import java.io.File; -import java.io.IOException; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javax.swing.SwingWorker; -import org.openide.explorer.ExplorerManager; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.EmptyNode; - -/** - * A Swing JPanel with a scroll pane child component. The scroll pane contain - * the table of cases. - * - * Used to display a list of multi user cases and allow the user to open one of - * them. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider { - - private static final long serialVersionUID = 1L; - private final Outline outline; - private ExplorerManager em; - private final org.openide.explorer.view.OutlineView outlineView; - private int originalPathColumnIndex = 0; - private static final Logger LOGGER = Logger.getLogger(CaseBrowser.class.getName()); - private LoadCaseListWorker tableWorker; - - @Override - public ExplorerManager getExplorerManager() { - return em; - } - - /** - * Creates a new CaseBrowser - */ - CaseBrowser() { - outlineView = new org.openide.explorer.view.OutlineView(); - initComponents(); - - outline = outlineView.getOutline(); - outlineView.setPropertyColumns( - Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), - Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath()); - ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); - customize(); - - } - - /** - * Configures the the table of cases and its columns. - */ - private void customize() { - outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - TableColumnModel columnModel = outline.getColumnModel(); - int dateColumnIndex = 0; - for (int index = 0; index < columnModel.getColumnCount(); index++) { - //get indexes for created date column and path column - if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_metadataFilePath())) { - originalPathColumnIndex = index; - } else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_createdTime())) { - dateColumnIndex = index; - } - } - //Hide path column by default (user can unhide it) - ETableColumn column = (ETableColumn) columnModel.getColumn(originalPathColumnIndex); - ((ETableColumnModel) columnModel).setColumnHidden(column, true); - outline.setRootVisible(false); - - //Sort on Created date column in descending order by default - outline.setColumnSorted(dateColumnIndex, false, 1); - if (null == em) { - em = new ExplorerManager(); - } - caseTableScrollPane.setViewportView(outlineView); - this.setVisible(true); - outline.setRowSelectionAllowed(false); - } - - /** - * Add a listener to changes in case selections in the table - * - * @param listener the ListSelectionListener to add - */ - void addListSelectionListener(ListSelectionListener listener) { - outline.getSelectionModel().addListSelectionListener(listener); - } - - /** - * Get the path to the .aut file for the selected case. - * - * @return the full path to the selected case's .aut file - */ - String getCasePath() { - int[] selectedRows = outline.getSelectedRows(); - if (selectedRows.length == 1) { - try { - return ((Node.Property) outline.getModel().getValueAt(outline.convertRowIndexToModel(selectedRows[0]), originalPathColumnIndex)).getValue().toString(); - } catch (IllegalAccessException | InvocationTargetException ex) { - LOGGER.log(Level.SEVERE, "Unable to get case path from table.", ex); - } - } - return null; - } - - /** - * Check if a row could be and is selected. - * - * @return true if a row is selected, false if no row is selected - */ - boolean isRowSelected() { - return outline.getRowSelectionAllowed() && outline.getSelectedRows().length > 0; - } - - @NbBundle.Messages({"CaseBrowser.caseListLoading.message=Please Wait..."}) - /** - * Gets the list of cases known to the review mode cases manager and - * refreshes the cases table. - */ - void refresh() { - if (tableWorker == null || tableWorker.isDone()) { - outline.setRowSelectionAllowed(false); - //create a new TableWorker to and execute it in a background thread if one is not currently working - //set the table to display text informing the user that the list is being retreived and disable case selection - EmptyNode emptyNode = new EmptyNode(Bundle.CaseBrowser_caseListLoading_message()); - em.setRootContext(emptyNode); - tableWorker = new LoadCaseListWorker(); - tableWorker.execute(); - } - - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents - private void initComponents() { - - caseTableScrollPane = new javax.swing.JScrollPane(); - - setMinimumSize(new java.awt.Dimension(0, 5)); - setPreferredSize(new java.awt.Dimension(5, 5)); - setLayout(new java.awt.BorderLayout()); - - caseTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - caseTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 5)); - caseTableScrollPane.setOpaque(false); - caseTableScrollPane.setPreferredSize(new java.awt.Dimension(5, 5)); - add(caseTableScrollPane, java.awt.BorderLayout.CENTER); - }// </editor-fold>//GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane caseTableScrollPane; - // End of variables declaration//GEN-END:variables - - /** - * Swingworker to fetch the updated List of cases in a background thread - */ - private class LoadCaseListWorker extends SwingWorker<Void, Void> { - - private List<CaseMetadata> cases; - - /** - * Gets a list of the cases in the top level case folder - * - * @return List of cases. - * - * @throws CoordinationServiceException - */ - private List<CaseMetadata> getCases() throws CoordinationService.CoordinationServiceException { - List<CaseMetadata> caseList = new ArrayList<>(); - List<String> nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); - - for (String node : nodeList) { - Path casePath; - try { - casePath = Paths.get(node).toRealPath(LinkOption.NOFOLLOW_LINKS); - - File caseFolder = casePath.toFile(); - if (caseFolder.exists()) { - /* - * Search for '*.aut' files. - */ - File[] fileArray = caseFolder.listFiles(); - if (fileArray == null) { - continue; - } - String autFilePath = null; - for (File file : fileArray) { - String name = file.getName().toLowerCase(); - if (autFilePath == null && name.endsWith(".aut")) { - try { - caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); - } catch (CaseMetadata.CaseMetadataException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } - break; - } - } - } - } catch (IOException ignore) { - //if a path could not be resolved to a real path do add it to the caseList - } - } - return caseList; - } - - @Override - protected Void doInBackground() throws Exception { - - try { - cases = getCases(); - } catch (CoordinationService.CoordinationServiceException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception while refreshing the table.", ex); //NON-NLS - } - return null; - } - - @Override - protected void done() { - EventQueue.invokeLater(() -> { - MultiUserNode caseListNode = new MultiUserNode(cases); - em.setRootContext(caseListNode); - outline.setRowSelectionAllowed(true); - }); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 6e47363db541b5ce8e46606e99a086711b2103c3..202ed86f7222d0cf8c0573a57e9412a454a7565e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -52,7 +53,8 @@ public final class CaseMetadata { private static final String FILE_EXTENSION = ".aut"; - private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)"); + private static final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss (z)"; + private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US); /* * Fields from schema version 1 @@ -92,6 +94,7 @@ public final class CaseMetadata { private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS + /* * Unread fields, regenerated on save. */ @@ -119,6 +122,15 @@ public static String getFileExtension() { return FILE_EXTENSION; } + /** + * Gets the date format used for dates in case metadata. + * + * @return The date format. + */ + public static DateFormat getDateFormat() { + return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US); + } + /** * Constructs a CaseMetadata object for a new case. The metadata is not * persisted to the case metadata file until writeFile or a setX method is @@ -295,7 +307,7 @@ public String getTextIndexName() { * * @return The date this case was created, as a string. */ - String getCreatedDate() { + public String getCreatedDate() { return createdDate; } @@ -352,7 +364,7 @@ void setCreatedByVersion(String buildVersion) throws CaseMetadataException { * @throws CaseMetadataException If there is an error writing to the case * metadata file. */ - private void writeToFile() throws CaseMetadataException { + void writeToFile() throws CaseMetadataException { try { /* * Create the XML DOM. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index a8e18da6507b6bf7719c064e1a050e0f6019809a..47c002d36b96e6a09a8d22617996acdcd0013bfb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -161,7 +161,7 @@ public void actionPerformed(ActionEvent e) { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (multiUserCaseWindow == null) { - multiUserCaseWindow = MultiUserCasesDialog.getInstance(); + multiUserCaseWindow = OpenMultiUserCaseDialog.getInstance(); } multiUserCaseWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); multiUserCaseWindow.setVisible(true); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java new file mode 100755 index 0000000000000000000000000000000000000000..aa689b0c250c59cdffd49bbfbf63558371776369 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java @@ -0,0 +1,103 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import java.util.ArrayList; +import java.util.List; +import javax.swing.Action; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.datamodel.NodeProperty; + +/** + * A NetBeans Explorer View node that represents a multi-user case. + */ +final class MultiUserCaseNode extends AbstractNode { + + private final CaseNodeData caseNodeData; + + /** + * Constructs a NetBeans Explorer View node that represents a multi-user + * case. + * + * @param caseNodeData The coordination service node data for the case. + */ + MultiUserCaseNode(CaseNodeData caseNodeData) { + super(Children.LEAF); + super.setName(caseNodeData.getDisplayName()); + setName(caseNodeData.getDisplayName()); + setDisplayName(caseNodeData.getDisplayName()); + this.caseNodeData = caseNodeData; + } + + @NbBundle.Messages({ + "MultiUserCaseNode.column.name=Name", + "MultiUserCaseNode.column.createTime=Create Time", + "MultiUserCaseNode.column.path=Path" + }) + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + sheetSet.put(new NodeProperty<>(Bundle.MultiUserCaseNode_column_name(), + Bundle.MultiUserCaseNode_column_name(), + Bundle.MultiUserCaseNode_column_name(), + caseNodeData.getDisplayName())); + sheetSet.put(new NodeProperty<>(Bundle.MultiUserCaseNode_column_createTime(), + Bundle.MultiUserCaseNode_column_createTime(), + Bundle.MultiUserCaseNode_column_createTime(), + caseNodeData.getCreateDate())); + sheetSet.put(new NodeProperty<>(Bundle.MultiUserCaseNode_column_path(), + Bundle.MultiUserCaseNode_column_path(), + Bundle.MultiUserCaseNode_column_path(), + caseNodeData.getDirectory().toString())); + return sheet; + } + + @Override + public Action[] getActions(boolean context) { + List<Action> actions = new ArrayList<>(); + actions.add(new OpenMultiUserCaseAction(this.caseNodeData)); + actions.add(new OpenCaseAutoIngestLogAction(this.caseNodeData)); + return actions.toArray(new Action[actions.size()]); + } + + @Override + public Action getPreferredAction() { + return new OpenMultiUserCaseAction(this.caseNodeData); + } + + /** + * Gets the coordintaion service case node data this Explorer View node + * represents. + * + * @return The case node data. + */ + CaseNodeData getCaseNodeData() { + return this.caseNodeData; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java new file mode 100755 index 0000000000000000000000000000000000000000..d90fec1a64baab4f1d272bb3aefb497c34fb8471 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java @@ -0,0 +1,164 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import java.io.File; +import java.io.IOException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Queries the coordination service to collect the multi-user case node data + * stored in the case directory lock ZooKeeper nodes. + */ +final class MultiUserCaseNodeDataCollector { + + private static final Logger logger = Logger.getLogger(MultiUserCaseNodeDataCollector.class.getName()); + private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS + private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS + + /** + * Queries the coordination service to collect the multi-user case node data + * stored in the case directory lock ZooKeeper nodes. + * + * @return A list of CaseNodedata objects that convert data for a case + * directory lock coordination service node to and from byte arrays. + * + * @throws CoordinationServiceException If there is an error + */ + public static List<CaseNodeData> getNodeData() throws CoordinationService.CoordinationServiceException { + final List<CaseNodeData> cases = new ArrayList<>(); + final CoordinationService coordinationService = CoordinationService.getInstance(); + final List<String> nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES); + for (String nodeName : nodeList) { + /* + * Ignore auto ingest case name lock nodes. + */ + final Path nodeNameAsPath = Paths.get(nodeName); + if (!(nodeNameAsPath.toString().contains("\\") || nodeNameAsPath.toString().contains("//"))) { + continue; + } + + /* + * Ignore case auto ingest log lock nodes and resource lock nodes. + */ + final String lastNodeNameComponent = nodeNameAsPath.getFileName().toString(); + if (lastNodeNameComponent.equals(CASE_AUTO_INGEST_LOG_NAME)) { + continue; + } + + /* + * Ignore case resources lock nodes. + */ + if (lastNodeNameComponent.endsWith(RESOURCES_LOCK_SUFFIX)) { + continue; + } + + /* + * Get the data from the case directory lock node. This data may not + * exist for "legacy" nodes. If it is missing, create it. + */ + try { + CaseNodeData nodeData; + byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); + if (nodeBytes != null && nodeBytes.length > 0) { + nodeData = new CaseNodeData(nodeBytes); + if (nodeData.getVersion() == 0) { + /* + * Version 0 case node data was only written if errors + * occurred during an auto ingest job and consisted of + * only the set errors flag. + */ + nodeData = createNodeDataFromCaseMetadata(nodeName, true); + } + } else { + nodeData = createNodeDataFromCaseMetadata(nodeName, false); + } + cases.add(nodeData); + + } catch (CoordinationService.CoordinationServiceException | InterruptedException | IOException | ParseException | CaseMetadata.CaseMetadataException ex) { + logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex); + } + + } + return cases; + } + + /** + * Creates and saves case directory lock coordination service node data from + * the metadata file for the case associated with the node. + * + * @param nodeName The coordination service node name, i.e., the case + * directory path. + * @param errorsOccurred Whether or not errors occurred during an auto + * ingest job for the case. + * + * @return A CaseNodedata object. + * + * @throws IOException If there is an error writing the + * node data to a byte array. + * @throws CaseMetadataException If there is an error reading the + * case metadata file. + * @throws ParseException If there is an error parsing a date + * from the case metadata file. + * @throws CoordinationServiceException If there is an error interacting + * with the coordination service. + * @throws InterruptedException If a coordination service operation + * is interrupted. + */ + private static CaseNodeData createNodeDataFromCaseMetadata(String nodeName, boolean errorsOccurred) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException { + CaseNodeData nodeData = null; + Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS); + File caseDirectory = caseDirectoryPath.toFile(); + if (caseDirectory.exists()) { + File[] files = caseDirectory.listFiles(); + for (File file : files) { + String name = file.getName().toLowerCase(); + if (name.endsWith(CaseMetadata.getFileExtension())) { + CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath())); + nodeData = new CaseNodeData(metadata); + nodeData.setErrorsOccurred(errorsOccurred); + break; + } + } + } + if (nodeData != null) { + CoordinationService coordinationService = CoordinationService.getInstance(); + coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray()); + return nodeData; + } else { + throw new IOException(String.format("Could not find case metadata file for %s", nodeName)); + } + } + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private MultiUserCaseNodeDataCollector() { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.form similarity index 100% rename from Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form rename to Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.form diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java new file mode 100644 index 0000000000000000000000000000000000000000..620c89a2d6a613b8f6f523b195fc7dcc125a788c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java @@ -0,0 +1,144 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableColumnModel; +import org.netbeans.swing.etable.ETableColumn; +import org.netbeans.swing.etable.ETableColumnModel; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import org.openide.util.NbBundle; +import org.openide.explorer.view.OutlineView; + +/** + * A JPanel with a scroll pane child component that contains a NetBeans + * OutlineView that can be used to display a list of the multi-user cases known + * to the coordination service. + */ +@SuppressWarnings("PMD.SingularField") // Matisse-generated UI widgets cause lots of false positives +final class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider { + + private static final long serialVersionUID = 1L; + private final ExplorerManager explorerManager; + private final OutlineView outlineView; + private final Outline outline; + + /** + * Constructs a JPanel with a scroll pane child component that contains a + * NetBeans OutlineView that can be used to display a list of the multi-user + * cases known to the coordination service. + */ + MultiUserCasesBrowserPanel() { + explorerManager = new ExplorerManager(); + outlineView = new org.openide.explorer.view.OutlineView(); + initComponents(); + outline = outlineView.getOutline(); + configureOutlineView(); + explorerManager.setRootContext(new MultiUserCasesRootNode()); + } + + /** + * Configures the child scroll pane component's child OutlineView component. + */ + private void configureOutlineView() { + outlineView.setPropertyColumns( + Bundle.MultiUserCaseNode_column_createTime(), Bundle.MultiUserCaseNode_column_createTime(), + Bundle.MultiUserCaseNode_column_path(), Bundle.MultiUserCaseNode_column_path()); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.MultiUserCaseNode_column_name()); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + TableColumnModel columnModel = outline.getColumnModel(); + int pathColumnIndex = 0; + int dateColumnIndex = 0; + for (int index = 0; index < columnModel.getColumnCount(); index++) { + if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.MultiUserCaseNode_column_path())) { + pathColumnIndex = index; + } else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.MultiUserCaseNode_column_createTime())) { + dateColumnIndex = index; + } + } + + /* + * Hide path column by default (user can unhide it) + */ + ETableColumn column = (ETableColumn) columnModel.getColumn(pathColumnIndex); + ((ETableColumnModel) columnModel).setColumnHidden(column, true); + outline.setRootVisible(false); + + /* + * Sort on Created date column in descending order by default. + */ + outline.setColumnSorted(dateColumnIndex, false, 1); + + caseTableScrollPane.setViewportView(outlineView); + this.setVisible(true); + } + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + /** + * Adds a listener to changes in case selection in this browser. + * + * @param listener the ListSelectionListener to add + */ + void addListSelectionListener(ListSelectionListener listener) { + outline.getSelectionModel().addListSelectionListener(listener); + } + + /** + * Refreshes the list of multi-user cases in this browser. + */ + @NbBundle.Messages({ + "MultiUserCasesBrowserPanel.waitNode.message=Please Wait..." + }) + void refreshCases() { + explorerManager.setRootContext(new MultiUserCasesRootNode()); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + caseTableScrollPane = new javax.swing.JScrollPane(); + + setMinimumSize(new java.awt.Dimension(0, 5)); + setPreferredSize(new java.awt.Dimension(5, 5)); + setLayout(new java.awt.BorderLayout()); + + caseTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + caseTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 5)); + caseTableScrollPane.setOpaque(false); + caseTableScrollPane.setPreferredSize(new java.awt.Dimension(5, 5)); + add(caseTableScrollPane, java.awt.BorderLayout.CENTER); + }// </editor-fold>//GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane caseTableScrollPane; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java deleted file mode 100644 index af95b0c5a0c87d6d70024d820b820f12f1e66db2..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2018 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.awt.Dialog; -import java.awt.event.KeyEvent; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.KeyStroke; -import org.openide.windows.WindowManager; - -/** - * This class extends a JDialog and maintains the MultiUserCasesPanel. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -final class MultiUserCasesDialog extends JDialog { - - private static final long serialVersionUID = 1L; - private static final String REVIEW_MODE_TITLE = "Open Multi-User Case"; - private static MultiUserCasesPanel multiUserCasesPanel; - private static MultiUserCasesDialog instance; - - /** - * Gets the instance of the MultiuserCasesDialog. - * - * @return The instance. - */ - static public MultiUserCasesDialog getInstance() { - if(instance == null) { - instance = new MultiUserCasesDialog(); - instance.init(); - } - return instance; - } - - /** - * Constructs a MultiUserCasesDialog object. - */ - private MultiUserCasesDialog() { - super(WindowManager.getDefault().getMainWindow(), - REVIEW_MODE_TITLE, - Dialog.ModalityType.APPLICATION_MODAL); - } - - /** - * Initializes the multi-user cases panel. - */ - private void init() { - getRootPane().registerKeyboardAction( - e -> { - setVisible(false); - }, - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); - - multiUserCasesPanel = new MultiUserCasesPanel(this); - add(multiUserCasesPanel); - pack(); - setResizable(false); - } - - /** - * Set the dialog visibility. When setting it to visible, the contents will - * refresh. - * - * @param value True or false. - */ - @Override - public void setVisible(boolean value) { - if(value) { - multiUserCasesPanel.refresh(); - } - super.setVisible(value); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java deleted file mode 100644 index 07d863bc2be6ddd3f734d8b6d8493fc0556d9561..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2018 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.awt.Cursor; -import java.util.ArrayList; -import java.util.Date; -import java.util.logging.Level; -import javax.swing.JDialog; -import javax.swing.JPanel; -import javax.swing.SortOrder; -import javax.swing.SwingUtilities; -import javax.swing.event.ListSelectionEvent; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableRowSorter; -import org.openide.util.Lookup; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; - -/** - * A panel that allows a user to open cases created by auto ingest. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -final class MultiUserCasesPanel extends JPanel{ - - private static final Logger logger = Logger.getLogger(MultiUserCasesPanel.class.getName()); - private static final long serialVersionUID = 1L; - private final JDialog parentDialog; - private final CaseBrowser caseBrowserPanel; - - /** - * Constructs a panel that allows a user to open cases created by automated - * ingest. - */ - MultiUserCasesPanel(JDialog parentDialog) { - this.parentDialog = parentDialog; - initComponents(); - - caseBrowserPanel = new CaseBrowser(); - caseExplorerScrollPane.add(caseBrowserPanel); - caseExplorerScrollPane.setViewportView(caseBrowserPanel); - /* - * Listen for row selection changes and set button state for the current - * selection. - */ - caseBrowserPanel.addListSelectionListener((ListSelectionEvent e) -> { - setButtons(); - }); - - } - - /** - * Gets the list of cases known to the review mode cases manager and - * refreshes the cases table. - */ - void refresh() { - caseBrowserPanel.refresh(); - } - - /** - * Enables/disables the Open and Show Log buttons based on the case selected - * in the cases table. - */ - void setButtons() { - bnOpen.setEnabled(caseBrowserPanel.isRowSelected()); - } - - /** - * Open a case. - * - * @param caseMetadataFilePath The path to the case metadata file. - */ - private void openCase(String caseMetadataFilePath) { - if (caseMetadataFilePath != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - - StartupWindowProvider.getInstance().close(); - if (parentDialog != null) { - parentDialog.setVisible(false); - } - new Thread(() -> { - try { - Case.openAsCurrentCase(caseMetadataFilePath); - } catch (CaseActionException ex) { - if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS - MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage()); - } - SwingUtilities.invokeLater(() -> { - //GUI changes done back on the EDT - StartupWindowProvider.getInstance().open(); - }); - } finally { - SwingUtilities.invokeLater(() -> { - //GUI changes done back on the EDT - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - } - }).start(); - } - } - - /** - * RowSorter which makes columns whose type is Date to be sorted first in - * Descending order then in Ascending order - */ - private static class RowSorter<M extends DefaultTableModel> extends TableRowSorter<M> { - - RowSorter(M tModel) { - super(tModel); - } - - @Override - public void toggleSortOrder(int column) { - if (!this.getModel().getColumnClass(column).equals(Date.class)) { - super.toggleSortOrder(column); //if it isn't a date column perform the regular sorting - } else { - ArrayList<RowSorter.SortKey> sortKeys = new ArrayList<>(getSortKeys()); - if (sortKeys.isEmpty() || sortKeys.get(0).getColumn() != column) { //sort descending - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else if (sortKeys.get(0).getSortOrder() == SortOrder.ASCENDING) { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.ASCENDING)); - } - setSortKeys(sortKeys); - } - } - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents - private void initComponents() { - - bnOpen = new javax.swing.JButton(); - bnOpenSingleUserCase = new javax.swing.JButton(); - cancelButton = new javax.swing.JButton(); - searchLabel = new javax.swing.JLabel(); - caseExplorerScrollPane = new javax.swing.JScrollPane(); - - setName("Completed Cases"); // NOI18N - setPreferredSize(new java.awt.Dimension(960, 485)); - - org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpen.text")); // NOI18N - bnOpen.setEnabled(false); - bnOpen.setMaximumSize(new java.awt.Dimension(80, 23)); - bnOpen.setMinimumSize(new java.awt.Dimension(80, 23)); - bnOpen.setPreferredSize(new java.awt.Dimension(80, 23)); - bnOpen.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - bnOpenActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpenSingleUserCase.text")); // NOI18N - bnOpenSingleUserCase.setMinimumSize(new java.awt.Dimension(156, 23)); - bnOpenSingleUserCase.setPreferredSize(new java.awt.Dimension(156, 23)); - bnOpenSingleUserCase.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - bnOpenSingleUserCaseActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.cancelButton.text")); // NOI18N - cancelButton.setMaximumSize(new java.awt.Dimension(80, 23)); - cancelButton.setMinimumSize(new java.awt.Dimension(80, 23)); - cancelButton.setPreferredSize(new java.awt.Dimension(80, 23)); - cancelButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cancelButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.searchLabel.text")); // NOI18N - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(caseExplorerScrollPane) - .addGroup(layout.createSequentialGroup() - .addComponent(searchLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(190, 190, 190) - .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addContainerGap()) - ); - - layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnOpen, cancelButton}); - - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(6, 6, 6) - .addComponent(caseExplorerScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap()) - ); - }// </editor-fold>//GEN-END:initComponents - - /** - * Open button action - * - * @param evt -- The event that caused this to be called - */ - private void bnOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenActionPerformed - openCase(caseBrowserPanel.getCasePath()); - }//GEN-LAST:event_bnOpenActionPerformed - - private void bnOpenSingleUserCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenSingleUserCaseActionPerformed - Lookup.getDefault().lookup(CaseOpenAction.class).openCaseSelectionWindow(); - }//GEN-LAST:event_bnOpenSingleUserCaseActionPerformed - - private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed - if (parentDialog != null) { - parentDialog.setVisible(false); - } - }//GEN-LAST:event_cancelButtonActionPerformed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton bnOpen; - private javax.swing.JButton bnOpenSingleUserCase; - private javax.swing.JButton cancelButton; - private javax.swing.JScrollPane caseExplorerScrollPane; - private javax.swing.JLabel searchLabel; - // End of variables declaration//GEN-END:variables -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java new file mode 100644 index 0000000000000000000000000000000000000000..7a8ec7893886de3c452f3d4df6e89fa7594f74e8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java @@ -0,0 +1,74 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * A root node for displaying MultiUserCaseNodes in a NetBeans Explorer View. + */ +final class MultiUserCasesRootNode extends AbstractNode { + + private static final Logger logger = Logger.getLogger(MultiUserCasesRootNode.class.getName()); + + /** + * Constructs a root node for displaying MultiUserCaseNodes in a NetBeans + * Explorer View. + * + * @param case A list of coordination service node data objects representing + * multi-user cases. + */ + MultiUserCasesRootNode() { + super(Children.create(new MultiUserCasesRootNodeChildren(), true)); + } + + /** + * A child factory for creating child nodes for a MultiUserCasesRootNode. + * The child nodes are of type MultiUserCaseNode. The node keys are of type + * CaseNodeData. + */ + private static class MultiUserCasesRootNodeChildren extends ChildFactory<CaseNodeData> { + + @Override + protected boolean createKeys(List<CaseNodeData> keys) { + try { + List<CaseNodeData> caseNodeData = MultiUserCaseNodeDataCollector.getNodeData(); + keys.addAll(caseNodeData); + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.SEVERE, "Failed to get case node data from coodination service", ex); + } + return true; + } + + @Override + protected Node createNodeForKey(CaseNodeData key) { + return new MultiUserCaseNode(key); + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java deleted file mode 100644 index 1efbffe19011d4b7473919ac46b55f9435b50a1c..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2017-2018 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.awt.Desktop; -import java.awt.event.ActionEvent; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.NodeProperty; - -/** - * A root node containing child nodes of the multi user cases - */ -final class MultiUserNode extends AbstractNode { - - @Messages({"CaseNode.column.name=Name", - "CaseNode.column.createdTime=Created Time", - "CaseNode.column.metadataFilePath=Path"}) - private static final Logger LOGGER = Logger.getLogger(MultiUserNode.class.getName()); - private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; - - /** - * Provides a root node with children which each represent a case. - * - * @param caseList the list of CaseMetadata objects representing the cases - */ - MultiUserNode(List<CaseMetadata> caseList) { - super(Children.create(new MultiUserNodeChildren(caseList), true)); - } - - static class MultiUserNodeChildren extends ChildFactory<CaseMetadata> { - - private final List<CaseMetadata> caseList; - - MultiUserNodeChildren(List<CaseMetadata> caseList) { - this.caseList = caseList; - } - - @Override - protected boolean createKeys(List<CaseMetadata> list) { - if (caseList != null && caseList.size() > 0) { - list.addAll(caseList); - } - return true; - } - - @Override - protected Node createNodeForKey(CaseMetadata key) { - return new MultiUserCaseNode(key); - } - - } - - /** - * A node which represents a single multi user case. - */ - static final class MultiUserCaseNode extends AbstractNode { - - private final String caseName; - private final String caseCreatedDate; - private final String caseMetadataFilePath; - private final Path caseLogFilePath; - - MultiUserCaseNode(CaseMetadata multiUserCase) { - super(Children.LEAF); - caseName = multiUserCase.getCaseDisplayName(); - caseCreatedDate = multiUserCase.getCreatedDate(); - super.setName(caseName); - setName(caseName); - setDisplayName(caseName); - caseMetadataFilePath = multiUserCase.getFilePath().toString(); - caseLogFilePath = Paths.get(multiUserCase.getCaseDirectory(), LOG_FILE_NAME); - } - - /** - * Returns action to open the Case represented by this node - * @return an action which will open the current case - */ - @Override - public Action getPreferredAction() { - return new OpenMultiUserCaseAction(caseMetadataFilePath); - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), - caseName)); - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), - caseCreatedDate)); - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), - caseMetadataFilePath)); - return sheet; - } - - @Override - public Action[] getActions(boolean context) { - List<Action> actions = new ArrayList<>(); - actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); //open case context menu option - actions.add(new OpenCaseLogAction(caseLogFilePath)); - return actions.toArray(new Action[actions.size()]); - } - } - - @Messages({"MultiUserNode.OpenMultiUserCaseAction.text=Open Case"}) - /** - * An action that opens the specified case and hides the multi user case - * panel. - */ - private static final class OpenMultiUserCaseAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - - private final String caseMetadataFilePath; - - OpenMultiUserCaseAction(String path) { - super(Bundle.MultiUserNode_OpenMultiUserCaseAction_text()); - caseMetadataFilePath = path; - } - - @Override - public void actionPerformed(ActionEvent e) { - StartupWindowProvider.getInstance().close(); - MultiUserCasesDialog.getInstance().setVisible(false); - new Thread( - () -> { - try { - Case.openAsCurrentCase(caseMetadataFilePath); - } catch (CaseActionException ex) { - if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS - MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage()); - } - SwingUtilities.invokeLater(() -> { - //GUI changes done back on the EDT - StartupWindowProvider.getInstance().open(); - MultiUserCasesDialog.getInstance().setVisible(true); - }); - } - } - ).start(); - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); //To change body of generated methods, choose Tools | Templates. - } - } - - @Messages({"MultiUserNode.OpenCaseLogAction.text=Open Log File"}) - /** - * An action that opens the specified case and hides the multi user case - * panel. - */ - private static final class OpenCaseLogAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - - private final Path pathToLog; - - OpenCaseLogAction(Path caseLogFilePath) { - super(Bundle.MultiUserNode_OpenCaseLogAction_text()); - pathToLog = caseLogFilePath; - this.setEnabled(caseLogFilePath != null && caseLogFilePath.toFile().exists()); - } - - @Override - public void actionPerformed(ActionEvent e) { - - if (pathToLog != null) { - try { - if (pathToLog.toFile().exists()) { - Desktop.getDesktop().edit(pathToLog.toFile()); - - } else { - JOptionPane.showMessageDialog(MultiUserCasesDialog.getInstance(), org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.cannotFindLog"), - org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE); - } - } catch (IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Error attempting to open case auto ingest log file %s", pathToLog), ex); - JOptionPane.showMessageDialog(MultiUserCasesDialog.getInstance(), - org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.cannotOpenLog"), - org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.unableToShowLogFile"), - JOptionPane.PLAIN_MESSAGE); - } - } - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); //To change body of generated methods, choose Tools | Templates. - } - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java new file mode 100755 index 0000000000000000000000000000000000000000..165af8776c44e158c001234bdd9529f0b1eb0dd2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java @@ -0,0 +1,83 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Desktop; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +/** + * An action that opens a case auto ingest log given the coordination service + * node data for the case. + */ +final class OpenCaseAutoIngestLogAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(OpenCaseAutoIngestLogAction.class.getName()); + private static final String CASE_AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; + private final Path caseAutoIngestLogFilePath; + + /** + * Constructs an action that opens a case auto ingest log given the + * coordination service node data for the case. + * + * @param caseNodeData The coordination service node data for the case. + */ + @NbBundle.Messages({ + "OpenCaseAutoIngestLogAction.menuItemText=Open Auto Ingest Log File" + }) + OpenCaseAutoIngestLogAction(CaseNodeData caseNodeData) { + super(Bundle.OpenCaseAutoIngestLogAction_menuItemText()); + this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME); + this.setEnabled(caseAutoIngestLogFilePath.toFile().exists()); + } + + @NbBundle.Messages({ + "OpenCaseAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.", + "OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details." + }) + @Override + public void actionPerformed(ActionEvent event) { + try { + if (caseAutoIngestLogFilePath.toFile().exists()) { + Desktop.getDesktop().edit(caseAutoIngestLogFilePath.toFile()); + } else { + MessageNotifyUtil.Message.error(Bundle.OpenCaseAutoIngestLogAction_deletedLogErrorMsg()); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error opening case auto ingest log file at %s", caseAutoIngestLogFilePath), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.OpenCaseAutoIngestLogAction_logOpenFailedErrorMsg()); + } + } + + @Override + public OpenCaseAutoIngestLogAction clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java new file mode 100755 index 0000000000000000000000000000000000000000..5814d6b12b43cc3a0299e2c44d1db9af72def43f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.SwingUtilities; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +/** + * An action that opens a multi-user case and hides the open multi-user case + * dialog given the coordination service node data for the case. + */ +final class OpenMultiUserCaseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(OpenMultiUserCaseAction.class.getName()); + private final CaseNodeData caseNodeData; + + /** + * Constructs an action that opens a multi-user case and hides the open + * multi-user case dialog given the coordination service node data for the + * case. + */ + @NbBundle.Messages({ + "OpenMultiUserCaseAction.menuItemText=Open Case" + }) + OpenMultiUserCaseAction(CaseNodeData caseNodeData) { + super(Bundle.OpenMultiUserCaseAction_menuItemText()); + this.caseNodeData = caseNodeData; + } + + @NbBundle.Messages({ + "# {0} - caseErrorMessage", "OpenMultiUserCaseAction.caseOpeningErrorErrorMsg=Failed to open case: {0}" + }) + @Override + public void actionPerformed(ActionEvent event) { + StartupWindowProvider.getInstance().close(); + OpenMultiUserCaseDialog.getInstance().setVisible(false); + new Thread(() -> { + String caseMetadataFilePath = 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()) { + caseMetadataFilePath = file.getPath(); + } + } + } + if (caseMetadataFilePath != null) { + try { + Case.openAsCurrentCase(caseMetadataFilePath); + } catch (CaseActionException ex) { + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS + } + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Message.error(Bundle.OpenMultiUserCaseAction_caseOpeningErrorErrorMsg(ex.getLocalizedMessage())); + StartupWindowProvider.getInstance().open(); + OpenMultiUserCaseDialog.getInstance().setVisible(true); + }); + } + } else { + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Message.error(Bundle.OpenMultiUserCaseAction_caseOpeningErrorErrorMsg("Could not locate case metadata file.")); + }); + } + }).start(); + } + + @Override + public OpenMultiUserCaseAction clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..c81ceabd7c6bc7580f3075ab83af53865a42c0ea --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java @@ -0,0 +1,84 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Dialog; +import javax.swing.JDialog; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; + +/** + * A singleton JDialog that allows a user to open a multi-user case. + */ +final class OpenMultiUserCaseDialog extends JDialog { + + private static final long serialVersionUID = 1L; + private static OpenMultiUserCaseDialog instance; + private static OpenMultiUserCasePanel multiUserCasesPanel; + + /** + * Gets the singleton JDialog that allows a user to open a multi-user case. + * + * @return The singleton JDialog instance. + */ + public synchronized static OpenMultiUserCaseDialog getInstance() { + if (instance == null) { + instance = new OpenMultiUserCaseDialog(); + instance.init(); + } + return instance; + } + + /** + * Constructs a singleton JDialog that allows a user to open a multi-user + * case. + */ + @NbBundle.Messages({ + "OpenMultiUserCaseDialog.title=Open Multi-User Case" + }) + private OpenMultiUserCaseDialog() { + super(WindowManager.getDefault().getMainWindow(), Bundle.OpenMultiUserCaseDialog_title(), Dialog.ModalityType.APPLICATION_MODAL); + } + + /** + * Registers a keyboard action to hide the dialog when the escape key is + * pressed and adds a OpenMultiUserCasePanel child component. + */ + private void init() { + multiUserCasesPanel = new OpenMultiUserCasePanel(this); + add(multiUserCasesPanel); + pack(); + setResizable(false); + } + + /** + * Sets the dialog visibility. When made visible, the dialog refreshes the + * display of its OpenMultiUserCasePanel child component. + * + * @param makeVisible True or false. + */ + @Override + public void setVisible(boolean makeVisible) { + if (makeVisible) { + multiUserCasesPanel.refreshDisplay(); + } + super.setVisible(makeVisible); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form similarity index 71% rename from Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form rename to Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form index 71810b738739972097df89b9a9022cc8835e4f98..30996651a699d90feebfa5d5258c7e774b1cf0b4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form @@ -28,12 +28,12 @@ <Component id="caseExplorerScrollPane" max="32767" attributes="0"/> <Group type="102" attributes="0"> <Component id="searchLabel" min="-2" max="-2" attributes="0"/> - <EmptySpace max="32767" attributes="0"/> - <Component id="bnOpenSingleUserCase" min="-2" pref="192" max="-2" attributes="0"/> - <EmptySpace min="-2" pref="190" max="-2" attributes="0"/> - <Component id="bnOpen" linkSize="1" min="-2" max="-2" attributes="0"/> + <EmptySpace min="-2" pref="32" max="-2" attributes="0"/> + <Component id="openSingleUserCaseButton" linkSize="10" min="-2" pref="172" max="-2" attributes="0"/> + <EmptySpace pref="96" max="32767" attributes="0"/> + <Component id="openSelectedCaseButton" linkSize="10" min="-2" pref="160" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/> - <Component id="cancelButton" linkSize="1" min="-2" max="-2" attributes="0"/> + <Component id="cancelButton" linkSize="10" min="-2" max="-2" attributes="0"/> </Group> </Group> <EmptySpace max="-2" attributes="0"/> @@ -47,10 +47,10 @@ <Component id="caseExplorerScrollPane" min="-2" pref="450" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/> <Group type="103" groupAlignment="3" attributes="0"> - <Component id="cancelButton" alignment="3" min="-2" max="-2" attributes="0"/> - <Component id="bnOpen" alignment="3" min="-2" max="-2" attributes="0"/> - <Component id="bnOpenSingleUserCase" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="cancelButton" linkSize="7" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="openSingleUserCaseButton" linkSize="7" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="searchLabel" alignment="3" max="32767" attributes="0"/> + <Component id="openSelectedCaseButton" linkSize="7" alignment="3" min="-2" max="-2" attributes="0"/> </Group> <EmptySpace max="-2" attributes="0"/> </Group> @@ -58,30 +58,10 @@ </DimensionLayout> </Layout> <SubComponents> - <Component class="javax.swing.JButton" name="bnOpen"> + <Component class="javax.swing.JButton" name="openSingleUserCaseButton"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.bnOpen.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - <Property name="enabled" type="boolean" value="false"/> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[80, 23]"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[80, 23]"/> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[80, 23]"/> - </Property> - </Properties> - <Events> - <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bnOpenActionPerformed"/> - </Events> - </Component> - <Component class="javax.swing.JButton" name="bnOpenSingleUserCase"> - <Properties> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.bnOpenSingleUserCase.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="OpenMultiUserCasePanel.openSingleUserCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Dimension value="[156, 23]"/> @@ -91,13 +71,13 @@ </Property> </Properties> <Events> - <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bnOpenSingleUserCaseActionPerformed"/> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openSingleUserCaseButtonActionPerformed"/> </Events> </Component> <Component class="javax.swing.JButton" name="cancelButton"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="OpenMultiUserCasePanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Dimension value="[80, 23]"/> @@ -116,7 +96,7 @@ <Component class="javax.swing.JLabel" name="searchLabel"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.searchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="OpenMultiUserCasePanel.searchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> </Component> @@ -124,5 +104,15 @@ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> </Container> + <Component class="javax.swing.JButton" name="openSelectedCaseButton"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="OpenMultiUserCasePanel.openSelectedCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openSelectedCaseButtonActionPerformed"/> + </Events> + </Component> </SubComponents> </Form> diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java new file mode 100644 index 0000000000000000000000000000000000000000..5159cb8163b0f44f2691f62886ff63692b80af20 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java @@ -0,0 +1,194 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.event.ListSelectionEvent; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; + +/** + * A JPanel that allows a user to open a multi-user case. + */ +@SuppressWarnings("PMD.SingularField") // Matisse-generated UI widgets cause lots of false positives +final class OpenMultiUserCasePanel extends JPanel { + + private static final long serialVersionUID = 1L; + private final JDialog parentDialog; + private final MultiUserCasesBrowserPanel caseBrowserPanel; + + /** + * Constructs a JPanel that allows a user to open a multi-user case. + * + * @param parentDialog The parent dialog of the panel, may be null. If + * provided, the dialog is hidden when this poanel's + * cancel button is pressed. + */ + OpenMultiUserCasePanel(JDialog parentDialog) { + this.parentDialog = parentDialog; + initComponents(); // Machine generated code + caseBrowserPanel = new MultiUserCasesBrowserPanel(); + caseExplorerScrollPane.add(caseBrowserPanel); + caseExplorerScrollPane.setViewportView(caseBrowserPanel); + openSelectedCaseButton.setEnabled(false); + caseBrowserPanel.addListSelectionListener((ListSelectionEvent event) -> { + openSelectedCaseButton.setEnabled(caseBrowserPanel.getExplorerManager().getSelectedNodes().length > 0); + }); + } + + /** + * Refreshes the child component that displays the multi-user cases known to + * the coordination service.. + */ + void refreshDisplay() { + caseBrowserPanel.refreshCases(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + openSingleUserCaseButton = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + searchLabel = new javax.swing.JLabel(); + caseExplorerScrollPane = new javax.swing.JScrollPane(); + openSelectedCaseButton = new javax.swing.JButton(); + + setName("Completed Cases"); // NOI18N + setPreferredSize(new java.awt.Dimension(960, 485)); + + org.openide.awt.Mnemonics.setLocalizedText(openSingleUserCaseButton, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.openSingleUserCaseButton.text")); // NOI18N + openSingleUserCaseButton.setMinimumSize(new java.awt.Dimension(156, 23)); + openSingleUserCaseButton.setPreferredSize(new java.awt.Dimension(156, 23)); + openSingleUserCaseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + openSingleUserCaseButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.cancelButton.text")); // NOI18N + cancelButton.setMaximumSize(new java.awt.Dimension(80, 23)); + cancelButton.setMinimumSize(new java.awt.Dimension(80, 23)); + cancelButton.setPreferredSize(new java.awt.Dimension(80, 23)); + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.searchLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(openSelectedCaseButton, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.openSelectedCaseButton.text")); // NOI18N + openSelectedCaseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + openSelectedCaseButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(caseExplorerScrollPane) + .addGroup(layout.createSequentialGroup() + .addComponent(searchLabel) + .addGap(32, 32, 32) + .addComponent(openSingleUserCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 172, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 96, Short.MAX_VALUE) + .addComponent(openSelectedCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 160, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, openSelectedCaseButton, openSingleUserCaseButton}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addComponent(caseExplorerScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(openSingleUserCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(openSelectedCaseButton)) + .addContainerGap()) + ); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {cancelButton, openSelectedCaseButton, openSingleUserCaseButton}); + + }// </editor-fold>//GEN-END:initComponents + + /** + * Opens the standard open single user case window. + * + * @param evt An ActionEvent, unused. + */ + private void openSingleUserCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openSingleUserCaseButtonActionPerformed + Lookup.getDefault().lookup(CaseOpenAction.class).openCaseSelectionWindow(); + }//GEN-LAST:event_openSingleUserCaseButtonActionPerformed + + /** + * Closes the parent open multi-user case dialog. + * + * @param evt An ActionEvent, unused. + */ + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + if (parentDialog != null) { + parentDialog.setVisible(false); + } + }//GEN-LAST:event_cancelButtonActionPerformed + + /** + * Opens the multi-user case selected in the child multi-user case browser + * panel. + * + * @param evt An ActionEvent, unused. + */ + private void openSelectedCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openSelectedCaseButtonActionPerformed + ExplorerManager explorerManager = caseBrowserPanel.getExplorerManager(); + Node[] selectedNodes = explorerManager.getSelectedNodes(); + if (selectedNodes.length > 0 && selectedNodes[0] instanceof MultiUserCaseNode) { + MultiUserCaseNode caseNode = (MultiUserCaseNode) selectedNodes[0]; + CaseNodeData nodeData = caseNode.getCaseNodeData(); + new OpenMultiUserCaseAction(nodeData).actionPerformed(evt); + } + }//GEN-LAST:event_openSelectedCaseButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton cancelButton; + private javax.swing.JScrollPane caseExplorerScrollPane; + private javax.swing.JButton openSelectedCaseButton; + private javax.swing.JButton openSingleUserCaseButton; + private javax.swing.JLabel searchLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java index 0b220e04b27e62ce40c3582c3c755f963857c526..2fd1b4cf04d7a2482683e6065c991a3464b0dea8 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2017-2019 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,69 +18,115 @@ */ package org.sleuthkit.autopsy.coordinationservice; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.Date; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; /** - * An object that converts case data for a case directory coordination service + * An object that converts data for a case directory lock coordination service * node to and from byte arrays. */ public final class CaseNodeData { - private static final int CURRENT_VERSION = 0; - - private int version; + private static final int CURRENT_VERSION = 1; + + /* + * Version 0 fields. + */ + private final int version; private boolean errorsOccurred; + /* + * Version 1 fields. + */ + private Path directory; + private Date createDate; + private Date lastAccessDate; + private String name; + private String displayName; + private short deletedItemFlags; + /** - * Gets the current version of the case directory coordination service node - * data. + * Gets the current version of the case directory lock coordination service + * node data. * * @return The version number. */ public static int getCurrentVersion() { return CaseNodeData.CURRENT_VERSION; } - + + /** + * Uses a CaseMetadata object to construct an object that converts data for + * a case directory lock coordination service node to and from byte arrays. + * + * @param metadata The case meta data. + * + * @throws java.text.ParseException If there is an error parsing dates from + * string representations of dates in the + * meta data. + */ + public CaseNodeData(CaseMetadata metadata) throws ParseException { + this.version = CURRENT_VERSION; + this.errorsOccurred = false; + this.directory = Paths.get(metadata.getCaseDirectory()); + this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate()); + this.lastAccessDate = new Date(); + this.name = metadata.getCaseName(); + this.displayName = metadata.getCaseDisplayName(); + this.deletedItemFlags = 0; + } + /** * Uses coordination service node data to construct an object that converts - * case data for a case directory coordination service node to and from byte + * data for a case directory lock coordination service node to and from byte * arrays. * * @param nodeData The raw bytes received from the coordination service. - * - * @throws InvalidDataException If the node data buffer is smaller than - * expected. - */ - public CaseNodeData(byte[] nodeData) throws InvalidDataException { - if(nodeData == null || nodeData.length == 0) { - this.version = CURRENT_VERSION; - this.errorsOccurred = false; + * + * @throws IOException If there is an error reading the node data. + */ + public CaseNodeData(byte[] nodeData) throws IOException { + if (nodeData == null || nodeData.length == 0) { + throw new IOException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array"); + } + DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(nodeData)); + this.version = inputStream.readInt(); + if (this.version > 0) { + this.errorsOccurred = inputStream.readBoolean(); } else { - /* - * Get fields from node data. - */ - ByteBuffer buffer = ByteBuffer.wrap(nodeData); - try { - if (buffer.hasRemaining()) { - this.version = buffer.getInt(); - - /* - * Flags bit format: 76543210 - * 0-6 --> reserved for future use - * 7 --> errorsOccurred - */ - byte flags = buffer.get(); - this.errorsOccurred = (flags < 0); - } - } catch (BufferUnderflowException ex) { - throw new InvalidDataException("Node data is incomplete", ex); - } + short legacyErrorsOccurred = inputStream.readByte(); + this.errorsOccurred = (legacyErrorsOccurred < 0); } + if (this.version > 0) { + this.directory = Paths.get(inputStream.readUTF()); + this.createDate = new Date(inputStream.readLong()); + this.lastAccessDate = new Date(inputStream.readLong()); + this.name = inputStream.readUTF(); + this.displayName = inputStream.readUTF(); + this.deletedItemFlags = inputStream.readShort(); + } + } + + /** + * Gets the node data version number of this node. + * + * @return The version number. + */ + public int getVersion() { + return this.version; } /** - * Gets whether or not any errors occurred during the processing of the job. + * Gets whether or not any errors occurred during the processing of any auto + * ingest job for the case represented by this node data. * * @return True or false. */ @@ -89,7 +135,8 @@ public boolean getErrorsOccurred() { } /** - * Sets whether or not any errors occurred during the processing of job. + * Sets whether or not any errors occurred during the processing of any auto + * ingest job for the case represented by this node data. * * @param errorsOccurred True or false. */ @@ -98,32 +145,121 @@ public void setErrorsOccurred(boolean errorsOccurred) { } /** - * Gets the node data version number. + * Gets the path of the case directory of the case represented by this node + * data. * - * @return The version number. + * @return The case directory path. */ - public int getVersion() { - return this.version; + public Path getDirectory() { + return this.directory; + } + + /** + * Sets the path of the case directory of the case represented by this node + * data. + * + * @param caseDirectory The case directory path. + */ + public void setDirectory(Path caseDirectory) { + this.directory = caseDirectory; + } + + /** + * Gets the date the case represented by this node data was created. + * + * @return The create date. + */ + public Date getCreateDate() { + return new Date(this.createDate.getTime()); + } + + /** + * Sets the date the case represented by this node data was created. + * + * @param createDate The create date. + */ + public void setCreateDate(Date createDate) { + this.createDate = new Date(createDate.getTime()); + } + + /** + * Gets the date the case represented by this node data last accessed. + * + * @return The last access date. + */ + public Date getLastAccessDate() { + return new Date(this.lastAccessDate.getTime()); + } + + /** + * Sets the date the case represented by this node data was last accessed. + * + * @param lastAccessDate The last access date. + */ + public void setLastAccessDate(Date lastAccessDate) { + this.lastAccessDate = new Date(lastAccessDate.getTime()); + } + + /** + * Gets the unique and immutable (user cannot change it) name of the case + * represented by this node data. + * + * @return The case name. + */ + public String getName() { + return this.name; + } + + /** + * Sets the unique and immutable (user cannot change it) name of the case + * represented by this node data. + * + * @param name The case name. + */ + public void setName(String name) { + this.name = name; } - + + /** + * Gets the display name of the case represented by this node data. + * + * @return The case display name. + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * Sets the display name of the case represented by this node data. + * + * @param displayName The case display name. + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + /** * Gets the node data as a byte array that can be sent to the coordination * service. * * @return The node data as a byte array. + * + * @throws IOException If there is an error writing the node data. */ - public byte[] toArray() { - ByteBuffer buffer = ByteBuffer.allocate(5); - - buffer.putInt(this.version); - buffer.put((byte)(this.errorsOccurred ? 0x80 : 0)); - - // Prepare the array - byte[] array = new byte[buffer.position()]; - buffer.rewind(); - buffer.get(array, 0, array.length); - - return array; + public byte[] toArray() throws IOException { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + DataOutputStream outputStream = new DataOutputStream(byteStream); + outputStream.writeInt(this.version); + outputStream.writeBoolean(this.errorsOccurred); + outputStream.writeUTF(this.directory.toString()); + outputStream.writeLong(this.createDate.getTime()); + outputStream.writeLong(this.lastAccessDate.getTime()); + outputStream.writeUTF(this.name); + outputStream.writeUTF(this.displayName); + outputStream.writeShort(this.deletedItemFlags); + outputStream.flush(); + byteStream.flush(); + return byteStream.toByteArray(); } public final static class InvalidDataException extends Exception { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index ea4f4d3136d922a6532a5b2b03e056fcb5c48698..f03c7b01cff5278930085b9961a298409e7865bc 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -380,7 +380,7 @@ public boolean isSupported(Node selectedNode) { public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (selectionListener == null) { - this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup + this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); } if (rootNodeChildren != null) { rootNodeChildren.cancelLoadingThumbnails(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 1a1a384745536f405689f3641f5e0026a7b40772..39b229f85643d8a468d06bb593d888fa179275c1 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1135,9 +1135,9 @@ void updateCoordinationServiceManifestNode(AutoIngestJob job) throws Coordinatio * * @throws CoordinationService.CoordinationServiceException * @throws InterruptedException - * @throws CaseNodeData.InvalidDataException + * @throws IOException */ - private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws CoordinationServiceException, InterruptedException, CaseNodeData.InvalidDataException { + private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws CoordinationServiceException, InterruptedException, IOException { CaseNodeData caseNodeData = new CaseNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString())); caseNodeData.setErrorsOccurred(true); byte[] rawData = caseNodeData.toArray(); @@ -1517,8 +1517,8 @@ private void doRecoveryIfCrashed(Manifest manifest, AutoIngestJobNodeData jobNod job.setErrorsOccurred(true); try { setCaseNodeDataErrorsOccurred(caseDirectoryPath); - } catch (CaseNodeData.InvalidDataException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to get case node data for %s", caseDirectoryPath), ex); + } catch (IOException ex) { + sysLogger.log(Level.SEVERE, String.format("Error attempting to set error flag in case node data for %s", caseDirectoryPath), ex); } } else { job.setErrorsOccurred(false); @@ -2012,7 +2012,7 @@ private void waitForInputDirScan() throws InterruptedException { * for an auto ingest * job. */ - private void processJobs() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobNodeData.InvalidDataException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void processJobs() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobNodeData.InvalidDataException, IOException, JobMetricsCollectionException { sysLogger.log(Level.INFO, "Started processing pending jobs queue"); Lock manifestLock = JobProcessingTask.this.dequeueAndLockNextJob(); while (null != manifestLock) { @@ -2213,7 +2213,7 @@ private Lock dequeueAndLockNextJob(boolean enforceMaxJobsPerCase) throws Coordin * for an auto ingest * job. */ - private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, JobMetricsCollectionException { Path manifestPath = currentJob.getManifest().getFilePath(); sysLogger.log(Level.INFO, "Started processing of {0}", manifestPath); currentJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.PROCESSING); @@ -2301,7 +2301,7 @@ private void processJob() throws CoordinationServiceException, SharedConfigurati * to collect metrics for an * auto ingest job. */ - private void attemptJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void attemptJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, JobMetricsCollectionException { updateConfiguration(); if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) { return; @@ -2481,7 +2481,7 @@ private Case openCase() throws CoordinationServiceException, CaseManagementExcep * collect metrics for an auto * ingest job. */ - private void runIngestForJob(Case caseForJob) throws CoordinationServiceException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void runIngestForJob(Case caseForJob) throws CoordinationServiceException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, JobMetricsCollectionException { try { if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) { return; @@ -2520,7 +2520,7 @@ private void runIngestForJob(Case caseForJob) throws CoordinationServiceExceptio * collect metrics for an auto * ingest job. */ - private void ingestDataSource(Case caseForJob) throws AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, CoordinationServiceException, JobMetricsCollectionException { + private void ingestDataSource(Case caseForJob) throws AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, CoordinationServiceException, JobMetricsCollectionException { if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) { return; } @@ -2577,7 +2577,7 @@ private void ingestDataSource(Case caseForJob) throws AnalysisStartupException, * interrupted while blocked, i.e., * if auto ingest is shutting down. */ - private AutoIngestDataSource identifyDataSource() throws AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private AutoIngestDataSource identifyDataSource() throws AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Identifying data source for {0} ", manifestPath); @@ -2611,7 +2611,7 @@ private AutoIngestDataSource identifyDataSource() throws AutoIngestJobLoggerExce * while blocked, i.e., if auto * ingest is shutting down. */ - private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException, AutoIngestJobLoggerException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException, AutoIngestJobLoggerException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Adding data source for {0} ", manifestPath); @@ -2693,7 +2693,7 @@ private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSo * while blocked, i.e., if auto * ingest is shutting down. */ - private void logDataSourceProcessorResult(AutoIngestDataSource dataSource) throws AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void logDataSourceProcessorResult(AutoIngestDataSource dataSource) throws AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); @@ -2755,7 +2755,7 @@ private void logDataSourceProcessorResult(AutoIngestDataSource dataSource) throw * while blocked, i.e., if auto * ingest is shutting down. */ - private void analyze(AutoIngestDataSource dataSource) throws AnalysisStartupException, AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void analyze(AutoIngestDataSource dataSource) throws AnalysisStartupException, AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Starting ingest modules analysis for {0} ", manifestPath); @@ -2893,7 +2893,7 @@ private void collectMetrics(SleuthkitCase caseDb, AutoIngestDataSource dataSourc * while blocked, i.e., if auto * ingest is shutting down. */ - private void exportFiles(AutoIngestDataSource dataSource) throws FileExportException, AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void exportFiles(AutoIngestDataSource dataSource) throws FileExportException, AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Exporting files for {0}", manifestPath);