diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 2232085c5bb2724c28d5a72a855528456192319a..9a711047a8b714f0d7ad7c5480aa13480bb77902 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -108,11 +108,11 @@ AddImageWizardIterator.stepXofN=Step {0} of {1} AddLocalFilesTask.localFileAdd.progress.text=Adding\: {0}/{1} Case.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open\! Case.create.exception.msg=Error creating a case\: {0} in dir {1} -Case.databaseConnectionInfo.error.msg=Error accessing case database connection info +Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-user. Case.open.exception.blankCase.msg=Case name is blank. Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the database with the following path has been made\:\n {0} Case.open.msgDlg.updated.title=Case Database Schema Update -Case.open.exception.checkFile.msg=Check that you selected the correct case file (usually with {0} extension) +Case.open.exception.checkFile.msg=Case file must have {0} extension. Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-user. Case.checkImgExist.confDlg.doesntExist.msg={0} has detected that one of the images associated with \n\ this case are missing. Would you like to search for them now?\n\ @@ -139,6 +139,9 @@ 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 +Case.metaDataFileCorrupt.exception.msg=The case metadata file (.aut) is corrupted. +Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk. +Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1} CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\ Case Name\: {0}\n\ Case Directory\: {1} @@ -208,14 +211,11 @@ NewCaseWizardPanel1.validate.errMsg.cantCreateDir=Error\: Could not create direc NewCaseWizardPanel1.validate.errMsg.invalidBaseDir.msg=ERROR\: The Base Directory that you entered is not valid.\nPlease enter a valid Base Directory. NewCaseWizardPanel1.createDir.errMsg.cantCreateDir.msg=ERROR\: Could not create the case directory. \nPlease enter a valid Case Name and Directory. NewCaseWizardPanel2.validate.errCreateCase.msg=Error creating case -OpenRecentCasePanel.openCase.msgDlg.caseDoesntExist.msg=Error\: Case {0} does not exist. -OpenRecentCasePanel.openCase.msgDlg.err=Error OpenRecentCasePanel.colName.caseName=Case Name OpenRecentCasePanel.colName.path=Path RecentCases.exception.caseIdxOutOfRange.msg=Recent case index {0} is out of range. RecentCases.getName.text=Clear Recent Cases -RecentItems.openRecentCase.msgDlg.text=Error\: Case {0} does not exist. -RecentItems.openRecentCase.msgDlg.err=Error +RecentItems.openRecentCase.msgDlg.text=Case {0} no longer exists. StartupWindow.title.text=Welcome UpdateRecentCases.menuItem.clearRecentCases.text=Clear Recent Cases UpdateRecentCases.menuItem.empty=-Empty- @@ -240,17 +240,14 @@ NewCaseVisualPanel1.caseParentDirWarningLabel.text= NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user NewCaseVisualPanel1.caseTypeLabel.text=Case Type: -Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk. -Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1} CasePropertiesForm.lbDbType.text=Case Type: CasePropertiesForm.tbDbType.text= CasePropertiesForm.lbDbName.text=Database Name: CasePropertiesForm.tbDbName.text= -CaseExceptionWarning.CheckMultiUserOptions=Check Multi-user options. SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user! SingleUserCaseConverter.NonUniqueDatabaseName=Database name not unique. SingleUserCaseConverter.UnableToCopySourceImages=Unable to copy source images SingleUserCaseConverter.CanNotOpenDatabase=Unable to open database CloseCaseWhileIngesting.Warning=Ingest is running. Are you sure you want to close the case? -CloseCaseWhileIngesting.Warning.title=Warning\: This will close the current case +CloseCaseWhileIngesting.Warning.title=Warning\: This will close the current case \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 6895817f35325e24e3e90b03e9e6f7cbdd613194..e61c8bf1cb9b9865480159be847acebcaae6111f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -192,14 +192,11 @@ NewCaseWizardPanel1.validate.errMsg.cantCreateDir=\u30a8\u30e9\u30fc\uff1a\u30c7 NewCaseWizardPanel1.validate.errMsg.invalidBaseDir.msg=\u30a8\u30e9\u30fc\uff1a\u5165\u529b\u3057\u305f\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306f\u6709\u52b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002\n\u6709\u52b9\u306a\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002 NewCaseWizardPanel1.createDir.errMsg.cantCreateDir.msg=\u30a8\u30e9\u30fc\uff1a\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\u6709\u52b9\u306a\u30b1\u30fc\u30b9\u540d\u304a\u3088\u3073\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002 NewCaseWizardPanel2.validate.errCreateCase.msg=\u30b1\u30fc\u30b9\u4f5c\u6210\u30a8\u30e9\u30fc -OpenRecentCasePanel.openCase.msgDlg.caseDoesntExist.msg=\u30a8\u30e9\u30fc\uff1a\u30b1\u30fc\u30b9{0}\u306f\u5b58\u5728\u3057\u307e\u305b\u3093\u3002 -OpenRecentCasePanel.openCase.msgDlg.err=\u30a8\u30e9\u30fc OpenRecentCasePanel.colName.caseName=\u30b1\u30fc\u30b9\u540d OpenRecentCasePanel.colName.path=\u30d1\u30b9 RecentCases.exception.caseIdxOutOfRange.msg=\u6700\u8fd1\u306e\u30b1\u30fc\u30b9\u30a4\u30f3\u30c7\u30c3\u30af\u30b9{0}\u306f\u7bc4\u56f2\u5916\u3067\u3059\u3002 RecentCases.getName.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u30af\u30ea\u30a2 RecentItems.openRecentCase.msgDlg.text=\u30a8\u30e9\u30fc\uff1a\u30b1\u30fc\u30b9{0}\u306f\u5b58\u5728\u3057\u307e\u305b\u3093\u3002 -RecentItems.openRecentCase.msgDlg.err=\u30a8\u30e9\u30fc StartupWindow.title.text=\u3088\u3046\u3053\u305d UpdateRecentCases.menuItem.clearRecentCases.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u30af\u30ea\u30a2 UpdateRecentCases.menuItem.empty=-\u7a7a\u767d- diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index fbf53a99fc131d588696b61a0c67330a96af0f0b..b9d67f8746ddd0e3573e8b33eaec97d993721029 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -384,7 +384,8 @@ private static void changeCase(Case newCase) { @Override public void receiveError(String context, String errorMessage) { - /* NOTE: We are accessing tskErrorReporter from two different threads. + /* + * NOTE: We are accessing tskErrorReporter from two different threads. * This is ok as long as we only read the value of tskErrorReporter * because tskErrorReporter is declared as volatile. */ @@ -398,63 +399,87 @@ AddImageProcess makeAddImageProcess(String timezone, boolean processUnallocSpace } /** - * Creates a new case (create the XML config file and database). Overload - * for API consistency, defaults to a single-user case. + * Creates a single-user new case. * - * @param caseDir The directory to store case data in. Will be created if - * it doesn't already exist. If it exists, it should have - * all of the needed sub dirs that createCaseDirectory() - * will create. - * @param caseName the name of case - * @param caseNumber the case number - * @param examiner the examiner for this case + * @param caseDir The full path of the case directory. It will be created + * if it doesn't already exist; if it exists, it should + * have been created using Case.createCaseDirectory() to + * ensure that the required sub-directories aere created. + * @param caseName The name of case. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be the + * empty string. * - * @throws org.sleuthkit.autopsy.casemodule.CaseActionException + * @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. If so, + * CaseActionException.getCause will return a + * Throwable (null otherwise). */ public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException { create(caseDir, caseName, caseNumber, examiner, CaseType.SINGLE_USER_CASE); } /** - * Creates a new case (create the XML config file and database) + * Creates a new case. * - * @param caseDir The directory to store case data in. Will be created if - * it doesn't already exist. If it exists, it should have - * all of the needed sub dirs that createCaseDirectory() - * will create. - * @param caseName the name of case - * @param caseNumber the case number - * @param examiner the examiner for this case - * @param caseType the type of case, single-user or multi-user + * @param caseDir The full path of the case directory. It will be created + * if it doesn't already exist; if it exists, it should + * have been created using Case.createCaseDirectory() to + * ensure that the required sub-directories aere created. + * @param caseName The name of case. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be the + * empty string. + * @param caseType The type of case (single-user or multi-user). The + * exception will have a user-friendly message and may be + * a wrapper for a lower-level exception. If so, + * CaseActionException.getCause will return a Throwable + * (null otherwise). + * + * @throws CaseActionException if there is a problem creating the case. */ public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { - logger.log(Level.INFO, "Creating new case.\ncaseDir: {0}\ncaseName: {1}", new Object[]{caseDir, caseName}); //NON-NLS + logger.log(Level.INFO, "Creating case with case directory {0}, caseName {1}", new Object[]{caseDir, caseName}); //NON-NLS - // create case directory if it doesn't already exist. + /* + * Create case directory if it doesn't already exist. + */ if (new File(caseDir).exists() == false) { Case.createCaseDirectory(caseDir, caseType); } - String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION; - - XMLCaseManagement xmlcm = new XMLCaseManagement(); - + /* + * Sanitize the case name, create a unique keyword search index name, + * and create a standard (single-user) or unique (multi-user) case + * database name. + */ + String santizedCaseName = sanitizeCaseName(caseName); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); Date date = new Date(); - String santizedCaseName = sanitizeCaseName(caseName); String indexName = santizedCaseName + "_" + dateFormat.format(date); String dbName = null; - - // figure out the database name and index name for text extraction if (caseType == CaseType.SINGLE_USER_CASE) { dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS } else if (caseType == CaseType.MULTI_USER_CASE) { dbName = indexName; } - xmlcm.create(caseDir, caseName, examiner, caseNumber, caseType, dbName, indexName); // create a new XML config file + /* + * Create the case metadata (.aut) file. + * + * TODO (AUT-1885): Replace use of obsolete and unsafe XMLCaseManagement + * class with use of CaseMetadata class. + */ + String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION; + XMLCaseManagement xmlcm = new XMLCaseManagement(); + xmlcm.create(caseDir, caseName, examiner, caseNumber, caseType, dbName, indexName); xmlcm.writeFile(); + /* + * Create the case database. + */ SleuthkitCase db = null; try { if (caseType == CaseType.SINGLE_USER_CASE) { @@ -463,24 +488,23 @@ public static void create(String caseDir, String caseName, String caseNumber, St db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); } } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error creating a case: " + caseName + " in dir " + caseDir + " " + ex.getMessage(), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS SwingUtilities.invokeLater(() -> { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }); + /* + * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly + * messages, so propagate the exception message. + */ throw new CaseActionException(ex.getMessage(), ex); //NON-NLS } catch (UserPreferencesException ex) { logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS SwingUtilities.invokeLater(() -> { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }); - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); } - /** - * Two-stage initialization to avoid leaking reference to "this" in - * constructor. - */ Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db, caseType); changeCase(newCase); } @@ -549,20 +573,28 @@ static String sanitizeCaseName(String caseName) { /** * Opens an existing case. * - * @param caseMetadataFilePath The path of the case metadata file for the - * case to be opened. + * @param caseMetadataFilePath The path of the case metadata file. * - * @throws CaseActionException + * @throws CaseActionException if there is a problem opening the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. If so, + * CaseActionException.getCause will return a + * Throwable (null otherwise). */ public static void open(String caseMetadataFilePath) throws CaseActionException { + logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS + + /* + * Verify the extension of the case metadata file. + */ if (!caseMetadataFilePath.endsWith(CASE_DOT_EXTENSION)) { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CASE_DOT_EXTENSION)); } - logger.log(Level.INFO, "Opening case, case metadata file path: {0}", caseMetadataFilePath); //NON-NLS try { - /** - * Get the case metadata from the file. + /* + * Get the case metadata required to open the case database. */ CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); String caseName = metadata.getCaseName(); @@ -571,7 +603,7 @@ public static void open(String caseMetadataFilePath) throws CaseActionException CaseType caseType = metadata.getCaseType(); String caseDir = metadata.getCaseDirectory(); - /** + /* * Open the case database. */ SleuthkitCase db; @@ -585,40 +617,57 @@ public static void open(String caseMetadataFilePath) throws CaseActionException try { db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir); } catch (UserPreferencesException ex) { - logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); } } - /** - * Do things that require a UI. + /* + * Check for the presence of the UI and do things that can only be + * done with user interaction. */ if (RuntimeProperties.coreComponentsAreActive()) { - /** + /* * If the case database was upgraded for a new schema, notify * the user. */ if (null != db.getBackupDatabasePath()) { SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(null, - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", - db.getBackupDatabasePath()), + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", db.getBackupDatabasePath()), NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), JOptionPane.INFORMATION_MESSAGE); }); } - /** - * TODO: This currently has no value if it there is no user to - * interact with a fid missing images dialog. + /* + * Look for the files for the data sources listed in the case + * database and give the user the opportunity to locate any that + * are missing. */ - checkImagesExist(db); + Map<Long, String> imgPaths = getImagePaths(db); + for (Map.Entry<Long, String> entry : imgPaths.entrySet()) { + long obj_id = entry.getKey(); + String path = entry.getValue(); + boolean fileExists = (pathExists(path) || driveExists(path)); + if (!fileExists) { + int ret = JOptionPane.showConfirmDialog( + WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", getAppName(), path), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), + JOptionPane.YES_NO_OPTION); + if (ret == JOptionPane.YES_OPTION) { + MissingImageDialog.makeDialog(obj_id, db); + } else { + logger.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS + } + } + } } - /** - * Two-stage initialization to avoid leaking reference to "this" in - * constructor. TODO: Remove use of obsolete XMLCaseManagement - * class. + /* + * TODO (AUT-1885): Replace use of obsolete and unsafe + * XMLCaseManagement class with use of CaseMetadata class. */ XMLCaseManagement xmlcm = new XMLCaseManagement(); xmlcm.open(caseMetadataFilePath); @@ -626,28 +675,16 @@ public static void open(String caseMetadataFilePath) throws CaseActionException changeCase(openedCase); } catch (CaseMetadataException ex) { - /** - * Attempt clean up. - */ - try { - Case badCase = Case.getCurrentCase(); - badCase.closeCase(); - } catch (IllegalStateException ignored) { - } - throw new CaseActionException(ex.getMessage(), ex); //NON-NLS + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.metaDataFileCorrupt.exception.msg"), ex); //NON-NLS } catch (TskCoreException ex) { - /** - * Attempt clean up. - */ - try { - Case badCase = Case.getCurrentCase(); - badCase.closeCase(); - } catch (IllegalStateException ignored) { - } SwingUtilities.invokeLater(() -> { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }); - throw new CaseActionException(ex.getMessage(), ex); //NON-NLS + /* + * SleuthkitCase.openCase throws TskCoreExceptions with + * user-friendly messages, so propagate the exception message. + */ + throw new CaseActionException(ex.getMessage(), ex); } } @@ -666,35 +703,6 @@ static Map<Long, String> getImagePaths(SleuthkitCase db) { //TODO: clean this up return imgPaths; } - /** - * Ensure that all image paths point to valid image files - */ - private static void checkImagesExist(SleuthkitCase db) { - Map<Long, String> imgPaths = getImagePaths(db); - for (Map.Entry<Long, String> entry : imgPaths.entrySet()) { - long obj_id = entry.getKey(); - String path = entry.getValue(); - boolean fileExists = (pathExists(path) || driveExists(path)); - if (!fileExists) { - int ret = JOptionPane.showConfirmDialog(null, - NbBundle.getMessage(Case.class, - "Case.checkImgExist.confDlg.doesntExist.msg", - getAppName(), path), - NbBundle.getMessage(Case.class, - "Case.checkImgExist.confDlg.doesntExist.title"), - JOptionPane.YES_NO_OPTION); - if (ret == JOptionPane.YES_OPTION) { - - MissingImageDialog.makeDialog(obj_id, db); - - } else { - logger.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS - } - - } - } - } - /** * Adds the image to the current case after it has been added to the DB. * Sends out event and reopens windows if needed. @@ -1585,10 +1593,10 @@ private static void doCaseChange(Case toChangeTo) { if (RuntimeProperties.coreComponentsAreActive()) { // enable these menus - CallableSystemAction.get(AddImageAction.class).setEnabled(true); - CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); - CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu + CallableSystemAction.get(AddImageAction.class).setEnabled(true); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); + CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu if (toChangeTo.hasData()) { // open all top components diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 5ea268adf47f42def8cff64568a1c953ee415167..f879393558e772ebacaa9a13b2f41a4b65382d17 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -65,7 +65,7 @@ public CaseOpenAction() { } /** - * Pops up a file chooser to allow the user to select a case meta data file + * Pops up a file chooser to allow the user to select a case metadata file * (.aut file) and attempts to open the case described by the file. * * @param e The action event. @@ -73,21 +73,28 @@ public CaseOpenAction() { @Override public void actionPerformed(ActionEvent e) { /* - * If ingest is running, do a dialog to warn the user and confirm - * abandoning the ingest. + * If ingest is running, do a dialog to warn the user and confirm the + * intent to close the current case and leave the ingest process + * incomplete. */ if (IngestManager.getInstance().isIngestRunning()) { - String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"), NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); Object res = DialogDisplayer.getDefault().notify(descriptor); if (res != null && res == DialogDescriptor.YES_OPTION) { + Case currentCase = null; try { - Case.getCurrentCase().closeCase(); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Error closing case", ex); //NON-NLS + currentCase = Case.getCurrentCase(); + currentCase.closeCase(); + } catch (IllegalStateException ignored) { + /* + * No current case. + */ + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null != currentCase ? currentCase.getCaseDirectory() : "?")), ex); //NON-NLS } } else { return; @@ -100,17 +107,13 @@ public void actionPerformed(ActionEvent e) { */ int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); if (retval == JFileChooser.APPROVE_OPTION) { - /** - * This is a bit of a hack, but close the startup window, if it was - * the source of the action invocation. + /* + * Close the startup window, if it is open. */ - try { - StartupWindowProvider.getInstance().close(); - } catch (Exception unused) { - } + StartupWindowProvider.getInstance().close(); - /** - * Try to open the case associated with the case meta data file the + /* + * Try to open the case associated with the case metadata file the * user selected. */ final String path = fileChooser.getSelectedFile().getPath(); @@ -121,12 +124,14 @@ public void actionPerformed(ActionEvent e) { try { Case.open(path); } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Could not open case at %s", path), ex); + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS SwingUtilities.invokeLater(() -> { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); //NON-NLS + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); if (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 9b6c29a7c5696bc12bedc3de8b9b5320f8af148e..39f831c5a188b4be6ca8cb3aaa8a1600ae97dc34 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -39,10 +39,11 @@ import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.openide.windows.WindowManager; import java.awt.Cursor; +import java.util.concurrent.ExecutionException; import org.sleuthkit.autopsy.ingest.IngestManager; /** - * An action that runs the new case wizard. + * An action that creates and runs the new case wizard. */ final class NewCaseWizardAction extends CallableSystemAction { @@ -53,32 +54,39 @@ final class NewCaseWizardAction extends CallableSystemAction { @Override public void performAction() { /* - * If ingest is running, do a dialog to warn the user and confirm - * abandoning the ingest. + * If ingest is running, do a dialog to warn the user and confirm the + * intent to close the current case and leave the ingest process + * incomplete. */ if (IngestManager.getInstance().isIngestRunning()) { - String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"), NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); Object res = DialogDisplayer.getDefault().notify(descriptor); if (res != null && res == DialogDescriptor.YES_OPTION) { + Case currentCase = null; try { - Case.getCurrentCase().closeCase(); - } catch (Exception ex) { - Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case", ex); //NON-NLS + currentCase = Case.getCurrentCase(); + currentCase.closeCase(); + } catch (IllegalStateException ignored) { + /* + * No current case. + */ + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null != currentCase ? currentCase.getCaseDirectory() : "?")), ex); //NON-NLS } } else { return; } } WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - newCaseAction(); + runNewCaseWizard(); } - private void newCaseAction() { - final WizardDescriptor wizardDescriptor = new WizardDescriptor(getPanels()); + private void runNewCaseWizard() { + final WizardDescriptor wizardDescriptor = new WizardDescriptor(getNewCaseWizardPanels()); wizardDescriptor.setTitleFormat(new MessageFormat("{0}")); wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text")); Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); @@ -104,21 +112,14 @@ protected void done() { AddImageAction addImageAction = SystemAction.get(AddImageAction.class); addImageAction.actionPerformed(null); } catch (Exception ex) { - logger.log(Level.SEVERE, "Error creating case", ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Error creating case %s", wizardDescriptor.getProperty("caseName")), ex); //NON-NLS SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getCause().getMessage() + " " - + NbBundle.getMessage(this.getClass(), "CaseExceptionWarning.CheckMultiUserOptions"), - NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), - JOptionPane.ERROR_MESSAGE); //NON-NLS - /** - * This is a bit of a hack, but close the startup - * window, if it was the source of the action - * invocation. - */ - try { - StartupWindowProvider.getInstance().close(); - } catch (Exception unused) { - } + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + (ex instanceof ExecutionException ? ex.getCause().getMessage() : ex.getMessage()), + NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + StartupWindowProvider.getInstance().close(); // RC: Why close and open? if (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); } @@ -137,7 +138,6 @@ protected void done() { private void doFailedCaseCleanup(WizardDescriptor wizardDescriptor) { String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS if (createdDirectory != null) { - logger.log(Level.INFO, "Deleting a created case directory due to an error, dir: {0}", createdDirectory); //NON-NLS Case.deleteCaseDirectory(new File(createdDirectory)); } SwingUtilities.invokeLater(() -> { @@ -146,10 +146,10 @@ private void doFailedCaseCleanup(WizardDescriptor wizardDescriptor) { } /** - * Initializes the new case wizard panels. + * Creates the new case wizard panels. */ @SuppressWarnings({"unchecked", "rawtypes"}) - private WizardDescriptor.Panel<WizardDescriptor>[] getPanels() { + private WizardDescriptor.Panel<WizardDescriptor>[] getNewCaseWizardPanels() { if (panels == null) { panels = new WizardDescriptor.Panel[]{ new NewCaseWizardPanel1(), @@ -180,21 +180,33 @@ private WizardDescriptor.Panel<WizardDescriptor>[] getPanels() { return panels; } + /** + * @inheritDoc + */ @Override public String getName() { return NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.getName.text"); } + /** + * @inheritDoc + */ @Override public String iconResource() { return null; } + /** + * @inheritDoc + */ @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } + /** + * @inheritDoc + */ @Override protected boolean asynchronous() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java index 6a0c0742eb7cc1dfa8105b963b4cd771bf0dbcf7..07706cc798d05e9a40d449c1130329a6df328c5f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java @@ -43,31 +43,39 @@ class OpenRecentCasePanel extends javax.swing.JPanel { private static String[] casePaths; private RecentCasesTableModel model; + /** + * Constructs a panel used by the the open recent case option of the start + * window. + */ private OpenRecentCasePanel() { initComponents(); } + /* + * Gets the singleton instance of the panel used by the the open recent case + * option of the start window. + */ static OpenRecentCasePanel getInstance() { if (instance == null) { instance = new OpenRecentCasePanel(); } - instance.generateRecentCases(); // refresh the case list + instance.refreshRecentCasesTable(); return instance; } /** - * Sets the Close button action listener. + * Adds an action listener to the cancel button. * - * @param e the action listener + * @param listener An action listener. */ - public void setCloseButtonActionListener(ActionListener e) { - this.cancelButton.addActionListener(e); + void setCloseButtonActionListener(ActionListener listener) { + this.cancelButton.addActionListener(listener); } - + /** * Retrieves all the recent cases and adds them to the table. */ - private void generateRecentCases() { + private void refreshRecentCasesTable() { caseNames = RecentCases.getInstance().getRecentCaseNames(); casePaths = RecentCases.getInstance().getRecentCasePaths(); model = new RecentCasesTableModel(); @@ -85,8 +93,10 @@ private void generateRecentCases() { openButton.setEnabled(false); } } - - // Open the selected case + + /* + * Opens the selected case. + */ private void openCase() { if (casePaths.length < 1) { return; @@ -102,7 +112,7 @@ private void openCase() { } /* - * Verify the case name and metadata file path. + * Open the case. */ if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) { JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), @@ -110,14 +120,9 @@ private void openCase() { NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore - - /* - * If a case was not already open, pop up the start window. - */ if (Case.isCaseOpen() == false) { StartupWindowProvider.getInstance().open(); } - } else { SwingUtilities.invokeLater(() -> { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); @@ -131,12 +136,9 @@ private void openCase() { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); JOptionPane.showMessageDialog( WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); //NON-NLS - /* - * If a case was not already open, pop up the start - * window. - */ + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); if (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); } @@ -154,6 +156,9 @@ private class RecentCasesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; + /** + * @inheritDoc + */ @Override public int getRowCount() { int count = 0; @@ -165,11 +170,17 @@ public int getRowCount() { return count; } + /** + * @inheritDoc + */ @Override public int getColumnCount() { return 2; } + /** + * @inheritDoc + */ @Override public String getColumnName(int column) { String colName = null; @@ -186,6 +197,9 @@ public String getColumnName(int column) { return colName; } + /** + * @inheritDoc + */ @Override public Object getValueAt(int rowIndex, int columnIndex) { Object ret = null; @@ -203,15 +217,28 @@ public Object getValueAt(int rowIndex, int columnIndex) { return ret; } + /** + * @inheritDoc + */ @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } + /** + * @inheritDoc + */ @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { } + /** + * Shortens a path to fit the display. + * + * @param path The path to shorten. + * + * @return The shortened path. + */ private String shortenPath(String path) { String shortenedPath = path; if (shortenedPath.length() > 50) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index 8e9c989d5ac53e70c4a793ef9eb941b373cb2095..2df4401ad9f7da16b22c74fae2455f340843a76d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,6 @@ import java.awt.event.ActionListener; import java.io.File; import javax.swing.JOptionPane; -import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; @@ -36,21 +35,23 @@ import org.sleuthkit.autopsy.ingest.IngestManager; /** - * This class is used to add the action to the recent case menu item. When the - * the recent case menu is pressed, it should open that selected case. + * An action listener that opens a recent case. */ class RecentItems implements ActionListener { - final String caseName; - final String casePath; - private JPanel caller; // for error handling + private static final Logger logger = Logger.getLogger(RecentItems.class.getName()); + private final String caseName; + private final String caseMetaDataFilePath; /** - * the constructor + * Constructs an action listener that opens a recent case. + * + * @param caseName The name of the case. + * @param caseMetaDataFilePath The path to the case metadata file. */ - public RecentItems(String caseName, String casePath) { + public RecentItems(String caseName, String caseMetaDataFilePath) { this.caseName = caseName; - this.casePath = casePath; + this.caseMetaDataFilePath = caseMetaDataFilePath; } /** @@ -60,58 +61,65 @@ public RecentItems(String caseName, String casePath) { */ @Override public void actionPerformed(ActionEvent e) { - - // if ingest is ongoing, warn and get confirmaion before opening a different case + /* + * If ingest is running, do a dialog to warn the user and confirm the + * intent to close the current case and leave the ingest process + * incomplete. + */ if (IngestManager.getInstance().isIngestRunning()) { - // show the confirmation first to close the current case and open the "New Case" wizard panel - String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"), NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object res = DialogDisplayer.getDefault().notify(descriptor); if (res != null && res == DialogDescriptor.YES_OPTION) { + Case currentCase = null; try { - Case.getCurrentCase().closeCase(); // close the current case - } catch (Exception ex) { - Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS + currentCase = Case.getCurrentCase(); + currentCase.closeCase(); + } catch (IllegalStateException ignored) { + /* + * No current case. + */ + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null!= currentCase ? currentCase.getCaseDirectory() : "?")),ex); //NON-NLS } } else { return; } } - // check if the file exists - if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) { - // throw an error here - JOptionPane.showMessageDialog(caller, - NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", - caseName), - NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.err"), + /* + * Open the case. + */ + if (caseName.equals("") || caseMetaDataFilePath.equals("") || (!new File(caseMetaDataFilePath).exists())) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName), + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); - RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore - - //if case is not opened, open the start window + RecentCases.getInstance().removeRecentCase(caseName, caseMetaDataFilePath); if (Case.isCaseOpen() == false) { EventQueue.invokeLater(() -> { StartupWindowProvider.getInstance().open(); }); - } } else { SwingUtilities.invokeLater(() -> { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); new Thread(() -> { - // Create case. try { - Case.open(casePath); + Case.open(caseMetaDataFilePath); } catch (CaseActionException ex) { SwingUtilities.invokeLater(() -> { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getMessage(), - NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); //NON-NLS + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); if (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); }