diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index c9a86ea72f..ea26aa612d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -192,10 +192,7 @@ MissingImageDialog.ErrorSettingImage=Error setting image path. Please try again. NewCaseVisualPanel1.getName.text=Case Info NewCaseVisualPanel1.caseDirBrowse.selectButton.text=Select NewCaseVisualPanel1.badCredentials.text=Bad multi-user settings (see Tools, Options, Multi-user) or services are down. -NewCaseVisualPanel1.MultiUserDisabled.text=Multi-user cases not enabled. See Tools, Options, Multi-user. NewCaseVisualPanel2.getName.text=Additional Information -NewCaseWizardAction.closeCurCase.confMsg.msg=Do you want to save and close this case and proceed with the new case creation? -NewCaseWizardAction.closeCurCase.confMsg.title=Warning\: Closing the Current Case NewCaseWizardAction.newCase.windowTitle.text=New Case Information NewCaseWizardAction.getName.text=New Case Wizard NewCaseWizardAction.databaseProblem1.text=Cannot open database. Cancelling case creation. @@ -239,25 +236,21 @@ CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1} MissingImageDialog.lbWarning.text= MissingImageDialog.lbWarning.toolTipText= -SingleUserCaseImporter.AlreadyMultiUser=Case is already multi-user! -SingleUserCaseImporter.BadCaseSourceFolder=Case source folder does not exist! -SingleUserCaseImporter.BadImageSourceFolder=Image source folder does not exist! -SingleUserCaseImporter.BadDatabaseFileName=Database file does not exist! -SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping -SingleUserCaseImporter.NonUniqueDatabaseName=Database name not unique. Skipping. -SingleUserCaseImporter.PotentiallyNonUniqueDatabaseName=Unclear if database name unique. Moving ahead. -SingleUserCaseImporter.ImportedAsMultiUser=\nThis case was imported as a multi-user collaborative case on -SingleUserCaseImporter.UnableToCopySourceImages=Unable to copy source images -SingleUserCaseImporter.DeletingCase=Deleting original case folder -SingleUserCaseImporter.CanNotOpenDatabase=Unable to open database -SingleUserCaseImporter.WillImport=Will import: -SingleUserCaseImporter.WillNotImport=Will not import: -SingleUserCaseImporter.None=None -SingleUserCaseImporter.ContinueWithImport=Continue with import? -SingleUserCaseImporter.Cancelled=Cancelled -NewCaseVisualPanel1.caseParentDirWarningLabel.text=Case directory warning label +NewCaseVisualPanel1.caseParentDirWarningLabel.text= NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user -NewCaseVisualPanel1.multiUserSettingsWarningLabel.text=Multi-user settings warning label +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 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 5dff497d16..4fd28db906 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -218,3 +218,4 @@ AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=\u30ad\u30e3\u30f3\u30bb\u30e ImageFilePanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb LocalFilesPanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb NewCaseVisualPanel1.caseParentDirWarningLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb +NewCaseVisualPanel1.caseTypeLabel.text=\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\uff1a diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index de01134bcd..530938dcf2 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 java.awt.Cursor; import java.awt.Frame; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; @@ -322,6 +323,9 @@ public class Case implements SleuthkitCase.ErrorObserver { Case oldCase = Case.currentCase; Case.currentCase = null; if (oldCase != null) { + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + }); doCaseChange(null); //closes windows, etc if (null != oldCase.tskErrorReporter) { oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case @@ -345,7 +349,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } // start listening for TSK errors for the new case currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECONDS_BETWEEN_ERROR_REPORTS, - NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText")); + NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText")); doCaseChange(currentCase); SwingUtilities.invokeLater(() -> { RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases @@ -370,8 +374,11 @@ public class Case implements SleuthkitCase.ErrorObserver { } else { Logger.setLogDirectory(PlatformUtil.getLogDirectory()); } + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); } - + @Override public void receiveError(String context, String errorMessage) { /* NOTE: We are accessing tskErrorReporter from two different threads. @@ -453,9 +460,11 @@ public class Case implements SleuthkitCase.ErrorObserver { db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); } } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error creating a case: " + caseName + " in dir " + caseDir, ex); //NON-NLS - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.create.exception.msg", caseName, caseDir), ex); + logger.log(Level.SEVERE, "Error creating a case: " + caseName + " in dir " + caseDir + " " + ex.getMessage(), ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + throw new CaseActionException(ex.getMessage(), ex); //NON-NLS } catch (UserPreferencesException ex) { logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS throw new CaseActionException( @@ -498,7 +507,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * * @return the sanitized case name to use for Database, Solr, and ActiveMQ */ - public static String sanitizeCaseName(String caseName) { + static String sanitizeCaseName(String caseName) { String result; @@ -615,19 +624,29 @@ public class Case implements SleuthkitCase.ErrorObserver { Case openedCase = new Case(caseName, caseNumber, examiner, caseMetadataFilePath, xmlcm, db, caseType); changeCase(openedCase); - } catch (CaseMetadataException | TskCoreException ex) { + } catch (CaseMetadataException ex) { /** * Clean-up the case if it was actually opened. TODO: Do this * better. */ + try { + Case badCase = Case.getCurrentCase(); + badCase.closeCase(); + } catch (IllegalStateException unused) { + // Already logged. + } + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ": " + ex.getMessage(), ex); //NON-NLS + } catch (TskCoreException ex) { try { Case badCase = Case.getCurrentCase(); badCase.closeCase(); } catch (CaseActionException | IllegalStateException unused) { // Already logged. } - - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ". " + ex.getMessage(), ex); + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + throw new CaseActionException(ex.getMessage(), ex); //NON-NLS } } @@ -654,8 +673,7 @@ public class Case implements SleuthkitCase.ErrorObserver { for (Map.Entry entry : imgPaths.entrySet()) { long obj_id = entry.getKey(); String path = entry.getValue(); - boolean fileExists = (pathExists(path) - || driveExists(path)); + boolean fileExists = (pathExists(path) || driveExists(path)); if (!fileExists) { int ret = JOptionPane.showConfirmDialog(null, NbBundle.getMessage(Case.class, @@ -686,7 +704,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * * @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and * {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and - * {@link #notifyFailedAddingDataSource(java.util.UUID)} + * {@link #notifyFailedAddingDataSource(java.util.UUID)} */ @Deprecated public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException { @@ -701,13 +719,13 @@ public class Case implements SleuthkitCase.ErrorObserver { /** * Finishes adding new local data source to the case. Sends out event and - * reopens windows if needed. + * reopens windows if needed. * * @param newDataSource new data source added * * @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and * {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and - * {@link #notifyFailedAddingDataSource(java.util.UUID)} + * {@link #notifyFailedAddingDataSource(java.util.UUID)} */ @Deprecated void addLocalDataSource(Content newDataSource) { @@ -879,7 +897,7 @@ public class Case implements SleuthkitCase.ErrorObserver { RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case updateMainWindowTitle(newCaseName); } catch (Exception e) { - Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", e); //NON-NLS + Logger.getLogger(Case.class.getName()).log(Level.WARNING, "Error: problem updating case name.", e); //NON-NLS } }); } catch (Exception e) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java index 1fc7c79575..ce97a09c9d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java @@ -28,6 +28,14 @@ import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import java.util.logging.Level; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.windows.WindowManager; +import java.awt.Cursor; /** * The action to close the current Case. This class should be disabled on @@ -57,10 +65,32 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese */ @Override public void actionPerformed(ActionEvent e) { + + // if ingest is ongoing, warn and get confirmaion before opening a different case + if (IngestManager.getInstance().isIngestRunning()) { + // show the confirmation first to close the current case and open the "New Case" wizard panel + String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, + NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + + Object res = DialogDisplayer.getDefault().notify(descriptor); + if (res != null && res == DialogDescriptor.YES_OPTION) { + try { + Case.getCurrentCase().closeCase(); // close the current case + } catch (Exception ex) { + Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS + } + } else { + return; + } + } + if (Case.existsCurrentCase() == false) { return; } - + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); new SwingWorker() { @Override @@ -76,6 +106,7 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese @Override protected void done() { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); StartupWindowProvider.getInstance().open(); } }.execute(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 7cea2ef29e..63ddefebf8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; @@ -31,6 +32,12 @@ import org.openide.util.lookup.ServiceProvider; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Version; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.sleuthkit.autopsy.coreutils.Logger; +import java.util.logging.Level; +import org.sleuthkit.autopsy.ingest.IngestManager; /** * An action that opens an existing case. @@ -64,6 +71,28 @@ public final class CaseOpenAction implements ActionListener { */ @Override public void actionPerformed(ActionEvent e) { + + // if ingest is ongoing, warn and get confirmaion before opening a different case + if (IngestManager.getInstance().isIngestRunning()) { + // show the confirmation first to close the current case and open the "New Case" wizard panel + String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, + NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + + Object res = DialogDisplayer.getDefault().notify(descriptor); + if (res != null && res == DialogDescriptor.YES_OPTION) { + try { + Case.getCurrentCase().closeCase(); // close the current case + } catch (Exception ex) { + Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS + } + } else { + return; + } + } + /** * Pop up a file chooser to allow the user to select a case meta data * file (.aut file) @@ -80,18 +109,22 @@ public final class CaseOpenAction implements ActionListener { } /** - * Try to open the caswe associated with the case meta data file the + * Try to open the case associated with the case meta data file the * user selected. */ final String path = fileChooser.getSelectedFile().getPath(); String dirPath = fileChooser.getSelectedFile().getParent(); ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator))); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); new Thread(() -> { try { Case.open(path); } catch (CaseActionException ex) { SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(null, ex.getMessage(), NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getMessage() + " " + + NbBundle.getMessage(this.getClass(), "CaseExceptionWarning.CheckMultiUserOptions"), + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); //NON-NLS if (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); } @@ -100,5 +133,4 @@ public final class CaseOpenAction implements ActionListener { }).start(); } } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form index ddbcd7e243..94e9161756 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form @@ -1,4 +1,4 @@ - +
@@ -37,49 +37,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + @@ -92,38 +87,51 @@ - + - + - + - + - + - + - + - + + + + + + + + + + + - + + + + @@ -253,11 +261,11 @@ + - @@ -304,5 +312,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java index 5a60ca75d7..99dcd8bfa2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java @@ -24,12 +24,11 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.*; +import java.nio.file.Paths; import java.awt.event.ActionListener; import java.io.File; import java.util.Map; import java.util.logging.Level; - import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JOptionPane; @@ -47,7 +46,9 @@ import org.openide.util.actions.CallableSystemAction; */ class CasePropertiesForm extends javax.swing.JPanel { - Case current = null; + private static final long serialVersionUID = 1L; + + private Case current = null; private static JPanel caller; // panel for error // Shrink a path to fit in targetLength (if necessary), by replaceing part @@ -78,16 +79,25 @@ class CasePropertiesForm extends javax.swing.JPanel { /** * Creates new form CasePropertiesForm */ - CasePropertiesForm(Case currentCase, String crDate, String caseDir, Map imgPaths) { + CasePropertiesForm(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException { initComponents(); caseNameTextField.setText(currentCase.getName()); caseNumberTextField.setText(currentCase.getNumber()); examinerTextField.setText(currentCase.getExaminer()); crDateTextField.setText(crDate); caseDirTextArea.setText(caseDir); - current = currentCase; + CaseMetadata caseMetadata = new CaseMetadata(Paths.get(currentCase.getConfigFilePath())); + tbDbName.setText(caseMetadata.getCaseDatabaseName()); + Case.CaseType caseType = caseMetadata.getCaseType(); + tbDbType.setText(caseType.toString()); + if (caseType == Case.CaseType.SINGLE_USER_CASE) { + deleteCaseButton.setEnabled(true); + } else { + deleteCaseButton.setEnabled(false); + } + int totalImages = imgPaths.size(); // create the headers and add all the rows @@ -186,12 +196,16 @@ class CasePropertiesForm extends javax.swing.JPanel { examinerLabel = new javax.swing.JLabel(); caseNumberTextField = new javax.swing.JTextField(); examinerTextField = new javax.swing.JTextField(); + lbDbType = new javax.swing.JLabel(); + tbDbType = new javax.swing.JTextField(); + lbDbName = new javax.swing.JLabel(); + tbDbName = new javax.swing.JTextField(); jTextArea1.setColumns(20); jTextArea1.setRows(5); jScrollPane1.setViewportView(jTextArea1); - casePropLabel.setFont(casePropLabel.getFont().deriveFont(Font.BOLD, 24)); + casePropLabel.setFont(new java.awt.Font("Tahoma", 1, 24)); // NOI18N casePropLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); casePropLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.casePropLabel.text")); // NOI18N @@ -213,11 +227,10 @@ class CasePropertiesForm extends javax.swing.JPanel { } }); - genInfoLabel.setFont(genInfoLabel.getFont().deriveFont(Font.BOLD, 14)); - genInfoLabel.setText( - org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.genInfoLabel.text")); // NOI18N + genInfoLabel.setFont(new java.awt.Font("Tahoma", 1, 14)); // NOI18N + genInfoLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.genInfoLabel.text")); // NOI18N - imgInfoLabel.setFont(imgInfoLabel.getFont().deriveFont(Font.BOLD, 14)); + imgInfoLabel.setFont(new java.awt.Font("Tahoma", 1, 14)); // NOI18N imgInfoLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.imgInfoLabel.text")); // NOI18N OKButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.OKButton.text")); // NOI18N @@ -227,7 +240,7 @@ class CasePropertiesForm extends javax.swing.JPanel { }, new String [] { - "Path", "Remove" //NON-NLS + "Path", "Remove" } ) { boolean[] canEdit = new boolean [] { @@ -244,9 +257,9 @@ class CasePropertiesForm extends javax.swing.JPanel { imagesTable.setUpdateSelectionOnSort(false); imagesTableScrollPane.setViewportView(imagesTable); + caseDirTextArea.setEditable(false); caseDirTextArea.setBackground(new java.awt.Color(240, 240, 240)); caseDirTextArea.setColumns(20); - caseDirTextArea.setEditable(false); caseDirTextArea.setRows(1); caseDirTextArea.setRequestFocusEnabled(false); jScrollPane2.setViewportView(caseDirTextArea); @@ -268,6 +281,16 @@ class CasePropertiesForm extends javax.swing.JPanel { examinerTextField.setEditable(false); examinerTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.examinerTextField.text")); // NOI18N + lbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbType.text")); // NOI18N + + tbDbType.setEditable(false); + tbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.tbDbType.text")); // NOI18N + + lbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbName.text")); // NOI18N + + tbDbName.setEditable(false); + tbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.tbDbName.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -276,38 +299,37 @@ class CasePropertiesForm extends javax.swing.JPanel { .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(casePropLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 440, Short.MAX_VALUE) - .addComponent(genInfoLabel) - .addComponent(imgInfoLabel) .addComponent(imagesTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 440, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addGap(181, 181, 181) - .addComponent(OKButton, javax.swing.GroupLayout.PREFERRED_SIZE, 78, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(caseNameLabel) - .addComponent(caseNumberLabel)) - .addGap(25, 25, 25) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(caseNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE) - .addComponent(caseNumberTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE))) - .addGroup(layout.createSequentialGroup() - .addComponent(examinerLabel) - .addGap(45, 45, 45) - .addComponent(examinerTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(caseDirLabel) - .addComponent(crDateLabel)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE) - .addComponent(crDateTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE)))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(caseNameLabel) + .addComponent(caseNumberLabel) + .addComponent(examinerLabel) + .addComponent(caseDirLabel) + .addComponent(crDateLabel) + .addComponent(lbDbType)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(caseNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) + .addComponent(caseNumberTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) + .addComponent(examinerTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) + .addComponent(crDateTextField) + .addComponent(jScrollPane2) + .addComponent(tbDbType) + .addComponent(tbDbName)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(updateCaseNameButton) - .addComponent(deleteCaseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addComponent(deleteCaseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(updateCaseNameButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(genInfoLabel) + .addComponent(imgInfoLabel) + .addGroup(layout.createSequentialGroup() + .addGap(181, 181, 181) + .addComponent(OKButton, javax.swing.GroupLayout.PREFERRED_SIZE, 78, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lbDbName)) + .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); layout.setVerticalGroup( @@ -317,32 +339,42 @@ class CasePropertiesForm extends javax.swing.JPanel { .addComponent(casePropLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(genInfoLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(caseNameLabel) .addComponent(caseNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(updateCaseNameButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(caseNumberLabel) .addComponent(caseNumberTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(examinerLabel) .addComponent(examinerTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 19, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(crDateLabel) .addComponent(crDateTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(caseDirLabel) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(39, 39, 39) + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(caseDirLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(tbDbType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbDbType)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbDbName) + .addComponent(tbDbName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) .addComponent(imgInfoLabel)) - .addComponent(deleteCaseButton)) + .addGroup(layout.createSequentialGroup() + .addGap(9, 9, 9) + .addComponent(deleteCaseButton))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(imagesTableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 170, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -445,6 +477,10 @@ class CasePropertiesForm extends javax.swing.JPanel { private javax.swing.JScrollPane jScrollPane1; private javax.swing.JScrollPane jScrollPane2; private javax.swing.JTextArea jTextArea1; + private javax.swing.JLabel lbDbName; + private javax.swing.JLabel lbDbType; + private javax.swing.JTextField tbDbName; + private javax.swing.JTextField tbDbType; private javax.swing.JButton updateCaseNameButton; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImportDoneCallback.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImportDoneCallback.java deleted file mode 100644 index bb83712cc3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImportDoneCallback.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.sleuthkit.autopsy.casemodule; - -public interface ImportDoneCallback { - - void importDoneCallback(boolean result, String resultString); -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form index c30aa65b0b..76bdf8ace0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form @@ -33,17 +33,27 @@ - - - - - - + + + + + + + + + + + + + + + + @@ -52,14 +62,7 @@ - - - - - - - - + @@ -82,20 +85,19 @@ - - - - + + + + + - - - + @@ -190,16 +192,6 @@ - - - - - - - - - - @@ -210,5 +202,12 @@ + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index 6aebc0fe99..5694ba5c70 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -62,11 +62,9 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { if (UserPreferences.getIsMultiUserModeEnabled()) { multiUserCaseRadioButton.setEnabled(true); multiUserCaseRadioButton.setSelected(true); - multiUserSettingsWarningLabel.setVisible(false); } else { multiUserCaseRadioButton.setEnabled(false); singleUserCaseRadioButton.setSelected(true); - multiUserSettingsWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.MultiUserDisabled.text")); } validateSettings(); } @@ -214,8 +212,8 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { caseDirTextField = new javax.swing.JTextField(); singleUserCaseRadioButton = new javax.swing.JRadioButton(); multiUserCaseRadioButton = new javax.swing.JRadioButton(); - multiUserSettingsWarningLabel = new javax.swing.JLabel(); caseParentDirWarningLabel = new javax.swing.JLabel(); + caseTypeLabel = new javax.swing.JLabel(); jLabel1.setFont(new java.awt.Font("Tahoma", 1, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.jLabel1.text_1")); // NOI18N @@ -256,12 +254,11 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { } }); - multiUserSettingsWarningLabel.setForeground(new java.awt.Color(255, 0, 0)); - org.openide.awt.Mnemonics.setLocalizedText(multiUserSettingsWarningLabel, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.multiUserSettingsWarningLabel.text")); // NOI18N - caseParentDirWarningLabel.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(caseParentDirWarningLabel, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.caseParentDirWarningLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(caseTypeLabel, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.caseTypeLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -278,25 +275,27 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addComponent(jLabel1) .addGap(0, 227, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(caseDirLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(caseParentDirTextField)) .addGroup(layout.createSequentialGroup() .addComponent(caseNameLabel) .addGap(26, 26, 26) .addComponent(caseNameTextField)) - .addComponent(multiUserSettingsWarningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(caseDirLabel) + .addComponent(caseTypeLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(singleUserCaseRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(multiUserCaseRadioButton) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(caseParentDirTextField)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(caseDirBrowseButton))) .addContainerGap()) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(singleUserCaseRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(multiUserCaseRadioButton)) - .addComponent(caseParentDirWarningLabel)) + .addComponent(caseParentDirWarningLabel) .addGap(0, 0, Short.MAX_VALUE)))) ); layout.setVerticalGroup( @@ -313,19 +312,18 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { .addComponent(caseDirLabel) .addComponent(caseParentDirTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(caseDirBrowseButton)) - .addGap(18, 18, 18) - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(caseDirTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(singleUserCaseRadioButton) - .addComponent(multiUserCaseRadioButton)) + .addComponent(multiUserCaseRadioButton) + .addComponent(caseTypeLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(caseDirTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(27, 27, 27) .addComponent(caseParentDirWarningLabel) - .addGap(1, 1, 1) - .addComponent(multiUserSettingsWarningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(21, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -367,10 +365,10 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { private javax.swing.JTextField caseParentDirTextField; private javax.swing.JLabel caseParentDirWarningLabel; private javax.swing.ButtonGroup caseTypeButtonGroup; + private javax.swing.JLabel caseTypeLabel; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JRadioButton multiUserCaseRadioButton; - private javax.swing.JLabel multiUserSettingsWarningLabel; private javax.swing.JRadioButton singleUserCaseRadioButton; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index a7d2d9481d..491f66ba5e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -39,43 +39,47 @@ import javax.swing.JOptionPane; import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.datamodel.CaseDbConnectionInfo; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskData.DbType; +import org.openide.windows.WindowManager; +import java.awt.Cursor; +import org.sleuthkit.autopsy.ingest.IngestManager; /** * Action to open the New Case wizard. */ final class NewCaseWizardAction extends CallableSystemAction { + private static final long serialVersionUID = 1L; + private WizardDescriptor.Panel[] panels; private static final Logger logger = Logger.getLogger(NewCaseWizardAction.class.getName()); @Override public void performAction() { - // there's a case open - if (Case.existsCurrentCase()) { - // show the confirmation first to close the current case and open the "New Case" wizard panel - String closeCurrentCase = NbBundle - .getMessage(this.getClass(), "NewCaseWizardAction.closeCurCase.confMsg.msg"); - NotifyDescriptor d = new NotifyDescriptor.Confirmation(closeCurrentCase, - NbBundle.getMessage(this.getClass(), - "NewCaseWizardAction.closeCurCase.confMsg.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - d.setValue(NotifyDescriptor.NO_OPTION); - Object res = DialogDisplayer.getDefault().notify(d); + // if ingest is ongoing, warn and get confirmaion before opening a different case + if (IngestManager.getInstance().isIngestRunning()) { + // show the confirmation first to close the current case and open the "New Case" wizard panel + String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, + NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + + Object res = DialogDisplayer.getDefault().notify(descriptor); if (res != null && res == DialogDescriptor.YES_OPTION) { try { Case.getCurrentCase().closeCase(); // close the current case - newCaseAction(); // start the new case creation process } catch (Exception ex) { Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS } + } else { + return; } - } else { - newCaseAction(); } + + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + newCaseAction(); // start the new case creation process } /** @@ -102,39 +106,34 @@ final class NewCaseWizardAction extends CallableSystemAction { final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS - Case.create(createdDirectory, caseName, caseNumber, examiner, caseType); return null; } @Override protected void done() { + final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS try { get(); CaseType currentCaseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo(); - if ((currentCaseType == CaseType.SINGLE_USER_CASE) || ((info.getDbType() != DbType.SQLITE) && SleuthkitCase.tryConnectOld(info.getHost(), info.getPort(), info.getUserName(), info.getPassword(), info.getDbType()))) { - AddImageAction addImageAction = SystemAction.get(AddImageAction.class); - addImageAction.actionPerformed(null); - } else { - // @@@ Should we log here? - JOptionPane.showMessageDialog(null, - NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem1.text"), - NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem2.text"), - JOptionPane.ERROR_MESSAGE); - doFailedCaseCleanup(wizardDescriptor); - } - + AddImageAction addImageAction = SystemAction.get(AddImageAction.class); + addImageAction.actionPerformed(null); } catch (Exception ex) { logger.log(Level.SEVERE, "Error creating case", ex); //NON-NLS - - final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), - "CaseCreateAction.msgDlg.cantCreateCase.msg") + " " + caseName, - NbBundle.getMessage(this.getClass(), - "CaseOpenAction.msgDlg.cantOpenCase.title"), - JOptionPane.ERROR_MESSAGE); + 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 + + try { + StartupWindowProvider.getInstance().close(); + } catch (Exception unused) { + } + if (!Case.isCaseOpen()) { + StartupWindowProvider.getInstance().open(); + } }); doFailedCaseCleanup(wizardDescriptor); } @@ -154,6 +153,9 @@ final class NewCaseWizardAction extends CallableSystemAction { 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(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java index 9741296bde..00b4b600c3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java @@ -28,6 +28,8 @@ import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.windows.WindowManager; +import java.awt.Cursor; /** * Panel show from the splash dialog that shows recent cases and allows them to @@ -196,7 +198,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel { } // Open the recent cases if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) { - JOptionPane.showMessageDialog(null, + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(this.getClass(), "OpenRecentCasePanel.openCase.msgDlg.caseDoesntExist.msg", caseName), @@ -211,12 +213,17 @@ class OpenRecentCasePanel extends javax.swing.JPanel { } } else { + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + }); new Thread(() -> { try { Case.open(casePath); } catch (CaseActionException ex) { SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(null, ex.getMessage(), NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); + 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 (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index a4859c9846..8e9c989d5a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -26,6 +26,14 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; +import java.awt.Cursor; +import java.util.logging.Level; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; /** * This class is used to add the action to the recent case menu item. When the @@ -52,6 +60,28 @@ class RecentItems implements ActionListener { */ @Override public void actionPerformed(ActionEvent e) { + + // if ingest is ongoing, warn and get confirmaion before opening a different case + if (IngestManager.getInstance().isIngestRunning()) { + // show the confirmation first to close the current case and open the "New Case" wizard panel + String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"); + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase, + NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"), + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + + Object res = DialogDisplayer.getDefault().notify(descriptor); + if (res != null && res == DialogDescriptor.YES_OPTION) { + try { + Case.getCurrentCase().closeCase(); // close the current case + } catch (Exception ex) { + Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS + } + } else { + return; + } + } + // check if the file exists if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) { // throw an error here @@ -70,13 +100,18 @@ class RecentItems implements ActionListener { } } else { + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + }); new Thread(() -> { // Create case. try { Case.open(casePath); } catch (CaseActionException ex) { SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(null, ex.getMessage(), NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE); + 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 if (!Case.isCaseOpen()) { StartupWindowProvider.getInstance().open(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseImporter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java similarity index 53% rename from Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseImporter.java rename to Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index b5498cc284..47d27c3ab9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseImporter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -18,18 +18,10 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.Dimension; -import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; @@ -37,376 +29,251 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.util.ArrayList; import java.util.Date; -import java.util.logging.Level; -import javax.swing.JOptionPane; -import static javax.swing.JOptionPane.OK_CANCEL_OPTION; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.SwingUtilities; import org.apache.commons.io.FileUtils; import org.openide.util.NbBundle; -import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case.CaseType; import static org.sleuthkit.autopsy.casemodule.Case.MODULE_FOLDER; -import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.coreutils.TimeStampUtils; -import org.sleuthkit.autopsy.coreutils.UNCPathUtilities; /** - * Import case(s) from single-user to multi-user. Recursively scans subfolders. + * Import a case from single-user to multi-user. */ -public class SingleUserCaseImporter implements Runnable { +public class SingleUserCaseConverter { private static final String AUTOPSY_DB_FILE = "autopsy.db"; //NON-NLS private static final String DOTAUT = ".aut"; //NON-NLS - public static final String CASE_IMPORT_LOG_FILE = "case_import_log.txt"; //NON-NLS - private static final String logDateFormat = "yyyy/MM/dd HH:mm:ss"; //NON-NLS - //If TIMELINE_FOLDER changes, also update TIMELINE in EventsRepository private static final String TIMELINE_FOLDER = "Timeline"; //NON-NLS - //If TIMELINE_FILE changes, also update TIMELINE_FILE in EventDB - private final static String TIMELINE_FILE = "events.db"; //NON-NLS - private final static String AIM_LOG_FILE_NAME = "auto_ingest_log.txt"; //NON-NLS - private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(logDateFormat); + private static final String TIMELINE_FILE = "events.db"; //NON-NLS + private static final String POSTGRES_DEFAULT_DB_NAME = "postgres"; private static final int MAX_DB_NAME_LENGTH = 63; - private final String SEP = System.getProperty("line.separator"); - private final Object threadWaitNotifyLock = new Object(); - private final Path caseInputFolder; - private final String caseOutputFolder; - private final String imageInputFolder; - private final String imageOutputFolder; - private final boolean copySourceImages; - private final boolean deleteCase; - private final CaseDbConnectionInfo db; - private final ImportDoneCallback notifyOnComplete; - private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities(); - private PrintWriter writer; - private XMLCaseManagement oldXmlCaseManagement; - private XMLCaseManagement newXmlCaseManagement; - private boolean addTimestamp; - private int userAnswer = 0; + public class ImportCaseData { - /** - * SingleUserCaseImporter constructor - * - * @param caseInput the folder to start our case search from. Will - * find valid cases from this folder down, and - * process them. - * @param caseOutput the folder to place processed cases into - * @param imageInput the folder that holds the images to copy over - * @param imageOutput the destination folder for the images - * @param database the connection information to talk to the - * PostgreSQL db - * @param copySourceImages true if images should be copied - * @param deleteCase true if the old version of the case should be - * deleted after import - * @param addTimestamp true if the output case name should end in a - * timestamp, false otherwise - * @param callback a callback from the calling panel for - * notification when the import has completed. This - * is a Runnable on a different thread. - */ - public SingleUserCaseImporter(String caseInput, String caseOutput, String imageInput, String imageOutput, CaseDbConnectionInfo database, - boolean copySourceImages, boolean deleteCase, ImportDoneCallback callback, boolean addTimestamp) { - this.caseInputFolder = Paths.get(caseInput); - this.caseOutputFolder = caseOutput; - this.imageInputFolder = imageInput; - this.imageOutputFolder = imageOutput; - this.copySourceImages = copySourceImages; - this.deleteCase = deleteCase; - this.db = database; - this.notifyOnComplete = callback; - this.addTimestamp = addTimestamp; - } + private final Path imageInputFolder; + private final Path caseInputFolder; + private final Path imageOutputFolder; + private final Path caseOutputFolder; + private final String oldCaseName; + private final String newCaseName; + private final boolean copySourceImages; + private final boolean deleteCase; + private String postgreSQLDbName; + private final String autFileName; + private final String rawFolderName; + private final CaseDbConnectionInfo db; - /** - * Tests if the input path has a corresponding image input folder and no - * repeated case names in the path. If both of these conditions are true, we - * can process this case, otherwise we can not. - * - * @param icd the import case data for the current case - * - * @return true if we can process it, false if not - */ - private boolean canProcess(ImportCaseData icd) { - try { - String relativeCaseName = TimeStampUtils.removeTimeStamp(icd.getRelativeCaseName()); - String caseName = TimeStampUtils.removeTimeStamp(icd.getOldCaseName()); + public ImportCaseData( + Path imageInput, + Path caseInput, + Path imageOutput, + Path caseOutput, + String oldCaseName, + String newCaseName, + String autFileName, + String rawFolderName, + boolean copySourceImages, + boolean deleteCase) throws UserPreferencesException { - // check for image folder - Path testImageInputsFromOldCase = Paths.get(imageInputFolder, relativeCaseName); - if (copySourceImages) { - if (!testImageInputsFromOldCase.toFile().isDirectory()) { - log(imageInputFolder + " has no corresponding images folder. Not able to process."); - return false; - } else { - icd.setSpecificImageInputFolder(testImageInputsFromOldCase); - } - - Path imagePath = Paths.get(imageInputFolder); - // see if case name is in the image path. This causes bad things to happen with the parsing. - for (int x = 0; x < imagePath.getNameCount(); ++x) { - if (caseName.toLowerCase().equals(imagePath.getName(x).toString().toLowerCase())) { - log(imagePath.toString() + " has case name \"" + caseName + "\" within path. Not able to process."); - return false; - } - } - } else { - icd.setSpecificImageInputFolder(testImageInputsFromOldCase); - } - } catch (Exception ex) { - log("Error processing " + icd.specificCaseInputFolder.toString() + ": " + ex.getMessage()); - return false; // anything goes wrong, bail. + this.imageInputFolder = imageInput; + this.caseInputFolder = caseInput; + this.imageOutputFolder = imageOutput; + this.caseOutputFolder = caseOutput; + this.oldCaseName = oldCaseName; + this.newCaseName = newCaseName; + this.autFileName = autFileName; + this.rawFolderName = rawFolderName; + this.copySourceImages = copySourceImages; + this.deleteCase = deleteCase; + this.db = UserPreferences.getDatabaseConnectionInfo(); } - return true; + public Path getCaseInputFolder() { + return this.caseInputFolder; + } + + public Path getCaseOutputFolder() { + return this.caseOutputFolder; + } + + Path getImageInputFolder() { + return this.imageInputFolder; + } + + Path getImageOutputFolder() { + return this.imageOutputFolder; + } + + String getOldCaseName() { + return this.oldCaseName; + } + + String getNewCaseName() { + return this.newCaseName; + } + + boolean getCopySourceImages() { + return this.copySourceImages; + } + + boolean getDeleteCase() { + return this.deleteCase; + } + + String getPostgreSQLDbName() { + return this.postgreSQLDbName; + } + + String getAutFileName() { + return this.autFileName; + } + + String getRawFolderName() { + return this.rawFolderName; + } + + CaseDbConnectionInfo getDb() { + return this.db; + } + + void setPostgreSQLDbName(String dbName) { + this.postgreSQLDbName = dbName; + } } /** - * Handles most of the heavy lifting for importing cases from single-user to + * Handles the heavy lifting for importing a case from single-user to * multi-user. Creates new .aut file, moves folders to the right place, * imports the database, and updates paths within the database. * * @param icd the Import Case Data for the current case * - * @return true if successful, false if not + * @throws java.lang.Exception */ - private boolean processCase(ImportCaseData icd) { - boolean result = true; - try { - log("Importing case " + icd.getSpecificCaseInputFolder().toString() + " to " + caseOutputFolder + "\\" + icd.getOldCaseName()); //NON-NLS + public static void importCase(ImportCaseData icd) throws Exception { - checkInputDatabase(icd.getSpecificCaseInputFolder()); + Class.forName("org.postgresql.Driver"); //NON-NLS - oldXmlCaseManagement = new XMLCaseManagement(); - - // read old xml config - oldXmlCaseManagement.open(icd.getSpecificCaseInputFolder().resolve(TimeStampUtils.removeTimeStamp(icd.getOldCaseName()) + DOTAUT).toString()); - if (oldXmlCaseManagement.getCaseType() == CaseType.MULTI_USER_CASE) { - throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.AlreadyMultiUser")); - } - - prepareOutput(icd); - - // create sanitized names for database and solr - String caseName = TimeStampUtils.removeTimeStamp(icd.getNewCaseName()); // caseName holds the deconflicted, timestampless name of the case - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); //NON-NLS - Date date = new Date(); - String santizedDatabaseName = Case.sanitizeCaseName(caseName); - String dbName = santizedDatabaseName + "_" + dateFormat.format(date); //NON-NLS - String solrName = dbName; - - icd.setSpecificImageOutputFolder(Paths.get(imageOutputFolder, icd.getNewCaseName())); - copyResults(icd); // Copy items to new hostname folder structure - dbName = importDb(dbName, icd.getSpecificCaseInputFolder(), icd.getSpecificCaseOutputFolder().toString()); // Change from SQLite to PostgreSQL - - fixPaths(icd, dbName); // Update paths in DB - - copyImages(icd); // Copy images over - - // create new XML config - newXmlCaseManagement = new XMLCaseManagement(); - newXmlCaseManagement.create(icd.getSpecificCaseOutputFolder().toString(), - caseName, - oldXmlCaseManagement.getCaseExaminer(), - oldXmlCaseManagement.getCaseNumber(), - CaseType.MULTI_USER_CASE, dbName, solrName); - - // Set created date. This calls writefile, no need to call it again - newXmlCaseManagement.setCreatedDate(oldXmlCaseManagement.getCreatedDate()); - - // At this point the import has been finished successfully so we can delete the original case - // (if requested). This *should* be fairly safe - at this point we know there was an autopsy file - // and database in the given directory so the user shouldn't be able to accidently blow away - // their C drive. - if (deleteCase) { - log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.DeletingCase") + " " + icd.getSpecificCaseInputFolder().toString()); - FileUtils.deleteDirectory(icd.getSpecificCaseInputFolder().toFile()); - } - - result = reportLostImages(db, dbName); - - log("Finished importing case " + icd.getSpecificCaseInputFolder().toString() + " to " + icd.getSpecificCaseOutputFolder().toString()); - } catch (Exception exp) { - /// clean up here - log("Error processing " + icd.specificCaseInputFolder.toString() + ": " + exp.getMessage()); - result = false; + // Make sure there is a SQLite databse file + Path oldDatabasePath = icd.getCaseInputFolder().resolve(AUTOPSY_DB_FILE); + if (false == oldDatabasePath.toFile().exists()) { + throw new Exception(NbBundle.getMessage(SingleUserCaseConverter.class, "SingleUserCaseConverter.BadDatabaseFileName")); //NON-NLS } - return result; - } - /** - * Searches for images in the filesystem. It parses the new PostgreSQL - * database to find images that should exist, and notifies when they do not. - * - * @param db database credentials - * @param dbName the name of the database - * - * @return true if successfully found all images, false otherwise. - */ - private boolean reportLostImages(CaseDbConnectionInfo db, String dbName) { - boolean result = true; - if (copySourceImages) { - try { - Class.forName("org.postgresql.Driver"); //NON-NLS - Connection dbConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS - Statement inputStatement = dbConnection.createStatement(); - ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS - - while (inputResultSet.next()) { - - File theFile = new File(inputResultSet.getString(2)); - if (false == theFile.exists()) { - log("Unable to find image " + theFile.toString() + " for case " + dbName); - result = false; - } - } - } catch (Exception ex) { - log("Error. Unable to verify images were copied."); - result = false; - } + // Read old xml config + XMLCaseManagement oldXmlCaseManagement = new XMLCaseManagement(); + oldXmlCaseManagement.open(icd.getCaseInputFolder().resolve(icd.getAutFileName()).toString()); + if (oldXmlCaseManagement.getCaseType() == CaseType.MULTI_USER_CASE) { + throw new Exception(NbBundle.getMessage(SingleUserCaseConverter.class, "SingleUserCaseConverter.AlreadyMultiUser")); //NON-NLS + } + + // Create sanitized names for PostgreSQL and Solr + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); //NON-NLS + Date date = new Date(); + String dbName = Case.sanitizeCaseName(icd.getNewCaseName()) + "_" + dateFormat.format(date); //NON-NLS + String solrName = dbName; + icd.setPostgreSQLDbName(dbName); + + // Copy items to new hostname folder structure + copyResults(icd); + + // Convert from SQLite to PostgreSQL + importDb(icd); + + // Update paths inside databse + fixPaths(icd); + + // Copy images + copyImages(icd); + + // Create new .aut file + XMLCaseManagement newXmlCaseManagement = new XMLCaseManagement(); + newXmlCaseManagement.create(icd.getCaseOutputFolder().toString(), + icd.getNewCaseName(), + oldXmlCaseManagement.getCaseExaminer(), + oldXmlCaseManagement.getCaseNumber(), + CaseType.MULTI_USER_CASE, dbName, solrName); + + // Set created date. This calls writefile, no need to call it again + newXmlCaseManagement.setCreatedDate(oldXmlCaseManagement.getCreatedDate()); + + // At this point the import has been finished successfully so we can delete the original case + // (if requested). This *should* be fairly safe - at this point we know there was an autopsy file + // and database in the given directory so the user shouldn't be able to accidently blow away + // their C drive. + if (icd.getDeleteCase()) { + FileUtils.deleteDirectory(icd.getCaseInputFolder().toFile()); } - return result; } /** * Figure out the input folder for images and return it. * - * @param icd the import case data for the current case + * @param icd the Import Case Data for the current case * - * @return the name of the proper input folder + * @return the name of the proper Image input folder */ - private File findInputFolder(ImportCaseData icd) { + private static File findInputFolder(ImportCaseData icd) { - File thePath = icd.getSpecificImageInputFolder().resolve(icd.getOldCaseName()).toFile(); + File thePath = icd.getImageInputFolder().resolve(icd.getOldCaseName()).toFile(); if (thePath.isDirectory()) { /// we've found it return thePath; - } else { - return icd.getSpecificImageInputFolder().toFile(); } + thePath = icd.getImageInputFolder().resolve(icd.getRawFolderName()).toFile(); + if (thePath.isDirectory()) { + /// we've found it + return thePath; + } + return icd.getImageInputFolder().toFile(); } /** - * Ensure the input source has an autopsy.db and exists. + * Copy all folders at the base level to the new scheme involving hostname. + * Also take care of a few files such as logs, timeline database, etc. * - * @param caseInput The folder containing a case to import. - * - * @throws Exception - */ - private void checkInputDatabase(Path caseInput) throws Exception { - Path path = caseInput.resolve(AUTOPSY_DB_FILE); - if (false == path.toFile().exists()) { - throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.BadDatabaseFileName")); - } - } - - /** - * Handles case folder, PosgreSql database, and Solr core name deconfliction - * Sets the appropriate portions of the ImportCaseData object. - * - * @param icd the case data folder name - * - * @throws Exception - */ - private void prepareOutput(ImportCaseData icd) throws Exception { - // test for uniqueness - String caseName = icd.getOldCaseName(); - File specificOutputFolder = Paths.get(caseOutputFolder, caseName).toFile(); - String sanitizedCaseName = caseName; - if (specificOutputFolder.exists()) { - // not unique. add numbers before timestamp to specific case name - String timeStamp = TimeStampUtils.getTimeStampOnly(caseName); //NON-NLS - sanitizedCaseName = TimeStampUtils.removeTimeStamp(caseName); - - int number = 1; - String temp = ""; //NON-NLS - while (specificOutputFolder.exists()) { - if (number == Integer.MAX_VALUE) { - // oops. it never became unique. give up. - throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.NonUniqueOutputFolder") + sanitizedCaseName); - } - temp = sanitizedCaseName + "_" + Integer.toString(number) + timeStamp; //NON-NLS - specificOutputFolder = Paths.get(caseOutputFolder, temp).toFile(); - ++number; - } - sanitizedCaseName = temp; - } - - if (addTimestamp && !TimeStampUtils.endsWithTimeStamp(sanitizedCaseName)) { - sanitizedCaseName += "_" + TimeStampUtils.createTimeStamp(); - } - - Path caseOutputPath = Paths.get(caseOutputFolder, sanitizedCaseName); - icd.setNewCaseName(sanitizedCaseName); - icd.setSpecificCaseOutputFolder(caseOutputPath); - caseOutputPath.toFile().mkdirs(); // create output folders just in case - } - - /** - * Copy all the folders at the base level to the new scheme involving - * hostname. Also take care of a few files such as logs, timeline db, etc. - * - * @param icd the case data + * @param icd the Import Case Data for the current case * * @throws IOException */ - private void copyResults(ImportCaseData icd) throws IOException { + private static void copyResults(ImportCaseData icd) throws IOException { /// get hostname String hostName = NetworkUtils.getLocalHostName(); - Path destination; - Path source; - source = icd.getSpecificCaseInputFolder(); + Path destination; + Path source = icd.getCaseInputFolder(); if (source.toFile().exists()) { - destination = icd.getSpecificCaseOutputFolder().resolve(hostName); + destination = icd.getCaseOutputFolder().resolve(hostName); FileUtils.copyDirectory(source.toFile(), destination.toFile()); } - source = icd.getSpecificCaseInputFolder().resolve(TIMELINE_FILE); + source = icd.getCaseInputFolder().resolve(TIMELINE_FILE); if (source.toFile().exists()) { - destination = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, MODULE_FOLDER, TIMELINE_FOLDER, TIMELINE_FILE); + destination = Paths.get(icd.getCaseOutputFolder().toString(), hostName, MODULE_FOLDER, TIMELINE_FOLDER, TIMELINE_FILE); FileUtils.copyFile(source.toFile(), destination.toFile()); } - source = icd.getSpecificCaseInputFolder().resolve(AIM_LOG_FILE_NAME); - destination = icd.getSpecificCaseOutputFolder().resolve(AIM_LOG_FILE_NAME); - if (source.toFile().exists()) { - FileUtils.copyFile(source.toFile(), destination.toFile()); - - } - try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(destination.toString(), true)))) { - out.println(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.ImportedAsMultiUser") + new Date()); - } catch (IOException e) { - // if unable to log it, no problem - } - - // Remove the single-user .aut file, database, Timeline database and log - File oldAutopsyFile = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, TimeStampUtils.removeTimeStamp(icd.getOldCaseName()) + DOTAUT).toFile(); + // Remove the single-user .aut file from the multi-user folder + File oldAutopsyFile = Paths.get(icd.getCaseOutputFolder().toString(), hostName, icd.getOldCaseName() + DOTAUT).toFile(); if (oldAutopsyFile.exists()) { oldAutopsyFile.delete(); } - File oldDatabaseFile = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, AUTOPSY_DB_FILE).toFile(); + // Remove the single-user database file from the multi-user folder + File oldDatabaseFile = Paths.get(icd.getCaseOutputFolder().toString(), hostName, AUTOPSY_DB_FILE).toFile(); if (oldDatabaseFile.exists()) { oldDatabaseFile.delete(); } - File oldTimelineFile = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, TIMELINE_FILE).toFile(); + // Remove the single-user Timeline file from the multi-user folder + File oldTimelineFile = Paths.get(icd.getCaseOutputFolder().toString(), hostName, TIMELINE_FILE).toFile(); if (oldTimelineFile.exists()) { oldTimelineFile.delete(); } - - File oldIngestLog = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, AIM_LOG_FILE_NAME).toFile(); - if (oldIngestLog.exists()) { - oldIngestLog.delete(); - } } /** @@ -414,29 +281,24 @@ public class SingleUserCaseImporter implements Runnable { * data while loading it over. Fixing paths is done once the database is * completely imported. * - * @param dbName the name of the database, could have name collision - * @param inputPath the path to the input case - * @param outputCaseName the name of the output case, could have extra - * digits to avoid name collisions - * - * @return the deconflicted name of the PostgreSQL database that was created + * @param icd the Import Case Data for the current case * + * @throws Exception * @throws SQLException * @throws ClassNotFoundException */ - private String importDb(String dbName, Path inputPath, String outputCaseName) throws SQLException, ClassNotFoundException, Exception { + private static void importDb(ImportCaseData icd) throws SQLException, ClassNotFoundException, Exception { // deconflict the database name - dbName = deconflictDatabaseName(db, dbName); + deconflictDatabaseName(icd); // Create a new database via SleuthkitCase - SleuthkitCase newCase = SleuthkitCase.newCase(dbName, db, outputCaseName); + SleuthkitCase newCase = SleuthkitCase.newCase(icd.getPostgreSQLDbName(), icd.getDb(), icd.getCaseOutputFolder().toString()); newCase.close(); /// Migrate from SQLite to PostgreSQL Class.forName("org.sqlite.JDBC"); //NON-NLS - Connection sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + inputPath.resolve(AUTOPSY_DB_FILE).toString(), "", ""); //NON-NLS - - Connection postgresqlConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS + Connection sqliteConnection = getSQLiteConnection(icd); + Connection postgreSQLConnection = getPostgreSQLConnection(icd); // blackboard_artifact_types Statement inputStatement = sqliteConnection.createStatement(); @@ -451,14 +313,14 @@ public class SingleUserCaseImporter implements Runnable { if (value > biggestPK) { biggestPK = value; } - Statement check = postgresqlConnection.createStatement(); + Statement check = postgreSQLConnection.createStatement(); ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_artifact_types WHERE artifact_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist String sql = "INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name) VALUES (" + value + ", '" + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "'," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 3, 1); pst.executeUpdate(); } @@ -468,7 +330,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE blackboard_artifact_types_artifact_type_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // blackboard_attribute_types @@ -482,7 +344,7 @@ public class SingleUserCaseImporter implements Runnable { if (value > biggestPK) { biggestPK = value; } - Statement check = postgresqlConnection.createStatement(); + Statement check = postgreSQLConnection.createStatement(); ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_attribute_types WHERE attribute_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist String sql = "INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name) VALUES (" @@ -490,7 +352,7 @@ public class SingleUserCaseImporter implements Runnable { + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "'," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 3, 1); pst.executeUpdate(); } @@ -500,7 +362,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE blackboard_attribute_types_attribute_type_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_objects @@ -509,7 +371,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_objects"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -525,7 +387,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_objects_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_image_names, no primary key @@ -533,7 +395,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { outputStatement.executeUpdate("INSERT INTO tsk_image_names (obj_id, name, sequence) VALUES (" + inputResultSet.getLong(1) + ",'" @@ -552,7 +414,6 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_info"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -567,7 +428,7 @@ public class SingleUserCaseImporter implements Runnable { + " ? ," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 4, 1); populateNullableString(pst, inputResultSet, 6, 2); populateNullableString(pst, inputResultSet, 7, 3); @@ -579,7 +440,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_image_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_fs_info @@ -604,7 +465,7 @@ public class SingleUserCaseImporter implements Runnable { + inputResultSet.getLong(8) + "," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 9, 1); pst.executeUpdate(); @@ -614,7 +475,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_fs_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_files_path @@ -623,7 +484,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files_path"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -638,7 +499,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_files_path_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_files @@ -679,7 +540,7 @@ public class SingleUserCaseImporter implements Runnable { + getNullableInt(inputResultSet, 24) + "," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 23, 1); populateNullableString(pst, inputResultSet, 25, 2); pst.executeUpdate(); @@ -690,7 +551,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_files_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_file_layout, no primary key @@ -698,7 +559,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_file_layout"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { outputStatement.executeUpdate("INSERT INTO tsk_file_layout (obj_id, byte_start, byte_len, sequence) VALUES (" + inputResultSet.getLong(1) + "," @@ -717,9 +578,9 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_db_info"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { - Statement check = postgresqlConnection.createStatement(); + Statement check = postgreSQLConnection.createStatement(); ResultSet checkResult = check.executeQuery("SELECT * FROM tsk_db_info WHERE schema_ver=" + inputResultSet.getInt(1) + " AND tsk_ver=" + inputResultSet.getInt(2)); //NON-NLS if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist outputStatement.executeUpdate("INSERT INTO tsk_db_info (schema_ver, tsk_ver) VALUES (" @@ -750,7 +611,7 @@ public class SingleUserCaseImporter implements Runnable { + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(3)) + "','" + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(4)) + "')"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 2, 1); pst.executeUpdate(); @@ -760,7 +621,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tag_names_tag_name_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // reports @@ -769,7 +630,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM reports"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -788,7 +649,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE reports_report_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // blackboard_artifacts @@ -797,7 +658,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifacts"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -814,7 +675,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE blackboard_artifacts_artifact_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // blackboard_attributes, no primary key @@ -835,7 +696,7 @@ public class SingleUserCaseImporter implements Runnable { + getNullableInt(inputResultSet, 9) + "," + getNullableLong(inputResultSet, 10) + "," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 3, 1); populateNullableString(pst, inputResultSet, 4, 2); populateNullableByteArray(pst, inputResultSet, 7, 3); @@ -868,7 +729,7 @@ public class SingleUserCaseImporter implements Runnable { + inputResultSet.getLong(4) + "," + " ? ," + inputResultSet.getInt(6) + ")"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 5, 1); pst.executeUpdate(); @@ -878,7 +739,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_vs_parts_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_vs_info @@ -887,7 +748,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_vs_info"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -905,7 +766,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_vs_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_files_derived @@ -923,7 +784,7 @@ public class SingleUserCaseImporter implements Runnable { + value + "," + inputResultSet.getLong(2) + "," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 3, 1); pst.executeUpdate(); @@ -933,7 +794,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_files_derived_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // tsk_files_derived_method @@ -952,7 +813,7 @@ public class SingleUserCaseImporter implements Runnable { + inputResultSet.getString(2) + "','" + inputResultSet.getString(3) + "'," + " ? )"; //NON-NLS - PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + PreparedStatement pst = postgreSQLConnection.prepareStatement(sql); populateNullableString(pst, inputResultSet, 4, 1); pst.executeUpdate(); @@ -962,7 +823,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE tsk_files_derived_method_derived_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // content_tags @@ -971,7 +832,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM content_tags"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -991,7 +852,7 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE content_tags_tag_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // blackboard_artifact_tags @@ -1000,7 +861,7 @@ public class SingleUserCaseImporter implements Runnable { inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifact_tags"); //NON-NLS while (inputResultSet.next()) { - outputStatement = postgresqlConnection.createStatement(); + outputStatement = postgreSQLConnection.createStatement(); try { long value = inputResultSet.getLong(1); if (value > biggestPK) { @@ -1018,55 +879,49 @@ public class SingleUserCaseImporter implements Runnable { } } } - numberingPK = postgresqlConnection.createStatement(); + numberingPK = postgreSQLConnection.createStatement(); numberingPK.execute("ALTER SEQUENCE blackboard_artifact_tags_tag_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS sqliteConnection.close(); - postgresqlConnection.close(); - - return dbName; + postgreSQLConnection.close(); } /** - * Checks that our database name is unique. If it is not, attempts to add + * Checks that the database name is unique. If it is not, attempts to add * numbers to it until it is unique. Gives up if it goes through all * positive integers without finding a unique name. * - * @param db Database credentials - * @param baseDbName proposed name of the database to check for collisions + * @param icd the Import Case Data for the current case * - * @return name to use for the new database. Could be the name passed in. - * - * @throws ClassNotFoundException - * @throws SQLException * @throws Exception + * @throws SQLException + * @throws ClassNotFoundException */ - private String deconflictDatabaseName(CaseDbConnectionInfo db, String baseDbName) throws ClassNotFoundException, SQLException, Exception { + private static void deconflictDatabaseName(ImportCaseData icd) throws ClassNotFoundException, SQLException, Exception { - Class.forName("org.postgresql.Driver"); //NON-NLS - Connection dbNameConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Connection postgreSQLConnection = getPostgreSQLConnection(icd, POSTGRES_DEFAULT_DB_NAME); int number = 1; boolean unique = false; - String sanitizedDbName = baseDbName; + String sanitizedDbName = icd.getPostgreSQLDbName(); if (sanitizedDbName.length() > MAX_DB_NAME_LENGTH) { sanitizedDbName = sanitizedDbName.substring(0, MAX_DB_NAME_LENGTH); } - if (dbNameConnection != null) { + if (postgreSQLConnection != null) { while (unique == false) { - Statement st = dbNameConnection.createStatement(); + Statement st = postgreSQLConnection.createStatement(); ResultSet answer = st.executeQuery("SELECT datname FROM pg_catalog.pg_database WHERE LOWER(datname) LIKE LOWER('" + sanitizedDbName + "%')"); //NON-NLS if (!answer.next()) { unique = true; } else { - // not unique. add numbers before dbName. + // not unique. add numbers to db name. if (number == Integer.MAX_VALUE) { // oops. it never became unique. give up. - throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.NonUniqueDatabaseName")); + throw new Exception(NbBundle.getMessage(SingleUserCaseConverter.class, "SingleUserCaseConverter.NonUniqueDatabaseName")); //NON-NLS } - sanitizedDbName = "_" + Integer.toString(number) + "_" + baseDbName; //NON-NLS + sanitizedDbName = "db_" + Integer.toString(number) + "_" + icd.getPostgreSQLDbName(); //NON-NLS // Chop full db name to 63 characters (max for PostgreSQL) if (sanitizedDbName.length() > MAX_DB_NAME_LENGTH) { @@ -1075,35 +930,34 @@ public class SingleUserCaseImporter implements Runnable { ++number; } } - dbNameConnection.close(); + postgreSQLConnection.close(); } else { // Could be caused by database credentials, using user accounts that // can not check if other databases exist, so allow it to continue - log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.PotentiallyNonUniqueDatabaseName")); } - return sanitizedDbName; + icd.setPostgreSQLDbName(sanitizedDbName); } /** - * Get the images from the old case and place them in the central - * repository, if the user chose to. + * Get the images from the old case and stage them for the new case, if the + * user chose to copy images over. * - * @param icd the Import Case Data + * @param icd the Import Case Data for the current case * * @throws IOException */ - private void copyImages(ImportCaseData icd) throws IOException { - if (copySourceImages) { + private static void copyImages(ImportCaseData icd) throws Exception { + if (icd.getCopySourceImages()) { File imageSource = findInputFolder(icd); // Find the folder for the input images - File imageDestination = new File(icd.getSpecificImageOutputFolder().toString()); + File imageDestination = new File(icd.getImageOutputFolder().toString()); // If we can find the input images, copy if needed. if (imageSource.exists()) { FileUtils.copyDirectory(imageSource, imageDestination); } else { - log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.UnableToCopySourceImages")); + throw new Exception(NbBundle.getMessage(SingleUserCaseConverter.class, "SingleUserCaseConverter.UnableToCopySourceImages")); //NON-NLS } } } @@ -1112,16 +966,19 @@ public class SingleUserCaseImporter implements Runnable { * Fix up any paths in the database that refer to items that have moved. * Candidates include events.db, input images, reports, file paths, etc. * - * @param icd the import case data for the current case - * @param dbName the name of the database + * @param icd the Import Case Data for the current case + * + * @throws Exception + * @throws SQLExceptionException */ - private void fixPaths(ImportCaseData icd, String dbName) throws SQLException { + private static void fixPaths(ImportCaseData icd) throws SQLException, Exception { /// Fix paths in reports, tsk_files_path, and tsk_image_names tables - String input = icd.getSpecificImageInputFolder().toString(); - String output = icd.getSpecificImageOutputFolder().toString(); + String input = icd.getImageInputFolder().toString(); + String output = icd.getImageOutputFolder().toString(); + + Connection postgresqlConnection = getPostgreSQLConnection(icd); - Connection postgresqlConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS if (postgresqlConnection != null) { String hostName = NetworkUtils.getLocalHostName(); @@ -1133,9 +990,9 @@ public class SingleUserCaseImporter implements Runnable { updateStatement = postgresqlConnection.createStatement(); updateStatement.executeUpdate("UPDATE tsk_files_path SET path=CONCAT('" + hostName + "\\', path) WHERE path IS NOT NULL AND path != ''"); //NON-NLS - String caseName = TimeStampUtils.removeTimeStamp(icd.getOldCaseName()).toLowerCase(); + String caseName = icd.getRawFolderName().toLowerCase(); - if (copySourceImages) { + if (icd.getCopySourceImages()) { // update path for images Statement inputStatement = postgresqlConnection.createStatement(); ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS @@ -1155,7 +1012,7 @@ public class SingleUserCaseImporter implements Runnable { } postgresqlConnection.close(); } else { - log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.CanNotOpenDatabase")); + throw new Exception(NbBundle.getMessage(SingleUserCaseConverter.class, "SingleUserCaseConverter.CanNotOpenDatabase")); //NON-NLS } } @@ -1170,7 +1027,7 @@ public class SingleUserCaseImporter implements Runnable { * * @throws SQLException */ - private String getNullableInt(ResultSet rs, int index) throws SQLException { + private static String getNullableInt(ResultSet rs, int index) throws SQLException { int value = rs.getInt(index); if (rs.wasNull()) { return "NULL"; //NON-NLS @@ -1190,7 +1047,7 @@ public class SingleUserCaseImporter implements Runnable { * * @throws SQLException */ - private String getNullableLong(ResultSet rs, int index) throws SQLException { + private static String getNullableLong(ResultSet rs, int index) throws SQLException { long value = rs.getLong(index); if (rs.wasNull()) { return "NULL"; //NON-NLS @@ -1210,7 +1067,7 @@ public class SingleUserCaseImporter implements Runnable { * * @throws SQLException */ - private void populateNullableString(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { + private static void populateNullableString(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { String nullableString = rs.getString(rsIndex); if (rs.wasNull()) { pst.setNull(psIndex, java.sql.Types.NULL); @@ -1230,7 +1087,7 @@ public class SingleUserCaseImporter implements Runnable { * * @throws SQLException */ - private void populateNullableByteArray(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { + private static void populateNullableByteArray(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { byte[] nullableBytes = rs.getBytes(rsIndex); if (rs.wasNull()) { pst.setNull(psIndex, java.sql.Types.NULL); @@ -1250,7 +1107,7 @@ public class SingleUserCaseImporter implements Runnable { * * @throws SQLException */ - private void populateNullableNumeric(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { + private static void populateNullableNumeric(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { double nullableNumeric = rs.getDouble(rsIndex); if (rs.wasNull()) { pst.setNull(psIndex, java.sql.Types.NULL); @@ -1259,286 +1116,49 @@ public class SingleUserCaseImporter implements Runnable { } } - private class ImportCaseData { - - private Path specificCaseInputFolder; - private Path specificCaseOutputFolder; - private Path specificImageInputFolder; - private Path specificImageOutputFolder; - private String relativeCaseName; - private String newCaseName; - private String oldCaseName; - - public Path getSpecificCaseInputFolder() { - return specificCaseInputFolder; - } - - public Path getSpecificCaseOutputFolder() { - return specificCaseOutputFolder; - } - - public Path getSpecificImageInputFolder() { - return specificImageInputFolder; - } - - public Path getSpecificImageOutputFolder() { - return specificImageOutputFolder; - } - - public String getRelativeCaseName() { - return relativeCaseName; - } - - public String getOldCaseName() { - return oldCaseName; - } - - public String getNewCaseName() { - return newCaseName; - } - - public void setSpecificCaseInputFolder(Path caseInputFolder) { - this.specificCaseInputFolder = caseInputFolder; - } - - public void setSpecificCaseOutputFolder(Path caseOutputFolder) { - this.specificCaseOutputFolder = caseOutputFolder; - } - - public void setSpecificImageInputFolder(Path imageInputFolder) { - this.specificImageInputFolder = imageInputFolder; - } - - public void setSpecificImageOutputFolder(Path imageOutputFolder) { - this.specificImageOutputFolder = imageOutputFolder; - } - - public void setRelativeCaseName(Path input, Path aut) { - this.relativeCaseName = input.relativize(aut).toString(); - } - - public void setOldCaseName(String oldCaseName) { - this.oldCaseName = oldCaseName; - } - - public void setNewCaseName(String newCaseName) { - this.newCaseName = newCaseName; - } - - public ImportCaseData(Path p) { - this.specificCaseInputFolder = p; - this.oldCaseName = p.getFileName().toString(); - this.specificCaseOutputFolder = null; - this.specificImageInputFolder = null; - this.specificImageOutputFolder = null; - this.relativeCaseName = null; - this.newCaseName = null; - } - } - /** - * This is the runnable's run method. It causes the iteration on all .aut - * files in the path, calling processCase for each one. - */ - @Override - public void run() { - openLog(); - boolean result = true; - - // iterate for .aut files - FindDotAutFolders dotAutFolders = new FindDotAutFolders(); - try { - Path walked = Files.walkFileTree(caseInputFolder, dotAutFolders); - } catch (IOException ex) { - log(ex.getMessage()); - result = false; - } - - ArrayList ableToProcess = new ArrayList<>(); - ArrayList unableToProcess = new ArrayList<>(); - - // validate we can convert this .aut file, one by one - for (Path p : dotAutFolders.getTheList()) { - ImportCaseData icd = new ImportCaseData(p); - icd.setRelativeCaseName(caseInputFolder, p); - if (canProcess(icd)) { - ableToProcess.add(icd); - } else { - unableToProcess.add(icd); - } - } - - StringBuilder casesThatWillBeProcessed = new StringBuilder(); - StringBuilder casesThatWillNotBeProcessed = new StringBuilder(); - - casesThatWillBeProcessed.append(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.WillImport")).append(SEP); // NON-NLS - if (ableToProcess.isEmpty()) { - casesThatWillBeProcessed.append(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.None")).append(SEP); // NON-NLS - } else { - for (ImportCaseData icd : ableToProcess) { - casesThatWillBeProcessed.append(icd.getSpecificCaseInputFolder().toString()).append(SEP); - } - } - - if (!unableToProcess.isEmpty()) { - casesThatWillNotBeProcessed.append(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.WillNotImport")).append(SEP); // NON-NLS - for (ImportCaseData icd : unableToProcess) { - casesThatWillNotBeProcessed.append(icd.getSpecificCaseInputFolder().toString()).append(SEP); - } - } - - JTextArea jta = new JTextArea(casesThatWillBeProcessed.toString() + SEP + casesThatWillNotBeProcessed.toString()); - jta.setEditable(false); - JScrollPane jsp = new JScrollPane(jta) { - private static final long serialVersionUID = 1L; - - @Override - public Dimension getPreferredSize() { - return new Dimension(700, 480); - } - }; - - SwingUtilities.invokeLater(() -> { - userAnswer = JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), - jsp, - NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.ContinueWithImport"), // NON-NLS - OK_CANCEL_OPTION); - synchronized (threadWaitNotifyLock) { - threadWaitNotifyLock.notify(); - } - }); - - synchronized (threadWaitNotifyLock) { - try { - threadWaitNotifyLock.wait(); - } catch (InterruptedException ex) { - log("Unable to wait for user input"); - } - } - - if (userAnswer == JOptionPane.OK_OPTION) { - // feed .aut files in one by one for processing - for (ImportCaseData icd : ableToProcess) { - if (false == processCase(icd)) { - result = false; - } - } - closeLog(result); - if (notifyOnComplete != null) { - notifyOnComplete.importDoneCallback(result, ""); // NON-NLS - } - } else { - closeLog(result); - if (notifyOnComplete != null) { - notifyOnComplete.importDoneCallback(false, NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.Cancelled")); // NON-NLS - } - } - } - - /** - * Open the case import log in the base output folder. + * Open the PostgreSQL database * + * @param icd Import Case Data holding connection credentials + * + * @return returns a Connection + * + * @throws SQLException if unable to open */ - private void openLog() { - File temp = new File(caseOutputFolder); - temp.mkdirs(); - File logFile = Paths.get(caseOutputFolder, CASE_IMPORT_LOG_FILE).toFile(); - try { - writer = new PrintWriter(new BufferedWriter(new FileWriter(logFile, logFile.exists())), true); - } catch (IOException ex) { - writer = null; - Logger.getLogger(SingleUserCaseImporter.class.getName()).log(Level.WARNING, "Error opening log file " + logFile.toString(), ex); - } - log("Starting batch processing of " + caseInputFolder.toString() + " to " + caseOutputFolder); + private static Connection getPostgreSQLConnection(ImportCaseData icd) throws SQLException { + return getPostgreSQLConnection(icd, icd.getPostgreSQLDbName()); } /** - * Log a message to the case import log in the base output folder. + * Open the PostgreSQL database * - * @param message the message to log. + * @param icd Import Case Data holding connection credentials + * @param dbName the name of the database to open + * + * @return returns a Connection + * + * @throws SQLException if unable to open */ - private void log(String message) { - if (writer != null) { - writer.println(String.format("%s %s", simpleDateFormat.format((Date.from(Instant.now()).getTime())), message)); //NON-NLS - } + private static Connection getPostgreSQLConnection(ImportCaseData icd, String dbName) throws SQLException { + return DriverManager.getConnection("jdbc:postgresql://" + + icd.getDb().getHost() + ":" + + icd.getDb().getPort() + "/" + + dbName, + icd.getDb().getUserName(), + icd.getDb().getPassword()); //NON-NLS } /** + * Open the SQLite database * - * Close the case import log in the base output folder. + * @param icd Import Case Data holding database path details * - * @param result this informs the log if the end result was successful or - * not. True if all was successful, false otherwise. + * @return returns a Connection + * + * @throws SQLException if unable to open */ - private void closeLog(boolean result) { - log("Completed batch processing of " + caseInputFolder.toString() + " to " + caseOutputFolder + ". Batch processing result: " + ((result == true) ? "Success" : "Failure")); - if (writer != null) { - writer.close(); - } + private static Connection getSQLiteConnection(ImportCaseData icd) throws SQLException { + return DriverManager.getConnection("jdbc:sqlite:" + icd.getCaseInputFolder().resolve(AUTOPSY_DB_FILE).toString(), "", ""); //NON-NLS } - /** - * This class extends SimpleFileVisitor to find all the cases to process - * based upon .aut files. - */ - private class FindDotAutFolders extends SimpleFileVisitor { - - private final ArrayList theList; - - public FindDotAutFolders() { - this.theList = new ArrayList<>(); - } - - /** - * Handle comparing .aut file and containing folder names without - * timestamps on either one. It strips them off if they exist. - * - * @param directory the directory we are currently visiting. - * @param attrs file attributes. - * - * @return Continue if we want to carry one, SKIP_SUBTREE if we've found - * a .aut file, precluding searching any deeper into this - * folder. - * - * @throws IOException - */ - @Override - public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException { - // find all files that end in .aut - File[] dotAutFiles = directory.toFile().listFiles((File dir, String name) -> name.toLowerCase().endsWith(DOTAUT)); - - for (File specificFile : dotAutFiles) { - // if it ends in a timestamp, strip it off - String sanitizedCaseName = specificFile.getName(); - if (TimeStampUtils.endsWithTimeStamp(sanitizedCaseName)) { - sanitizedCaseName = sanitizedCaseName.substring(0, sanitizedCaseName.length() - TimeStampUtils.getTimeStampLength()); - } - - // if folder ends in a timestamp, strip it off - String sanitizedFolderName = directory.getFileName().toString(); - if (TimeStampUtils.endsWithTimeStamp(sanitizedFolderName)) { - sanitizedFolderName = sanitizedFolderName.substring(0, sanitizedFolderName.length() - TimeStampUtils.getTimeStampLength()); - } - - // If file and folder match, found leaf node case - if (sanitizedCaseName.toLowerCase().startsWith(sanitizedFolderName.toLowerCase())) { - theList.add(directory); - return FileVisitResult.SKIP_SUBTREE; - } - } - - // If no matching .aut files, traverse subfolders - return FileVisitResult.CONTINUE; - } - - /** - * This returns the list of folders we've found that need to be looked - * at for possible import as multi-user cases. - * - * @return the theList - */ - public ArrayList getTheList() { - return theList; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index a9909593b0..03c97a0479 100755 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -30,6 +30,7 @@ import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.TskData.DbType; @@ -39,6 +40,7 @@ import org.sleuthkit.datamodel.TskData.DbType; */ public final class UserPreferences { + private static final boolean isWindowsOS = PlatformUtil.isWindowsOS(); private static final Preferences preferences = NbPreferences.forModule(UserPreferences.class); public static final String KEEP_PREFERRED_VIEWER = "KeepPreferredViewer"; // NON-NLS public static final String HIDE_KNOWN_FILES_IN_DATA_SOURCES_TREE = "HideKnownFilesInDataSourcesTree"; //NON-NLS @@ -177,6 +179,9 @@ public final class UserPreferences { } public static boolean getIsMultiUserModeEnabled() { + if (!isWindowsOS) { + return false; + } return preferences.getBoolean(IS_MULTI_USER_MODE_ENABLED, false); } diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 42315074d4..8e71419b45 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -14,6 +14,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 06146d2d8f..7364d72922 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -27,7 +27,7 @@ Format_OperatingSystem_Value={0} version {1} running on {2} LBL_Copyright=
Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2014.
URL_ON_IMG=http://www.sleuthkit.org/ -URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/3.1/ +URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.0/ FILE_FOR_LOCAL_HELP=file:/// INDEX_FOR_LOCAL_HELP=/docs/index.html @@ -153,6 +153,7 @@ MultiUserSettingsPanel.lbSolrSettings.text=Solr Settings MultiUserSettingsPanel.cbEnableMultiUser.text=Enable Multi-user cases MultiUserSettingsPanel.lbDatabaseSettings.text=Database Settings MultiUserSettingsPanel.validationErrMsg.incomplete=Fill in all values +MultiUserSettingsPanel.nonWindowsOs.msg=Multi-user cases are only available on Windows platforms MultiUserSettingsPanel.validationErrMsg.invalidDatabasePort=Invalid database port number MultiUserSettingsPanel.validationErrMsg.invalidMessageServicePort=Invalid message service port number MultiUserSettingsPanel.validationErrMsg.invalidIndexingServerPort=Invalid Solr server port number @@ -185,9 +186,9 @@ MultiUserSettingsPanel.bnTestMessageService.text=Test MultiUserSettingsPanel.lbMessageServiceSettings.text=ActiveMQ Message Service Settings MultiUserSettingsPanel.tbMsgPort.toolTipText=Port Number MultiUserSettingsPanel.tbMsgPort.text= -MultiUserSettingsPanel.tbMsgUsername.toolTipText=User Name +MultiUserSettingsPanel.tbMsgUsername.toolTipText=User Name (optional) MultiUserSettingsPanel.tbMsgUsername.text= -MultiUserSettingsPanel.tbMsgPassword.toolTipText=Password +MultiUserSettingsPanel.tbMsgPassword.toolTipText=Password (optional) MultiUserSettingsPanel.tbMsgPassword.text= MultiUserSettingsPanel.tbMsgHostname.toolTipText=Hostname or IP Address MultiUserSettingsPanel.tbMsgHostname.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form index 57f1eca7d5..0ffb633a41 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form @@ -16,86 +16,36 @@ - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - + - + - - - - - - - - - - - - + - - + + + + + + @@ -104,416 +54,472 @@ - - - - - + + + - - - + - - - - - + + + - + - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - + + + - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java index 0a26244edc..ad83b4340f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java @@ -23,6 +23,7 @@ import javax.swing.ImageIcon; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.events.MessageServiceException; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; @@ -35,11 +36,13 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private static final String PORT_PROMPT = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbDbPort.toolTipText"); private static final String USER_NAME_PROMPT = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbDbUsername.toolTipText"); private static final String PASSWORD_PROMPT = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbDbPassword.toolTipText"); + private static final String USER_NAME_PROMPT_OPT = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbMsgUsername.toolTipText"); + private static final String PASSWORD_PROMPT_OPT = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbMsgPassword.toolTipText"); private static final String INCOMPLETE_SETTINGS_MSG = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.validationErrMsg.incomplete"); private static final String INVALID_DB_PORT_MSG = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.validationErrMsg.invalidDatabasePort"); private static final String INVALID_MESSAGE_SERVICE_PORT_MSG = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.validationErrMsg.invalidMessageServicePort"); private static final String INVALID_INDEXING_SERVER_PORT_MSG = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.validationErrMsg.invalidIndexingServerPort"); - private static final int DEFAULT_MESSAGE_SERVICE_PORT = 61616; + private static final String NON_WINDOWS_OS_MSG = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.nonWindowsOs.msg"); private static final long serialVersionUID = 1L; private final MultiUserSettingsPanelController controller; private final Collection textBoxes = new ArrayList<>(); @@ -47,6 +50,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(MultiUserSettingsPanel.class.getName()); private final ImageIcon goodIcon; private final ImageIcon badIcon; + private static final boolean isWindowsOS = PlatformUtil.isWindowsOS(); /** * Creates new form AutopsyMultiUserSettingsPanel @@ -68,8 +72,8 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { textPrompts.add(new TextPrompt(PASSWORD_PROMPT, tbDbPassword)); textPrompts.add(new TextPrompt(HOST_NAME_OR_IP_PROMPT, tbMsgHostname)); textPrompts.add(new TextPrompt(PORT_PROMPT, tbMsgPort)); - textPrompts.add(new TextPrompt(USER_NAME_PROMPT, tbMsgUsername)); - textPrompts.add(new TextPrompt(PASSWORD_PROMPT, tbMsgPassword)); + textPrompts.add(new TextPrompt(USER_NAME_PROMPT_OPT, tbMsgUsername)); + textPrompts.add(new TextPrompt(PASSWORD_PROMPT_OPT, tbMsgPassword)); textPrompts.add(new TextPrompt(HOST_NAME_OR_IP_PROMPT, tbSolrHostname)); textPrompts.add(new TextPrompt(PORT_PROMPT, tbSolrPort)); configureTextPrompts(textPrompts); @@ -105,6 +109,10 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { addDocumentListeners(textBoxes, textBoxChangedListener); goodIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/images/good.png", false)); badIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/images/bad.png", false)); + if (!isWindowsOS) { + cbEnableMultiUser.setEnabled(false); + cbEnableMultiUser.setSelected(false); + } enableMultiUserComponents(textBoxes, cbEnableMultiUser.isSelected()); } @@ -143,6 +151,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { + jScrollPane = new javax.swing.JScrollPane(); pnOverallPanel = new javax.swing.JPanel(); pnDatabaseSettings = new javax.swing.JPanel(); tbDbHostname = new javax.swing.JTextField(); @@ -432,15 +441,17 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { .addContainerGap(39, Short.MAX_VALUE)) ); + jScrollPane.setViewportView(pnOverallPanel); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(pnOverallPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 555, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 555, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(pnOverallPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 559, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 559, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -457,7 +468,11 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private void cbEnableMultiUserItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cbEnableMultiUserItemStateChanged if (!cbEnableMultiUser.isSelected()) { - tbOops.setText(""); + if (!isWindowsOS) { + tbOops.setText(NON_WINDOWS_OS_MSG); + } else { + tbOops.setText(""); + } bnTestDatabase.setEnabled(false); lbTestDatabase.setIcon(null); bnTestSolr.setEnabled(false); @@ -627,19 +642,29 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { } /** - * Tests whether or not values have been entered in all of the message - * service settings text fields. + * Tests whether or not values have been entered in all of the + * required message service settings text fields. * * @return True or false. */ private boolean messageServiceFieldsArePopulated() { - return !tbMsgHostname.getText().trim().isEmpty() - && !tbMsgPort.getText().trim().isEmpty() - && !tbMsgUsername.getText().trim().isEmpty() - && tbMsgPassword.getPassword().length != 0; + + if ((tbMsgHostname.getText().trim().isEmpty()) || + (tbMsgPort.getText().trim().isEmpty())) { + return false; + } + + // user name and pw are optional, but make sure they are both set or both empty + boolean isUserSet = (tbMsgUsername.getText().trim().isEmpty() == false); + boolean isPwSet = (tbMsgPassword.getPassword().length != 0); + return (isUserSet == isPwSet); } void store() { + if (!isWindowsOS) { + return; + } + DbType dbType = DbType.SQLITE; if (cbEnableMultiUser.isSelected()) { @@ -691,7 +716,11 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { * @return true if it's okay, false otherwise. */ boolean valid() { - tbOops.setText(""); + if (!isWindowsOS) { + tbOops.setText(NON_WINDOWS_OS_MSG); + } else { + tbOops.setText(""); + } if (cbEnableMultiUser.isSelected()) { return checkFieldsAndEnableButtons() @@ -800,6 +829,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private javax.swing.JButton bnTestMessageService; private javax.swing.JButton bnTestSolr; private javax.swing.JCheckBox cbEnableMultiUser; + private javax.swing.JScrollPane jScrollPane; private javax.swing.JLabel lbDatabaseSettings; private javax.swing.JLabel lbMessageServiceSettings; private javax.swing.JLabel lbSolrSettings; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/TimeStampUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/TimeStampUtils.java deleted file mode 100644 index 05e8cb9a52..0000000000 --- a/Core/src/org/sleuthkit/autopsy/coreutils/TimeStampUtils.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2015 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.coreutils; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Class offers utility functions to identify and process time stamped folders. - */ -public final class TimeStampUtils { - - // Pattern to identify whether case name contains a generated time stamp. - // Sample case name with time stamp: Case 1_2015_02_02_12_10_31 for case "Case 1" - private static final Pattern timeStampPattern = Pattern.compile("\\d{4}_\\d{2}_\\d{2}_\\d{2}_\\d{2}_\\d{2}$"); - private static final int LENGTH_OF_DATE_TIME_STAMP = 20; // length of the above time stamp - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); - - /** - * Checks whether a string ends with a time stamp defined by pattern. - * - * @param inputString Input string - * - * @return true if string ends with a time stamp, false otherwise. - */ - public static boolean endsWithTimeStamp(String inputString) { - Matcher m = timeStampPattern.matcher(inputString); - return m.find(); - } - - /** - * Returns length of time stamp string. - * - * @return length of time stamp string. - */ - public static int getTimeStampLength() { - return LENGTH_OF_DATE_TIME_STAMP; - } - - /** - * Create a timestamp using the current time - * - * @return the timestamp as a String - */ - public static String createTimeStamp() { - return dateFormat.format(Calendar.getInstance().getTime()); - } - - /** - * Remove a timestamp if it exists - * - * @param input the String to remove the trailing timestamp from - * - * @return the String without timestamp - */ - public static String removeTimeStamp(String input) { - String result = input; - if (input != null && endsWithTimeStamp(input)) { - result = input.substring(0, input.length() - getTimeStampLength()); - } - return result; - } - - /** - * Return the timestamp portion of the name passed in - * - * @param input the name to check for a timestamp - * - * @return the timestamp only, or empty String if none - */ - public static String getTimeStampOnly(String input) { - String result = ""; - if (input != null && endsWithTimeStamp(input)) { - result = input.substring(input.length() - getTimeStampLength(), input.length()); - } - return result; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 73800b9b89..f04abb8e14 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -454,7 +454,7 @@ public class EventsRepository { } @Override - @NbBundle.Messages({"progressWindow.msg.populateMacEventsFiles=populating mac events for files:", + @NbBundle.Messages({"progressWindow.msg.populateMacEventsFiles==Populating MAC time events for files:", "progressWindow.msg.reinit_db=(re)initializing events database", "progressWindow.msg.commitingDb=committing events db"}) protected Void doInBackground() throws Exception { @@ -587,7 +587,7 @@ public class EventsRepository { * @param trans the db transaction to use * @param skCase a reference to the sleuthkit case */ - @NbBundle.Messages({"# {0} - event type ", "progressWindow.populatingXevents=populating {0} events"}) + @NbBundle.Messages({"# {0} - event type ", "progressWindow.populatingXevents=Populating {0} events"}) private void populateEventType(final ArtifactEventType type, EventDB.EventTransaction trans) { try { //get all the blackboard artifacts corresponding to the given event sub_type diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenHelpAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenHelpAction.java index e86c20b28c..c2a9fe783f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenHelpAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenHelpAction.java @@ -43,7 +43,7 @@ public final class OpenHelpAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { try { - Desktop.getDesktop().browse(URI.create("http://sleuthkit.org/autopsy/docs/user-docs/")); + Desktop.getDesktop().browse(URI.create("http://sleuthkit.org/autopsy/docs/user-docs/4.0/image_gallery_page.html")); } catch (IOException ex) { Exceptions.printStackTrace(ex); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index e8805b9a5d..5ce5869d6b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2015 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,27 +25,14 @@ import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.logging.Level; -import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.client.solrj.request.AbstractUpdateRequest; -import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.util.ContentStream; import org.apache.solr.common.SolrInputDocument; import org.openide.util.Exceptions; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.keywordsearch.Server.SolrServerNoPortException; import org.sleuthkit.datamodel.AbstractContent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -56,7 +43,6 @@ import org.sleuthkit.datamodel.File; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.ReadContentInputStream; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -66,7 +52,6 @@ class Ingester { private static final Logger logger = Logger.getLogger(Ingester.class.getName()); private volatile boolean uncommitedIngests = false; - private final ExecutorService upRequestExecutor = Executors.newSingleThreadExecutor(); private final Server solrServer = KeywordSearch.getServer(); private final GetContentFieldsV getContentFieldsV = new GetContentFieldsV(); private static Ingester instance; @@ -193,7 +178,7 @@ class Ingester { @Override protected Map defaultVisit(Content cntnt) { - return new HashMap(); + return new HashMap<>(); } @Override @@ -239,14 +224,13 @@ class Ingester { } private Map getCommonFields(AbstractFile af) { - Map params = new HashMap(); + Map params = new HashMap<>(); params.put(Server.Schema.ID.toString(), Long.toString(af.getId())); - long dataSourceId = -1; try { - dataSourceId = af.getDataSource().getId(); + long dataSourceId = af.getDataSource().getId(); params.put(Server.Schema.IMAGE_ID.toString(), Long.toString(dataSourceId)); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Could not get data source id to properly index the file " + af.getId()); //NON-NLS + logger.log(Level.SEVERE, "Could not get data source id to properly index the file {0}", af.getId()); //NON-NLS params.put(Server.Schema.IMAGE_ID.toString(), Long.toString(-1)); } @@ -291,7 +275,7 @@ class Ingester { //using size here, but we are no longer ingesting entire files //size is normally a chunk size, up to 1MB if (size > 0) { - + // TODO (RC): Use try with resources, adjust exception messages InputStream is = null; int read = 0; try { @@ -302,10 +286,12 @@ class Ingester { NbBundle.getMessage(this.getClass(), "Ingester.ingest.exception.cantReadStream.msg", cs.getName())); } finally { - try { - is.close(); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not close input stream after reading content, " + cs.getName(), ex); //NON-NLS + if (null != is) { + try { + is.close(); + } catch (IOException ex) { + logger.log(Level.WARNING, "Could not close input stream after reading content, " + cs.getName(), ex); //NON-NLS + } } } @@ -336,78 +322,6 @@ class Ingester { } - /** - * Delegate method actually performing the indexing work for objects - * implementing ContentStream - * - * @param cs ContentStream to ingest - * @param fields content specific fields - * @param size size of the content - used to determine the Solr timeout, - * not used to populate meta-data - * - * @throws IngesterException if there was an error processing a specific - * content, but the Solr server is probably fine. - */ - private void ingestExtract(ContentStream cs, Map fields, final long size) throws IngesterException { - final ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update/extract"); //NON-NLS - up.addContentStream(cs); - setFields(up, fields); - up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true); - - final String contentType = cs.getContentType(); - if (contentType != null && !contentType.trim().equals("")) { - up.setParam("stream.contentType", contentType); //NON-NLS - } - - //logger.log(Level.INFO, "Ingesting " + fields.get("file_name")); - up.setParam("commit", "false"); //NON-NLS - - final Future f = upRequestExecutor.submit(new UpRequestTask(up)); - - try { - f.get(getTimeout(size), TimeUnit.SECONDS); - } catch (TimeoutException te) { - logger.log(Level.WARNING, "Solr timeout encountered, trying to restart Solr"); //NON-NLS - //restart may be needed to recover from some error conditions - hardSolrRestart(); - throw new IngesterException( - NbBundle.getMessage(this.getClass(), "Ingester.ingestExtract.exception.solrTimeout.msg", - fields.get("id"), fields.get("file_name"))); //NON-NLS - } catch (Exception e) { - throw new IngesterException( - NbBundle.getMessage(this.getClass(), "Ingester.ingestExtract.exception.probPostToSolr.msg", - fields.get("id"), fields.get("file_name")), e); //NON-NLS - } - uncommitedIngests = true; - } - - /** - * attempt to restart Solr and recover from its internal error - */ - private void hardSolrRestart() { - try { - solrServer.closeCore(); - } catch (KeywordSearchModuleException ex) { - logger.log(Level.WARNING, "Cannot close core", ex); //NON-NLS - } - - solrServer.stop(); - - try { - solrServer.start(); - } catch (KeywordSearchModuleException ex) { - logger.log(Level.WARNING, "Cannot start", ex); //NON-NLS - } catch (SolrServerNoPortException ex) { - logger.log(Level.WARNING, "Cannot start server with this port", ex); //NON-NLS - } - - try { - solrServer.openCore(); - } catch (KeywordSearchModuleException ex) { - logger.log(Level.WARNING, "Cannot open core", ex); //NON-NLS - } - } - /** * return timeout that should be used to index the content * @@ -431,51 +345,6 @@ class Ingester { } - private class UpRequestTask implements Runnable { - - ContentStreamUpdateRequest up; - - UpRequestTask(ContentStreamUpdateRequest up) { - this.up = up; - } - - @Override - public void run() { - try { - up.setMethod(METHOD.POST); - solrServer.request(up); - } catch (NoOpenCoreException ex) { - throw new RuntimeException( - NbBundle.getMessage(this.getClass(), "Ingester.UpReqestTask.run.exception.sorlNotAvail.msg"), ex); - } catch (IllegalStateException ex) { - // problems with content - throw new RuntimeException( - NbBundle.getMessage(this.getClass(), "Ingester.UpRequestTask.run.exception.probReadFile.msg"), ex); - } catch (SolrServerException ex) { - // If there's a problem talking to Solr, something is fundamentally - // wrong with ingest - throw new RuntimeException( - NbBundle.getMessage(this.getClass(), "Ingester.UpRequestTask.run.exception.solrProb.msg"), ex); - } catch (SolrException ex) { - // Tika problems result in an unchecked SolrException - ErrorCode ec = ErrorCode.getErrorCode(ex.code()); - - // When Tika has problems with a document, it throws a server error - // but it's okay to continue with other documents - if (ec.equals(ErrorCode.SERVER_ERROR)) { - throw new RuntimeException(NbBundle.getMessage(this.getClass(), - "Ingester.UpRequestTask.run.exception.probPostToSolr.msg", - ec), - ex); - } else { - // shouldn't get any other error codes - throw ex; - } - } - - } - } - /** * Tells Solr to commit (necessary before ingested files will appear in * searches) @@ -484,22 +353,8 @@ class Ingester { try { solrServer.commit(); uncommitedIngests = false; - } catch (NoOpenCoreException ex) { + } catch (NoOpenCoreException | SolrServerException ex) { logger.log(Level.WARNING, "Error commiting index", ex); //NON-NLS - } catch (SolrServerException ex) { - logger.log(Level.WARNING, "Error commiting index", ex); //NON-NLS - } - } - - /** - * Helper to set document fields - * - * @param up request with document - * @param fields map of field-names->values - */ - private static void setFields(ContentStreamUpdateRequest up, Map fields) { - for (Entry field : fields.entrySet()) { - up.setParam("literal." + field.getKey(), field.getValue()); //NON-NLS } } @@ -595,6 +450,8 @@ class Ingester { */ static class IngesterException extends Exception { + private static final long serialVersionUID = 1L; + IngesterException(String message, Throwable ex) { super(message, ex); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 0ec4205d75..d9e168b823 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -182,7 +182,8 @@ public class Server { // This could be a local or remote server. private HttpSolrServer currentSolrServer; - private final String instanceDir; + private Core currentCore; + private final File solrFolder; private final ServerAction serverAction; private InputStreamPrinterThread errorRedirectThread; @@ -197,9 +198,8 @@ public class Server { this.localSolrServer = new HttpSolrServer("http://localhost:" + currentSolrServerPort + "/solr"); //NON-NLS serverAction = new ServerAction(); solrFolder = InstalledFileLocator.getDefault().locate("solr", Server.class.getPackage().getName(), false); //NON-NLS - instanceDir = solrFolder.getAbsolutePath() + File.separator + "solr"; //NON-NLS javaPath = PlatformUtil.getJavaPath(); - + logger.log(Level.INFO, "Created Server instance"); //NON-NLS } @@ -295,7 +295,7 @@ public class Server { BufferedReader br = new BufferedReader(isr); OutputStreamWriter osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset()); BufferedWriter bw = new BufferedWriter(osw);) { - + String line = null; while (doRun && (line = br.readLine()) != null) { bw.write(line); @@ -586,11 +586,10 @@ public class Server { return true; } - /** + + /* * ** Convenience methods for use while we only open one case at a time *** */ - private volatile Core currentCore = null; - synchronized void openCore() throws KeywordSearchModuleException { if (currentCore != null) { throw new KeywordSearchModuleException( @@ -1251,6 +1250,7 @@ public class Server { } class ServerAction extends AbstractAction { + private static final long serialVersionUID = 1L; @Override @@ -1263,6 +1263,7 @@ public class Server { * Exception thrown if solr port not available */ class SolrServerNoPortException extends SocketException { + private static final long serialVersionUID = 1L; /** diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index a41dd17420..bbdfa12f42 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Tue, 13 Oct 2015 17:00:33 -0400 +#Mon, 26 Oct 2015 09:54:05 -0400 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 3.1.3 +currentVersion=Autopsy 4.0.0 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 5dd3fa12e1..a375cb839f 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Tue, 13 Oct 2015 17:00:33 -0400 -CTL_MainWindow_Title=Autopsy 3.1.3 -CTL_MainWindow_Title_No_Project=Autopsy 3.1.3 +#Mon, 26 Oct 2015 09:54:05 -0400 +CTL_MainWindow_Title=Autopsy 4.0.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.0.0 diff --git a/docs/doxygen-user/android.dox b/docs/doxygen-user/android.dox deleted file mode 100644 index 04733581eb..0000000000 --- a/docs/doxygen-user/android.dox +++ /dev/null @@ -1,26 +0,0 @@ -/*! \page android_page Android Analyzer -Overview -======== - -The Android Analyzer module allows you to analyze SQLite and other files from an Android device. It should work on Physical dumps from most Android devices (note that we do not provide an acquisition method). Autopsy will not support older Android devices that do not have a volume system. These devices will often have a single physical image file for them and there is no information in the image that describes the layout of the file systems. Autopsy will therefore not be able to detect what it is. - -Simply add your physical images or file system dumps as data sources and enable the Android Analyzer module. - -NOTE: This module is not exhaustive with its support for Android. It was created as a starting point for others to contribute plug-ins for 3rd party apps. See the Developer docs (http://sleuthkit.org/autopsy/docs/api-docs/3.1/mod_mobile_page.html) for information on writing modules. - - -Analysis -======== -The module should be able to extract the following: - -- Text messages / SMS / MMS -- Call Logs -- Contacts -- Tango Messages -- Words with Friends Messages -- GPS from the browser and Google Maps -- GPS from cache.wifi and cache.cell files - -NOTE: These database formats vary by version of OS and different vendors can place the databaes in different places. Autopsy may not support all versions and vendors. - -*/ diff --git a/docs/doxygen-user/android_analyzer.dox b/docs/doxygen-user/android_analyzer.dox index f23df9714c..0943bd05c9 100755 --- a/docs/doxygen-user/android_analyzer.dox +++ b/docs/doxygen-user/android_analyzer.dox @@ -18,7 +18,7 @@ The module should be able to extract the following: NOTE: These database formats vary by version of OS and different vendors can place the databases in different places. Autopsy may not support all versions and vendors. -NOTE: This module is not exhaustive with its support for Android. It was created as a starting point for others to contribute plug-ins for 3rd party apps. See the Developer docs for information on writing modules. +NOTE: This module is not exhaustive with its support for Android. It was created as a starting point for others to contribute plug-ins for 3rd party apps. See the Developer docs for information on writing modules. Configuration diff --git a/docs/doxygen-user/images/case-newcase.PNG b/docs/doxygen-user/images/case-newcase.PNG new file mode 100755 index 0000000000..3bb7122c5d Binary files /dev/null and b/docs/doxygen-user/images/case-newcase.PNG differ diff --git a/docs/doxygen-user/images/case-newcase.png b/docs/doxygen-user/images/case-newcase.png deleted file mode 100755 index 40209b9c84..0000000000 Binary files a/docs/doxygen-user/images/case-newcase.png and /dev/null differ diff --git a/docs/doxygen-user/images/credentialManager.PNG b/docs/doxygen-user/images/credentialManager.PNG new file mode 100755 index 0000000000..21acb8779b Binary files /dev/null and b/docs/doxygen-user/images/credentialManager.PNG differ diff --git a/docs/doxygen-user/images/credentialsWithDomain.PNG b/docs/doxygen-user/images/credentialsWithDomain.PNG new file mode 100755 index 0000000000..4127a9497f Binary files /dev/null and b/docs/doxygen-user/images/credentialsWithDomain.PNG differ diff --git a/docs/doxygen-user/images/getHostname.PNG b/docs/doxygen-user/images/getHostname.PNG new file mode 100755 index 0000000000..9497077beb Binary files /dev/null and b/docs/doxygen-user/images/getHostname.PNG differ diff --git a/docs/doxygen-user/images/hostname.PNG b/docs/doxygen-user/images/hostname.PNG new file mode 100755 index 0000000000..d97f78ae42 Binary files /dev/null and b/docs/doxygen-user/images/hostname.PNG differ diff --git a/docs/doxygen-user/images/toConnect.PNG b/docs/doxygen-user/images/toConnect.PNG new file mode 100755 index 0000000000..4bfecbc4b6 Binary files /dev/null and b/docs/doxygen-user/images/toConnect.PNG differ diff --git a/docs/doxygen-user/images/urlInAddressbar.PNG b/docs/doxygen-user/images/urlInAddressbar.PNG new file mode 100755 index 0000000000..2577a55450 Binary files /dev/null and b/docs/doxygen-user/images/urlInAddressbar.PNG differ diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index e3ded15e32..0d9144240f 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -4,7 +4,7 @@ Overview ----- -This is the User's Guide for the open source Autopsy platform. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The developer's guide will help you develop your own Autopsy modules. +This is the User's Guide for the open source Autopsy platform. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The developer's guide will help you develop your own Autopsy modules. Autopsy 4 (and 3) are a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2. @@ -51,6 +51,7 @@ The following topics are available here: - \subpage install_activemq - \subpage install_postgresql - \subpage install_solr + - \subpage windows_authentication - \subpage multiuser_page If the topic you need is not listed, refer to the Autopsy Wiki or join the SleuthKit User List at SourceForge. diff --git a/docs/doxygen-user/windows_authentication.dox b/docs/doxygen-user/windows_authentication.dox new file mode 100755 index 0000000000..d86d0249cd --- /dev/null +++ b/docs/doxygen-user/windows_authentication.dox @@ -0,0 +1,57 @@ +/*! \page windows_authentication Shared Drive Authentication + +

+If your shared drive is a Windows-hosted shared drive, you will likely need to provide authentication for each machine that connects to the shared drive. This guide only covers Windows-hosted shared drives. + +To authenticate with Windows and allow access to a shared drive, you will need: +- A username +- A password +- The domain name (if the machine hosting the shared drive is on a domain) +- The IP address of the machine hosting the shared drive +- The hostname of the machine hosting the shared drive + +Using Windows Explorer, in the address bar enter two slashes "\\" followed by the storage machine's IP address and press _Enter_. An example is shown below with the text "\\10.10.152.211" entered. +

+\image html urlInAddressbar.PNG +

+ +You will see a dialog similar to the following, asking for your credentials. + +

+\image html credentialsWithDomain.PNG +

+ +If you have a domain name, add it in the top box before the "\". Follow the slash with your username. If you have no domain name, just use your username with no slashes. Add your password in the next box down and place a check mark in "Remember my credentials", then click "OK". + +Next, we will do the same steps over again, using the hostname of the machine. This is necessary to authenticate with both IP address access and hostname access. If you do not know the hostname, you may find it by pinging the IP address with the "-a" flag set. It will look something like the screenshot below, where we find the hostname associated with the IP address 10.10.142.56   is   win-kmort-4863.basistech.net. + +

+\image html getHostname.PNG +

+ +In Windows Explorer, use this hostname preceded by two slashes, "\\", in the address bar as shown below and press enter. + +

+\image html hostname.PNG +

+ +You will see a screen similar to the screenshot below. Do the same steps with domain, username, and password as you did above. + +

+\image html toConnect.PNG +

+ +Do these steps for each machine that will be accessing the shared drive. +


+


+- - - - - +Note that if you are familiar with the Windows Credential Manager, you may use this tool to manage credentials. These credentials can also be managed from the command line using the "net use" command. To get to Credential Manager click on to Start, and typing "Credential Manager" and pressing enter. A screenshot of the Windows Credential Manager with some domain names intentionally blanked out is shown below. + +

+\image html credentialManager.PNG +

+ +Also note that authentication and access can be an issue when passwords change. When passwords change, for every computer using a credential that is no longer valid, you will need to redo the above steps. One indicator this is a problem is seeing the text: "The system detected a possible attempt to compromise security. Please ensure that you can contact the server that authenticated you."   Do not forget to re-authenticate with both the IP address and the hostname. +


+ +*/ diff --git a/docs/doxygen/main.dox b/docs/doxygen/main.dox index 17ba30d6e7..9b4510f820 100755 --- a/docs/doxygen/main.dox +++ b/docs/doxygen/main.dox @@ -18,7 +18,7 @@ If you want to write modules, then these pages are for you: - \subpage mod_content_page - \subpage mod_result_page - \subpage adv_dev_page -- \subpage query_database_page +- Query the Database - \subpage mod_mobile_page These pages are more detailed if you want to modify Autopsy code instead of writing add-on modules. diff --git a/docs/doxygen/modIngest.dox b/docs/doxygen/modIngest.dox index 0f3d4a1ebb..4a88c317b0 100755 --- a/docs/doxygen/modIngest.dox +++ b/docs/doxygen/modIngest.dox @@ -43,7 +43,7 @@ ingest modules. \section ingest_modules_lifecycle Ingest Module Life Cycle -Before we dive into the details of creating a module, it is important to understand the life cycle of the module. Note that this life cycle is much different for Autopsy 3.1 modules compared to Autopsy 3.0 modules. This section only talks about 3.1 modules. +Before we dive into the details of creating a module, it is important to understand the life cycle of the module. Note that this life cycle is much different for Autopsy 3.1 and newer modules compared to Autopsy 3.0 modules. This section only talks about 3.1 and newer modules. You will need to implement at least two classes to make an ingest module: -# A factory class that will be created when Autopsy starts and will provide configuration panels to Autopsy and will create instances of your ingest module. @@ -319,7 +319,7 @@ databases from the hash databases manager. -\section ingest_modules_api_migration Migrating 3.0 Java Ingest Modules to the 3.1 API +\section ingest_modules_api_migration Migrating 3.0 Java Ingest Modules to the 3.1 and newer API This section is a guide for module developers who wrote modules for the 3.0 API. These API changes occurred so that we could make parallel pipelines of the file-level ingest modules. This section assumes you've read the above description of the new API. diff --git a/nbproject/platform.properties b/nbproject/platform.properties index aa34a2c2be..6c3e50c82d 100644 --- a/nbproject/platform.properties +++ b/nbproject/platform.properties @@ -133,5 +133,4 @@ disabled.modules=\ org.netbeans.modules.whitelist,\ org.netbeans.modules.xml.jaxb,\ org.netbeans.modules.xml.tools.java,\ - org.netbeans.spi.java.hints - + org.netbeans.spi.java.hints \ No newline at end of file diff --git a/nbproject/project.properties b/nbproject/project.properties index f80c2ec8a8..d288f226cc 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,7 +4,7 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=3.1.3 +app.version=4.0.0 ### Build type isn't used at this point, but it may be useful ### Must be one of: DEVELOPMENT, RELEASE #build.type=RELEASE