diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java index 97456bda04..62cbbeb24b 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java @@ -42,11 +42,13 @@ public abstract class AbstractCommonAttributeSearcher { private final Map dataSourceIdToNameMap; private boolean filterByMedia; private boolean filterByDoc; + final int frequencyPercentageThreshold; - AbstractCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMedia, boolean filterByDoc){ + AbstractCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMedia, boolean filterByDoc, int percentageThreshold){ this.filterByDoc = filterByDoc; this.filterByMedia = filterByMedia; this.dataSourceIdToNameMap = dataSourceIdMap; + this.frequencyPercentageThreshold = percentageThreshold; } Map getDataSourceIdToNameMap(){ @@ -100,17 +102,18 @@ public abstract class AbstractCommonAttributeSearcher { } } - static Map> collateMatchesByNumberOfInstances(Map commonFiles) { + static Map collateMatchesByNumberOfInstances(Map commonFiles) { //collate matches by number of matching instances - doing this in sql doesnt seem efficient - Map> instanceCollatedCommonFiles = new TreeMap<>(); + Map instanceCollatedCommonFiles = new TreeMap<>(); + for(CommonAttributeValue md5Metadata : commonFiles.values()){ Integer size = md5Metadata.getInstanceCount(); if(instanceCollatedCommonFiles.containsKey(size)){ - instanceCollatedCommonFiles.get(size).add(md5Metadata); + instanceCollatedCommonFiles.get(size).addMetadataToList(md5Metadata); } else { - ArrayList value = new ArrayList<>(); - value.add(md5Metadata); + CommonAttributeValueList value = new CommonAttributeValueList(); + value.addMetadataToList(md5Metadata); instanceCollatedCommonFiles.put(size, value); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java index ca436b7809..ac84d0188f 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java @@ -41,15 +41,15 @@ public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttribut * broadly categorized as document types * @throws EamDbException */ - public AllInterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) throws EamDbException { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + public AllInterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) throws EamDbException { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold); } @Override public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap()); - Map> interCaseCommonFiles = eamDbAttrInst.findInterCaseCommonAttributeValues(Case.getCurrentCase()); - return new CommonAttributeSearchResults(interCaseCommonFiles); + Map interCaseCommonFiles = eamDbAttrInst.findInterCaseCommonAttributeValues(Case.getCurrentCase()); + return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java index eed61b0fc1..35a5c70452 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java @@ -37,8 +37,8 @@ final public class AllIntraCaseCommonAttributeSearcher extends IntraCaseCommonAt * @param filterByMediaMimeType match only on files whose mime types can be broadly categorized as media types * @param filterByDocMimeType match only on files whose mime types can be broadly categorized as document types */ - public AllIntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + public AllIntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties index 23598648ea..b8f2e3e0d7 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -9,6 +9,17 @@ IntraCasePanel.withinDataSourceRadioButton.text=At least one match must appear i IntraCasePanel.selectDataSourceComboBox.actionCommand= InterCasePanel.specificCentralRepoCaseRadio.text=Matches must be from the following Central Repo case: InterCasePanel.anyCentralRepoCaseRadio.text=Matches may be from any Central Repo case +CommonAttributePanel.jCheckBox1.text=Hide files found in over +CommonAttributePanel.jLabel1.text=% of data sources in central repository. +CommonAttributePanel.percentageThresholdTextTwo.text_1=% of data sources in central repository. +CommonAttributePanel.percentageThresholdTextOne.text=20 +CommonAttributePanel.percentageThresholdCheck.text_1=Hide files found in over +CommonAttributePanel.intraCaseRadio.text=Within current case +CommonAttributePanel.commonFilesSearchLabel1.text=Find common files to correlate data soures or cases. +CommonAttributePanel.errorText.text=In order to search, you must select a file category. +CommonAttributePanel.categoriesLabel.text=File Types To Include: +CommonAttributePanel.documentsCheckbox.text=Documents +CommonAttributePanel.pictureVideoCheckbox.text=Pictures and Videos CommonAttributePanel.selectedFileCategoriesButton.toolTipText=Select from the options below... CommonAttributePanel.selectedFileCategoriesButton.text=Only the selected file types: CommonAttributePanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results... @@ -17,9 +28,3 @@ CommonAttributePanel.cancelButton.actionCommand=Cancel CommonAttributePanel.cancelButton.text=Cancel CommonAttributePanel.searchButton.text=Search CommonAttributePanel.commonFilesSearchLabel2.text=Scope of Search -CommonAttributePanel.intraCaseRadio.text=Within current case -CommonAttributePanel.commonFilesSearchLabel1.text=Find common files to correlate data soures or cases. -CommonAttributePanel.errorText.text=In order to search, you must select a file category. -CommonAttributePanel.categoriesLabel.text=File Types To Include: -CommonAttributePanel.documentsCheckbox.text=Documents -CommonAttributePanel.pictureVideoCheckbox.text=Pictures and Videos diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form index c08c3c2d78..20cbb33fe9 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form @@ -8,8 +8,11 @@ + + + - + @@ -29,16 +32,23 @@ - + - - + + + + + + + + + @@ -49,46 +59,56 @@ - + + - - + - - - - - - - - - - + + + + + + + - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - @@ -104,7 +124,9 @@ - + + + @@ -116,18 +138,23 @@ - - - + + + - - - - - - + + + + + + + + + + + @@ -238,6 +265,7 @@ + @@ -295,6 +323,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java index bf1765e310..21f2b72624 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.commonfilesearch; +import java.awt.Dimension; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; @@ -29,6 +30,8 @@ import java.util.logging.Level; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import org.netbeans.api.progress.ProgressHandle; import org.openide.explorer.ExplorerManager; import org.openide.util.NbBundle; @@ -52,16 +55,21 @@ import org.sleuthkit.datamodel.TskCoreException; * logic. Nested within CommonFilesDialog. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public final class CommonAttributePanel extends javax.swing.JDialog { +public final class CommonAttributePanel extends javax.swing.JDialog { + private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName()); private static final long serialVersionUID = 1L; private static final Long NO_DATA_SOURCE_SELECTED = -1L; - private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName()); + private final UserInputErrorManager errorManager; + private boolean pictureViewCheckboxState; + private boolean documentsCheckboxState; + private int percentageThresholdValue = 20; + /** * Creates new form CommonFilesPanel */ @@ -74,25 +82,71 @@ public final class CommonAttributePanel extends javax.swing.JDialog { super(new JFrame(Bundle.CommonFilesPanel_frame_title()), Bundle.CommonFilesPanel_frame_msg(), true); initComponents(); + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); - this.errorText.setVisible(false); this.setupDataSources(); - if (CommonAttributePanel.isEamDbAvailable()) { + if (CommonAttributePanel.isEamDbAvailableForIntercaseSearch()) { this.setupCases(); } else { this.disableIntercaseSearch(); } + + if(CommonAttributePanel.isEamDbAvailableForPercentageFrequencyCalculations()){ + this.enablePercentageOptions(); + } else { + this.disablePercentageOptions(); + } + + this.errorManager = new UserInputErrorManager(); + + this.percentageThresholdTextOne.getDocument().addDocumentListener(new DocumentListener(){ + + private Dimension preferredSize = CommonAttributePanel.this.percentageThresholdTextOne.getPreferredSize(); + + private void maintainSize(){ + CommonAttributePanel.this.percentageThresholdTextOne.setSize(preferredSize); + } + + @Override + public void insertUpdate(DocumentEvent event) { + this.maintainSize(); + CommonAttributePanel.this.percentageThresholdChanged(); + } + + @Override + public void removeUpdate(DocumentEvent event) { + this.maintainSize(); + CommonAttributePanel.this.percentageThresholdChanged(); + } + + @Override + public void changedUpdate(DocumentEvent event) { + this.maintainSize(); + CommonAttributePanel.this.percentageThresholdChanged(); + } + }); } - private static boolean isEamDbAvailable() { + private static boolean isEamDbAvailableForIntercaseSearch() { try { - return EamDb.isEnabled() && - EamDb.getInstance() != null && - EamDb.getInstance().getCases().size() > 1 && - Case.isCaseOpen() && - Case.getCurrentCase() != null && - EamDb.getInstance().getCase(Case.getCurrentCase()) != null; + return EamDb.isEnabled() + && EamDb.getInstance() != null + && EamDb.getInstance().getCases().size() > 1 + && Case.isCaseOpen() + && Case.getCurrentCase() != null + && EamDb.getInstance().getCase(Case.getCurrentCase()) != null; + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while checking for EamDB enabled.", ex); + } + return false; + } + + private static boolean isEamDbAvailableForPercentageFrequencyCalculations(){ + try { + return EamDb.isEnabled() + && EamDb.getInstance() != null + && EamDb.getInstance().getCases().size() > 0; } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Unexpected exception while checking for EamDB enabled.", ex); } @@ -158,20 +212,27 @@ public final class CommonAttributePanel extends javax.swing.JDialog { } } + int percentageThreshold = CommonAttributePanel.this.percentageThresholdValue; + + if (!CommonAttributePanel.this.percentageThresholdCheck.isSelected()) { + //0 has the effect of disabling the feature + percentageThreshold = 0; + } + if (CommonAttributePanel.this.interCaseRadio.isSelected()) { if (caseId == InterCasePanel.NO_CASE_SELECTED) { - builder = new AllInterCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + builder = new AllInterCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold); } else { - builder = new SingleInterCaseCommonAttributeSearcher(caseId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + builder = new SingleInterCaseCommonAttributeSearcher(caseId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold); } } else { if (dataSourceId == CommonAttributePanel.NO_DATA_SOURCE_SELECTED) { - builder = new AllIntraCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + builder = new AllIntraCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold); setTitleForAllDataSources(); } else { - builder = new SingleIntraCaseCommonAttributeSearcher(dataSourceId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + builder = new SingleIntraCaseCommonAttributeSearcher(dataSourceId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold); setTitleForSingleSource(dataSourceId); } @@ -230,7 +291,6 @@ public final class CommonAttributePanel extends javax.swing.JDialog { }.execute(); } - /** * Sets up the data sources dropdown and returns the data sources map for * future usage. @@ -263,8 +323,7 @@ public final class CommonAttributePanel extends javax.swing.JDialog { boolean multipleDataSources = this.caseHasMultipleSources(); CommonAttributePanel.this.intraCasePanel.rigForMultipleDataSources(multipleDataSources); - //TODO this should be attached to the intra/inter radio buttons - CommonAttributePanel.this.setSearchButtonEnabled(true); + CommonAttributePanel.this.updateErrorTextAndSearchBox(); } } @@ -405,9 +464,14 @@ public final class CommonAttributePanel extends javax.swing.JDialog { layoutPanel = new java.awt.Panel(); intraCasePanel = new org.sleuthkit.autopsy.commonfilesearch.IntraCasePanel(); interCasePanel = new org.sleuthkit.autopsy.commonfilesearch.InterCasePanel(); + percentageThresholdCheck = new javax.swing.JCheckBox(); + percentageThresholdTextOne = new javax.swing.JTextField(); + percentageThresholdTextTwo = new javax.swing.JLabel(); + filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); + filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 32767)); - setMinimumSize(new java.awt.Dimension(412, 350)); - setPreferredSize(new java.awt.Dimension(412, 350)); + setMaximumSize(new java.awt.Dimension(450, 375)); + setMinimumSize(new java.awt.Dimension(450, 375)); setResizable(false); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosed(java.awt.event.WindowEvent evt) { @@ -415,7 +479,10 @@ public final class CommonAttributePanel extends javax.swing.JDialog { } }); - jPanel1.setPreferredSize(new java.awt.Dimension(412, 350)); + jPanel1.setMaximumSize(new java.awt.Dimension(450, 375)); + jPanel1.setMinimumSize(new java.awt.Dimension(450, 375)); + jPanel1.setPreferredSize(new java.awt.Dimension(450, 375)); + jPanel1.setRequestFocusEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel2, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.commonFilesSearchLabel2.text")); // NOI18N commonFilesSearchLabel2.setFocusable(false); @@ -478,6 +545,7 @@ public final class CommonAttributePanel extends javax.swing.JDialog { errorText.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(errorText, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.errorText.text")); // NOI18N + errorText.setVerticalAlignment(javax.swing.SwingConstants.TOP); org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel1, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.commonFilesSearchLabel1.text")); // NOI18N commonFilesSearchLabel1.setFocusable(false); @@ -503,42 +571,64 @@ public final class CommonAttributePanel extends javax.swing.JDialog { layoutPanel.add(intraCasePanel, "card3"); layoutPanel.add(interCasePanel, "card2"); + org.openide.awt.Mnemonics.setLocalizedText(percentageThresholdCheck, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdCheck.text_1")); // NOI18N + percentageThresholdCheck.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + percentageThresholdCheckActionPerformed(evt); + } + }); + + percentageThresholdTextOne.setText(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdTextOne.text")); // NOI18N + percentageThresholdTextOne.setMaximumSize(new java.awt.Dimension(40, 24)); + percentageThresholdTextOne.setMinimumSize(new java.awt.Dimension(40, 24)); + percentageThresholdTextOne.setPreferredSize(new java.awt.Dimension(40, 24)); + + org.openide.awt.Mnemonics.setLocalizedText(percentageThresholdTextTwo, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdTextTwo.text_1")); // NOI18N + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addComponent(searchButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(errorText)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(commonFilesSearchLabel2) - .addComponent(intraCaseRadio) - .addComponent(interCaseRadio) - .addComponent(commonFilesSearchLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(categoriesLabel) - .addComponent(selectedFileCategoriesButton))) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(80, 80, 80) + .addComponent(filler2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(errorText))) .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(35, 35, 35) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(documentsCheckbox) - .addComponent(pictureVideoCheckbox))) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(allFileCategoriesRadioButton))) - .addContainerGap()) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(20, 20, 20) - .addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(10, 10, 10))) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(commonFilesSearchLabel2) + .addComponent(intraCaseRadio) + .addComponent(interCaseRadio) + .addComponent(commonFilesSearchLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(categoriesLabel) + .addComponent(selectedFileCategoriesButton) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(29, 29, 29) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(documentsCheckbox) + .addComponent(pictureVideoCheckbox))) + .addComponent(allFileCategoriesRadioButton) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(percentageThresholdCheck) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(percentageThresholdTextOne, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(percentageThresholdTextTwo))) + .addContainerGap(9, Short.MAX_VALUE)))) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -551,7 +641,9 @@ public final class CommonAttributePanel extends javax.swing.JDialog { .addComponent(intraCaseRadio) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(interCaseRadio) - .addGap(79, 79, 79) + .addGap(2, 2, 2) + .addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(categoriesLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(selectedFileCategoriesButton) @@ -563,85 +655,90 @@ public final class CommonAttributePanel extends javax.swing.JDialog { .addComponent(allFileCategoriesRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(searchButton) - .addComponent(cancelButton) - .addComponent(errorText)) + .addComponent(percentageThresholdCheck) + .addComponent(percentageThresholdTextOne, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(percentageThresholdTextTwo)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(filler2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(searchButton) + .addComponent(cancelButton) + .addComponent(errorText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addGap(98, 98, 98) - .addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(180, 180, 180))) ); getContentPane().add(jPanel1, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents + private void formWindowClosed(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosed + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_formWindowClosed + + private void percentageThresholdCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_percentageThresholdCheckActionPerformed + if (this.percentageThresholdCheck.isSelected()) { + this.percentageThresholdTextOne.setEnabled(true); + } else { + this.percentageThresholdTextOne.setEnabled(false); + } + + this.handleFrequencyPercentageState(); + }//GEN-LAST:event_percentageThresholdCheckActionPerformed + + private void interCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_interCaseRadioActionPerformed + ((java.awt.CardLayout) this.layoutPanel.getLayout()).last(this.layoutPanel); + }//GEN-LAST:event_interCaseRadioActionPerformed + + private void intraCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_intraCaseRadioActionPerformed + ((java.awt.CardLayout) this.layoutPanel.getLayout()).first(this.layoutPanel); + }//GEN-LAST:event_intraCaseRadioActionPerformed + + private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed + this.handleFileTypeCheckBoxState(); + }//GEN-LAST:event_documentsCheckboxActionPerformed + + private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed + this.handleFileTypeCheckBoxState(); + }//GEN-LAST:event_pictureVideoCheckboxActionPerformed + + private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed + this.handleFileTypeCheckBoxState(); + }//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed + + private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed + this.handleFileTypeCheckBoxState(); + }//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed search(); SwingUtilities.windowForComponent(this).dispose(); }//GEN-LAST:event_searchButtonActionPerformed - private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed - SwingUtilities.windowForComponent(this).dispose(); - }//GEN-LAST:event_cancelButtonActionPerformed + private void percentageThresholdChanged(){ + String percentageString = this.percentageThresholdTextOne.getText(); - private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed - this.manageCheckBoxState(); - this.toggleErrorTextAndSearchBox(); - }//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed - - private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed - this.manageCheckBoxState(); - }//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed - - private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed - this.toggleErrorTextAndSearchBox(); - }//GEN-LAST:event_pictureVideoCheckboxActionPerformed - - private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed - this.toggleErrorTextAndSearchBox(); - }//GEN-LAST:event_documentsCheckboxActionPerformed - - private void intraCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_intraCaseRadioActionPerformed - ((java.awt.CardLayout) this.layoutPanel.getLayout()).first(this.layoutPanel); - handleIntraCaseSearchCriteriaChanged(); - }//GEN-LAST:event_intraCaseRadioActionPerformed - - public void handleIntraCaseSearchCriteriaChanged() { - if (this.areIntraCaseSearchCriteriaMet()) { - this.searchButton.setEnabled(true); - this.hideErrorMessages(); - } else { - this.searchButton.setEnabled(false); - this.hideErrorMessages(); - this.showIntraCaseErrorMessage(); + try { + this.percentageThresholdValue = Integer.parseInt(percentageString); + + } catch (NumberFormatException exception) { + this.percentageThresholdValue = -1; } + + this.handleFrequencyPercentageState(); } + + private void updateErrorTextAndSearchBox() { - private void interCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_interCaseRadioActionPerformed - ((java.awt.CardLayout) this.layoutPanel.getLayout()).last(this.layoutPanel); - handleInterCaseSearchCriteriaChanged(); - }//GEN-LAST:event_interCaseRadioActionPerformed - - private void formWindowClosed(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosed - SwingUtilities.windowForComponent(this).dispose(); - }//GEN-LAST:event_formWindowClosed - - public void handleInterCaseSearchCriteriaChanged() { - if (this.areInterCaseSearchCriteriaMet()) { - this.searchButton.setEnabled(true); - this.hideErrorMessages(); - } else { - this.searchButton.setEnabled(false); - this.hideErrorMessages(); - this.showInterCaseErrorMessage(); - } - } - - private void toggleErrorTextAndSearchBox() { - if (!this.pictureVideoCheckbox.isSelected() && !this.documentsCheckbox.isSelected() && !this.allFileCategoriesRadioButton.isSelected()) { + if (this.errorManager.anyErrors()) { this.searchButton.setEnabled(false); + //grab the first error error and show it + this.errorText.setText(this.errorManager.getErrors().get(0)); this.errorText.setVisible(true); } else { this.searchButton.setEnabled(true); @@ -649,7 +746,21 @@ public final class CommonAttributePanel extends javax.swing.JDialog { } } - private void manageCheckBoxState() { + private void enablePercentageOptions() { + this.percentageThresholdTextOne.setEnabled(true); + this.percentageThresholdCheck.setEnabled(true); + this.percentageThresholdCheck.setSelected(true); + this.percentageThresholdTextTwo.setEnabled(true); + } + + private void disablePercentageOptions() { + this.percentageThresholdTextOne.setEnabled(false); + this.percentageThresholdCheck.setEnabled(false); + this.percentageThresholdCheck.setSelected(false); + this.percentageThresholdTextTwo.setEnabled(false); + } + + private void handleFileTypeCheckBoxState() { this.pictureViewCheckboxState = this.pictureVideoCheckbox.isSelected(); this.documentsCheckboxState = this.documentsCheckbox.isSelected(); @@ -657,6 +768,8 @@ public final class CommonAttributePanel extends javax.swing.JDialog { if (this.allFileCategoriesRadioButton.isSelected()) { this.pictureVideoCheckbox.setEnabled(false); this.documentsCheckbox.setEnabled(false); + + this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, false); } if (this.selectedFileCategoriesButton.isSelected()) { @@ -667,8 +780,24 @@ public final class CommonAttributePanel extends javax.swing.JDialog { this.pictureVideoCheckbox.setEnabled(true); this.documentsCheckbox.setEnabled(true); - this.toggleErrorTextAndSearchBox(); + if (!this.pictureVideoCheckbox.isSelected() && !this.documentsCheckbox.isSelected() && !this.allFileCategoriesRadioButton.isSelected()) { + this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, true); + } else { + this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, false); + } } + + this.updateErrorTextAndSearchBox(); + } + + private void handleFrequencyPercentageState() { + if (this.percentageThresholdValue > 0 && this.percentageThresholdValue <= 100) { + this.errorManager.setError(UserInputErrorManager.FREQUENCY_PERCENTAGE_OUT_OF_RANGE_KEY, false); + } else { + this.errorManager.setError(UserInputErrorManager.FREQUENCY_PERCENTAGE_OUT_OF_RANGE_KEY, true); + } + + this.updateErrorTextAndSearchBox(); } // Variables declaration - do not modify//GEN-BEGIN:variables @@ -680,6 +809,8 @@ public final class CommonAttributePanel extends javax.swing.JDialog { private javax.swing.JCheckBox documentsCheckbox; private javax.swing.JLabel errorText; private javax.swing.ButtonGroup fileTypeFilterButtonGroup; + private javax.swing.Box.Filler filler1; + private javax.swing.Box.Filler filler2; private org.sleuthkit.autopsy.commonfilesearch.InterCasePanel interCasePanel; private javax.swing.JRadioButton interCaseRadio; private javax.swing.ButtonGroup interIntraButtonGroup; @@ -687,34 +818,11 @@ public final class CommonAttributePanel extends javax.swing.JDialog { private javax.swing.JRadioButton intraCaseRadio; private javax.swing.JPanel jPanel1; private java.awt.Panel layoutPanel; + private javax.swing.JCheckBox percentageThresholdCheck; + private javax.swing.JTextField percentageThresholdTextOne; + private javax.swing.JLabel percentageThresholdTextTwo; private javax.swing.JCheckBox pictureVideoCheckbox; private javax.swing.JButton searchButton; private javax.swing.JRadioButton selectedFileCategoriesButton; // End of variables declaration//GEN-END:variables - - void setSearchButtonEnabled(boolean enabled) { - this.searchButton.setEnabled(enabled); - } - - private boolean areIntraCaseSearchCriteriaMet() { - return this.intraCasePanel.areSearchCriteriaMet(); - } - - private boolean areInterCaseSearchCriteriaMet() { - return this.interCasePanel.areSearchCriteriaMet(); - } - - private void hideErrorMessages() { - this.errorText.setVisible(false); - } - - private void showIntraCaseErrorMessage() { - this.errorText.setText(this.intraCasePanel.getErrorMessage()); - this.errorText.setVisible(true); - } - - private void showInterCaseErrorMessage() { - this.errorText.setText(this.interCasePanel.getErrorMessage()); - this.errorText.setVisible(true); - } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java index 8e33401af7..3aede081f5 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java @@ -19,10 +19,13 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -65,6 +68,8 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo */ static class InstanceCountNodeFactory extends ChildFactory{ + private static final Logger LOGGER = Logger.getLogger(InstanceCountNodeFactory.class.getName()); + private final CommonAttributeSearchResults searchResults; /** @@ -78,13 +83,17 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo @Override protected boolean createKeys(List list) { - list.addAll(this.searchResults.getMetadata().keySet()); + try { + list.addAll(this.searchResults.getMetadata().keySet()); + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Unable to create keys.", ex); + } return true; } @Override protected Node createNodeForKey(Integer instanceCount){ - List attributeValues = this.searchResults.getAttributeValuesForInstanceCount(instanceCount); + CommonAttributeValueList attributeValues = this.searchResults.getAttributeValuesForInstanceCount(instanceCount); return new InstanceCountNode(instanceCount, attributeValues); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResults.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResults.java index 8ff13e87f1..bac59fde12 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResults.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResults.java @@ -19,27 +19,37 @@ */ package org.sleuthkit.autopsy.commonfilesearch; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; /** * Stores the results from the various types of common attribute searching * Stores results based on how they are currently displayed in the UI */ final public class CommonAttributeSearchResults { - + // maps instance count to list of attribute values. - private final Map> instanceCountToAttributeValues; + private final Map instanceCountToAttributeValues; + + private final int percentageThreshold; /** * Create a values object which can be handed off to the node factories. - * - * @param values list of CommonAttributeValue indexed by size of + * + * @param values list of CommonAttributeValue indexed by size of * CommonAttributeValue */ - CommonAttributeSearchResults(Map> metadata){ - this.instanceCountToAttributeValues = metadata; + CommonAttributeSearchResults(Map metadata, int percentageThreshold) { + //wrap in a new object in case any client code has used an unmodifiable collection + this.instanceCountToAttributeValues = new HashMap<>(metadata); + this.percentageThreshold = percentageThreshold; } /** @@ -48,32 +58,103 @@ final public class CommonAttributeSearchResults { * This is a convenience method - you can also iterate over * getValues(). * - * @param isntanceCound key + * @param instanceCount key * @return list of values which represent matches */ - List getAttributeValuesForInstanceCount(Integer instanceCount) { + CommonAttributeValueList getAttributeValuesForInstanceCount(Integer instanceCount) { return this.instanceCountToAttributeValues.get(instanceCount); } - /** - * Get an unmodifiable collection of values, indexed by number of - * grandchildren, which represents the common attributes found in the + /** + * Get an unmodifiable collection of values, indexed by number of + * grandchildren, which represents the common attributes found in the * search. + * * @return map of sizes of children to list of matches - */ -public Map> getMetadata() { + */ + public Map getMetadata() throws EamDbException { + if(this.percentageThreshold == 0){ + return Collections.unmodifiableMap(this.instanceCountToAttributeValues); + } else { + return this.getMetadata(this.percentageThreshold); + } + } + + /** + * Get an unmodifiable collection of values, indexed by number of + * grandchildren, which represents the common attributes found in the + * search. + * + * Remove results which are not found in the portion of available data + sources described by maximumPercentageThreshold. + * + * @return metadata + */ + private Map getMetadata(int maximumPercentageThreshold) throws EamDbException { + + if(maximumPercentageThreshold == 0){ + return Collections.unmodifiableMap(this.instanceCountToAttributeValues); + } + + CorrelationAttributeInstance.Type fileAttributeType = CorrelationAttributeInstance + .getDefaultCorrelationTypes() + .stream() + .filter(filterType -> filterType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) + .findFirst().get(); + + EamDb eamDb = EamDb.getInstance(); + + Map> itemsToRemove = new HashMap<>(); + + for(Entry listOfValues : Collections.unmodifiableMap(this.instanceCountToAttributeValues).entrySet()){ + + final Integer key = listOfValues.getKey(); + final CommonAttributeValueList values = listOfValues.getValue(); + + for(CommonAttributeValue value : values.getDelayedMetadataList()){ // Need the real metadata + + int frequencyPercentage = eamDb.getFrequencyPercentage(new CorrelationAttributeInstance(fileAttributeType, value.getValue())); + + if(frequencyPercentage > maximumPercentageThreshold){ + if(itemsToRemove.containsKey(key)){ + itemsToRemove.get(key).add(value); + } else { + List toRemove = new ArrayList<>(); + toRemove.add(value); + itemsToRemove.put(key, toRemove); + } + } + } + } + + for(Entry> valuesToRemove : itemsToRemove.entrySet()){ + + final Integer key = valuesToRemove.getKey(); + final List values = valuesToRemove.getValue(); + + for (CommonAttributeValue value : values){ + final CommonAttributeValueList instanceCountValue = this.instanceCountToAttributeValues.get(key); + instanceCountValue.removeMetaData(value); + + if(instanceCountValue.getDelayedMetadataList().isEmpty()){ // Check the real metadata + this.instanceCountToAttributeValues.remove(key); + } + } + } + return Collections.unmodifiableMap(this.instanceCountToAttributeValues); } /** * How many distinct common files exist for this search results? + * * @return number of common files */ public int size() { - + int count = 0; - for (List data : this.instanceCountToAttributeValues.values()) { - for(CommonAttributeValue md5 : data){ + for (CommonAttributeValueList data : this.instanceCountToAttributeValues.values()) { + for(CommonAttributeValue md5 : data.getMetadataList()){ count += md5.getInstanceCount(); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java index 2e9dd4675a..f6372c89f7 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java @@ -39,6 +39,7 @@ final public class CommonAttributeValue { CommonAttributeValue(String md5, List fileInstances) { this.md5 = md5; this.fileInstances = fileInstances; + } CommonAttributeValue(String md5) { @@ -85,4 +86,7 @@ final public class CommonAttributeValue { public int getInstanceCount() { return this.fileInstances.size(); } + + + } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueList.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueList.java new file mode 100644 index 0000000000..01690b357b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueList.java @@ -0,0 +1,111 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 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.commonfilesearch; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Utility and wrapper model around data required for Common Files Search + * results. Subclass this to implement different selections of files from the + * case. + */ +final public class CommonAttributeValueList { + + /** + * The list of value nodes, which begins empty. + */ + private final List metadataList; + + /** + * The backing list of value nodes, which will be dynamically loaded + * when requested. + */ + private final List delayedMetadataList; + + /** + * Create a metadata object containing the list of metadata which can be + * handed off to the node factories. + * + * @param metadata list of Md5Metadata indexed by size of Md5Metadata + */ + CommonAttributeValueList(List metadata) { + this.metadataList = new ArrayList<>(); + this.delayedMetadataList = metadata; + } + + CommonAttributeValueList() { + this.metadataList = new ArrayList<>(); + this.delayedMetadataList = new ArrayList<>(); + } + + /** + * Get the list of value nodes. Will be empty if + * displayDelayedMetadata() has not been called for the + * parent InstanceCountNode + * @return metadataList the list of nodes + */ + public List getMetadataList() { + return Collections.unmodifiableList(this.metadataList); + } + + /** + * Get the delayed list of value nodes. Only use for + * determining how many CommonAttributeValues + * actually exist in the list. + * @return metadataList the list of nodes + */ + List getDelayedMetadataList() { + return Collections.unmodifiableList(this.delayedMetadataList); + } + + void removeMetaData(CommonAttributeValue commonVal) { + this.delayedMetadataList.remove(commonVal); + } + + /** + * Return the size of the backing list, in case + * displayDelayedMetadata() has not be called yet. + * @return int the number of matches for this value + */ + int getCommonAttributeListSize() { + return this.delayedMetadataList.size(); + } + + /** + * Dynamically load the list CommonAttributeValue when called. Until called + * metadataList should be empty. The parent node, InstanceCountNode, will + * trigger the factory call and refresh. + */ + public void displayDelayedMetadata() { + if (metadataList.isEmpty()) { + this.metadataList.addAll(this.delayedMetadataList); + } + } + + /** + * A a value node to the list, to be loaded later. + * @param metadata the node to add + */ + void addMetadataToList(CommonAttributeValue metadata) { + delayedMetadataList.add(metadata); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java index c7478f3fc4..97a41b27be 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java @@ -29,6 +29,7 @@ import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import org.sleuthkit.autopsy.corecomponents.DelayedLoadChildNodesOnTreeExpansion; /** * DataResultViewerTable which overrides the default column @@ -59,7 +60,19 @@ public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTa COLUMN_WIDTHS = Collections.unmodifiableMap(map); } - + /** + * Implements a DataResultViewerTable which constructs a tabular result viewer that + * displays the children of the given root node using an OutlineView. The explorer + * manager will be discovered at runtime. + * + * Adds a TreeExpansionsListener to the outlineView to receive tree expansion events + * which dynamically loads children nodes when requested. + */ + public CommonAttributesSearchResultsViewerTable() { + super(); + outlineView.addTreeExpansionListener(new DelayedLoadChildNodesOnTreeExpansion()); + } + @NbBundle.Messages({ "CommonFilesSearchResultsViewerTable.noDescText= ", "CommonFilesSearchResultsViewerTable.filesColLbl=Files", diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index dbc3d362e9..fa298c121d 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -19,7 +19,6 @@ */ package org.sleuthkit.autopsy.commonfilesearch; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -29,52 +28,68 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; /** - * Node used to indicate the number of matches found with the MD5 children - * of this Node. + * Node used to indicate the number of matches found with the MD5 children of + * this Node. */ final public class InstanceCountNode extends DisplayableItemNode { + private static final Logger logger = Logger.getLogger(InstanceCountNode.class.getName()); + final private int instanceCount; - final private List attributeValues; + final private CommonAttributeValueList attributeValues; /** - * Create a node with the given number of instances, and the given - * selection of metadata. + * Create a node with the given number of instances, and the given selection + * of metadata. + * * @param instanceCount - * @param attributeValues + * @param attributeValues */ @NbBundle.Messages({ "InstanceCountNode.displayName=Files with %s instances (%s)" }) - public InstanceCountNode(int instanceCount, List attributeValues) { - super(Children.create(new CommonAttributeValueNodeFactory(attributeValues), true)); + public InstanceCountNode(int instanceCount, CommonAttributeValueList attributeValues) { + super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), true)); this.instanceCount = instanceCount; this.attributeValues = attributeValues; - - this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), attributeValues.size())); + + this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), attributeValues.getCommonAttributeListSize())); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } /** * Number of matches found for each of the MD5 children. + * * @return int match count */ int getInstanceCount() { return this.instanceCount; } + /** + * Refresh the node, by dynamically loading in the children when called, and + * calling the CommonAttributeValueNodeFactory to generate nodes for the + * children in attributeValues. + */ + public void refresh() { + attributeValues.displayDelayedMetadata(); + setChildren(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), true)); + } + /** * Get a list of metadata for the MD5s which are children of this object. + * * @return List */ - List getAttributeValues() { - return Collections.unmodifiableList(this.attributeValues); + CommonAttributeValueList getAttributeValues() { + return this.attributeValues; } @Override @@ -101,17 +116,23 @@ final public class InstanceCountNode extends DisplayableItemNode { sheetSet = Sheet.createPropertiesSet(); sheet.put(sheetSet); } - + final String NO_DESCR = Bundle.InstanceCountNode_createSheet_noDescription(); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, "")); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, this.getInstanceCount())); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1(), Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1(), NO_DESCR, "")); + return sheet; } - /** * ChildFactory which builds CommonFileParentNodes from the - * CommonFilesMetaaData models. + * CommonAttributeValue metadata models. */ static class CommonAttributeValueNodeFactory extends ChildFactory { @@ -138,11 +159,11 @@ final public class InstanceCountNode extends DisplayableItemNode { list.addAll(this.metadata.keySet()); return true; } - + @Override protected Node createNodeForKey(String attributeValue) { CommonAttributeValue md5Metadata = this.metadata.get(attributeValue); return new CommonAttributeValueNode(md5Metadata); } } -} \ No newline at end of file +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java index 2c745b5271..d1bff54c99 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java @@ -43,8 +43,8 @@ abstract class InterCaseCommonAttributeSearcher extends AbstractCommonAttributeS * * @throws EamDbException */ - InterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) throws EamDbException { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + InterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) throws EamDbException { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold); dbManager = EamDb.getInstance(); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java index 67aa40134b..8fc4efd01e 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java @@ -24,7 +24,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.swing.ComboBoxModel; -import org.openide.util.NbBundle; /** * UI controls for Common Files Search scenario where the user intends to find @@ -39,9 +38,7 @@ public class InterCasePanel extends javax.swing.JPanel { private ComboBoxModel casesList = new DataSourceComboBoxModel(); private final Map caseMap; - - private String errorMessage; - + //True if we are looking in any or all cases, // false if we must find matches in a given case plus the current case private boolean anyCase; @@ -51,7 +48,6 @@ public class InterCasePanel extends javax.swing.JPanel { */ public InterCasePanel() { initComponents(); - this.errorMessage = ""; this.caseMap = new HashMap<>(); this.anyCase = true; } @@ -64,10 +60,6 @@ public class InterCasePanel extends javax.swing.JPanel { } } - String getErrorMessage(){ - return this.errorMessage; - } - /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -189,16 +181,4 @@ public class InterCasePanel extends javax.swing.JPanel { return InterCasePanel.NO_CASE_SELECTED; } - - @NbBundle.Messages({ - "InterCasePanel.showInterCaseErrorMessage.message=Cannot run intercase correlation search: no cases in Central Repository." - }) - boolean areSearchCriteriaMet() { - if(this.caseMap.isEmpty()){ - this.errorMessage = Bundle.InterCasePanel_showInterCaseErrorMessage_message(); - return false; - } else { - return true; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java index 3282a92357..16ee2bd23a 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java @@ -20,10 +20,8 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; @@ -100,7 +98,7 @@ final class InterCaseSearchResultsProcessor { * * @param currentCase The current TSK Case. */ - Map> findInterCaseCommonAttributeValues(Case currentCase) { + Map findInterCaseCommonAttributeValues(Case currentCase) { try { InterCaseCommonAttributesCallback instancetableCallback = new InterCaseCommonAttributesCallback(); EamDb DbManager = EamDb.getInstance(); @@ -127,7 +125,7 @@ final class InterCaseSearchResultsProcessor { * @param currentCase The current TSK Case. * @param singleCase The case of interest. Matches must exist in this case. */ - Map> findSingleInterCaseCommonAttributeValues(Case currentCase, CorrelationCase singleCase) { + Map findSingleInterCaseCommonAttributeValues(Case currentCase, CorrelationCase singleCase) { try { InterCaseCommonAttributesCallback instancetableCallback = new InterCaseCommonAttributesCallback(); EamDb DbManager = EamDb.getInstance(); @@ -149,7 +147,7 @@ final class InterCaseSearchResultsProcessor { */ private class InterCaseCommonAttributesCallback implements InstanceTableCallback { - final Map> instanceCollatedCommonFiles = new HashMap<>(); + final Map instanceCollatedCommonFiles = new HashMap<>(); private CommonAttributeValue commonAttributeValue = null; private String previousRowMd5 = ""; @@ -183,10 +181,10 @@ final class InterCaseSearchResultsProcessor { if (!md5Value.equals(previousRowMd5)) { int size = commonAttributeValue.getInstanceCount(); if (instanceCollatedCommonFiles.containsKey(size)) { - instanceCollatedCommonFiles.get(size).add(commonAttributeValue); + instanceCollatedCommonFiles.get(size).addMetadataToList(commonAttributeValue); } else { - ArrayList value = new ArrayList<>(); - value.add(commonAttributeValue); + CommonAttributeValueList value = new CommonAttributeValueList(); + value.addMetadataToList(commonAttributeValue); instanceCollatedCommonFiles.put(size, value); } @@ -200,7 +198,7 @@ final class InterCaseSearchResultsProcessor { commonAttributeValue.addInstance(searchResult); } - Map> getInstanceCollatedCommonFiles() { + Map getInstanceCollatedCommonFiles() { return Collections.unmodifiableMap(instanceCollatedCommonFiles); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java index 172108d5ef..f02f26b869 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java @@ -55,8 +55,8 @@ public abstract class IntraCaseCommonAttributeSearcher extends AbstractCommonAtt * @param filterByDocMimeType match only on files whose mime types can be * broadly categorized as document types */ - IntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + IntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold); } /** @@ -128,9 +128,9 @@ public abstract class IntraCaseCommonAttributeSearcher extends AbstractCommonAtt } } - Map> instanceCollatedCommonFiles = collateMatchesByNumberOfInstances(commonFiles); + Map instanceCollatedCommonFiles = collateMatchesByNumberOfInstances(commonFiles); - return new CommonAttributeSearchResults(instanceCollatedCommonFiles); + return new CommonAttributeSearchResults(instanceCollatedCommonFiles, this.frequencyPercentageThreshold); } /** diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java index c62552082b..babbbd4096 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java @@ -24,8 +24,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.swing.ComboBoxModel; -import org.openide.util.NbBundle; - /** * UI controls for Common Files Search scenario where the user intends to find * common files between datasources. It is an inner panel which provides the ability @@ -40,15 +38,12 @@ public class IntraCasePanel extends javax.swing.JPanel { private boolean singleDataSource; private ComboBoxModel dataSourcesList = new DataSourceComboBoxModel(); private final Map dataSourceMap; - - private String errorMessage; /** * Creates new form IntraCasePanel */ public IntraCasePanel() { initComponents(); - this.errorMessage = ""; this.dataSourceMap = new HashMap<>(); this.singleDataSource = true; } @@ -175,20 +170,4 @@ public class IntraCasePanel extends javax.swing.JPanel { this.dataSourceMap.clear(); this.dataSourceMap.putAll(dataSourceMap); } - - @NbBundle.Messages({ - "IntraCasePanel.areSearchCriteriaMet.message=Cannot run intra-case correlation search." - }) - boolean areSearchCriteriaMet() { - if(this.dataSourceMap.isEmpty()){ - this.errorMessage = Bundle.IntraCasePanel_areSearchCriteriaMet_message(); - return false; - } else { - return true; - } - } - - String getErrorMessage() { - return this.errorMessage; - } } \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java index 6c06da8b38..5bed8625a0 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java @@ -44,8 +44,8 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri * @param filterByDocMimeType * @throws EamDbException */ - public SingleInterCaseCommonAttributeSearcher(int correlationCaseId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) throws EamDbException { - super(dataSourceIdMap,filterByMediaMimeType, filterByDocMimeType); + public SingleInterCaseCommonAttributeSearcher(int correlationCaseId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) throws EamDbException { + super(dataSourceIdMap,filterByMediaMimeType, filterByDocMimeType, percentageThreshold); this.corrleationCaseId = correlationCaseId; this.correlationCaseName = ""; @@ -72,9 +72,9 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri CommonAttributeSearchResults findFiles(CorrelationCase correlationCase) throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap()); - Map> interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseCommonAttributeValues(Case.getCurrentCase(), correlationCase); + Map interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseCommonAttributeValues(Case.getCurrentCase(), correlationCase); - return new CommonAttributeSearchResults(interCaseCommonFiles); + return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java index 02bd480093..f28f0d0bf0 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java @@ -41,8 +41,8 @@ final public class SingleIntraCaseCommonAttributeSearcher extends IntraCaseCommo * @param filterByDocMimeType match only on files whose mime types can be * broadly categorized as document types */ - public SingleIntraCaseCommonAttributeSearcher(Long dataSourceId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + public SingleIntraCaseCommonAttributeSearcher(Long dataSourceId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold); this.selectedDataSourceId = dataSourceId; this.dataSourceName = dataSourceIdMap.get(this.selectedDataSourceId); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/UserInputErrorManager.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/UserInputErrorManager.java new file mode 100644 index 0000000000..3e09e32193 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/UserInputErrorManager.java @@ -0,0 +1,130 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 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.commonfilesearch; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.openide.util.NbBundle; + +/** + * Manager for present state of errors on the Common Files Search. + */ +class UserInputErrorManager { + + static final int FREQUENCY_PERCENTAGE_OUT_OF_RANGE_KEY = 1; + static final int NO_FILE_CATEGORIES_SELECTED_KEY = 2; + + private final Map currentErrors; + + /** + * Construct a new ErrorManager which can be used to track the status + * of all known error states, retrieve error messages, and determine if + * anything is in an error state. + */ + @NbBundle.Messages({ + "UserInputErrorManager.frequency=Invalid Frequency Percentage: 0 < % < 100.", + "UserInputErrorManager.categories=No file categories are included in the search."}) + UserInputErrorManager (){ + + //when new errors are needed for the dialog, define a key and a value + // and add them to the map. + + this.currentErrors = new HashMap<>(); + this.currentErrors.put(FREQUENCY_PERCENTAGE_OUT_OF_RANGE_KEY, new ErrorMessage(Bundle.UserInputErrorManager_frequency())); + this.currentErrors.put(NO_FILE_CATEGORIES_SELECTED_KEY, new ErrorMessage(Bundle.UserInputErrorManager_categories())); + } + + /** + * Toggle the given error message on, or off + * @param errorId the error to toggle + * @param errorState true for on, false for off + */ + void setError(int errorId, boolean errorState){ + if(this.currentErrors.containsKey(errorId)){ + this.currentErrors.get(errorId).setStatus(errorState); + } else { + throw new IllegalArgumentException(String.format("The given errorId is not mapped to an ErrorMessage: %s.", errorId)); + } + } + + /** + * Are any user settings presently in an error state? + * @return true for yes, else false + */ + boolean anyErrors(){ + return this.currentErrors.values().stream().anyMatch(errorMessage -> errorMessage.isErrorSet() == true); + } + + /** + * Get a list of distinct string messages describing the various error states. + */ + List getErrors(){ + return this.currentErrors.values().stream() + .filter(errorMessage -> errorMessage.isErrorSet() == true) + .map(ErrorMessage::getMessage) + .collect(Collectors.toList()); + } + + /** + * Represents an error message for the CommonFilesSearch panel, it's + * uniqueId, and it's status. + */ + private class ErrorMessage { + + private final String message; + private boolean status; + + /** + * Create a message with a unique uniqueId. Default status is false (off). + * @param uniqueId unique uniqueId + * @param message message to display + */ + ErrorMessage(String message){ + this.message = message; + this.status = false; + } + + /** + * Update the status of this message + * @param status + */ + void setStatus(boolean status){ + this.status = status; + } + + /** + * Return the message + * @return + */ + String getMessage(){ + return this.message; + } + + /** + * Return the status (true for error status, false for no error) + * @return + */ + boolean isErrorSet(){ + return this.status; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index d6c32623a4..8ad47b32c7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -29,6 +29,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index d6dfff02ca..4a59d47410 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -111,6 +111,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { public DataResultViewerTable() { this(null, Bundle.DataResultViewerTable_title()); } + /** * Constructs a tabular result viewer that displays the children of a given @@ -1031,7 +1032,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private org.openide.explorer.view.OutlineView outlineView; + protected org.openide.explorer.view.OutlineView outlineView; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java new file mode 100644 index 0000000000..6e6d28af2a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java @@ -0,0 +1,54 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 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.corecomponents; + +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import org.openide.explorer.view.Visualizer; +import org.openide.nodes.Node; + +/** + * A tree expansion listener that will trigger a recreation of childs through + * its child factory on re-expansion of a node (causes to recreate the + * ChildFactory for this purpose.). + */ +public final class DelayedLoadChildNodesOnTreeExpansion implements TreeExpansionListener { + + /** + * A flag for avoiding endless recursion inside the expansion listener that + * could trigger collapsing and (re-)expanding nodes again. + * @param event + */ + + @Override + public synchronized void treeCollapsed(final TreeExpansionEvent event) { + // Do nothing on collapse. Netbeans should manage nodes falling out of scope and GC. + } + + @Override + public synchronized void treeExpanded(final TreeExpansionEvent event) { + Node eventNode = Visualizer.findNode(event.getPath().getLastPathComponent()); + if (eventNode instanceof TableFilterNode) { + final TableFilterNode node = (TableFilterNode) eventNode; + node.refresh(); + } + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java index eb36cf2e87..2ba02f694a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java @@ -128,6 +128,16 @@ public class TableFilterNode extends FilterNode { return null; } } + + /** + * Refreshes the inner node, which depending on the actual node type that was wrapped + * could trigger a dynamic refresh of the children, if supported. + */ + void refresh() { + DataResultFilterNode innerNode = getLookup().lookup(DataResultFilterNode.class); + innerNode.refresh(); + + } /** * @return the column order key, which allows custom column ordering to be diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index be242c8698..b83b37b4c8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -30,7 +30,6 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Children; import org.openide.nodes.Sheet; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.WeakListeners; import org.sleuthkit.autopsy.casemodule.Case; @@ -38,11 +37,8 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; -import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score; import org.sleuthkit.autopsy.coreutils.Logger; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 763d453892..ebfbbe803d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -49,11 +49,8 @@ import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; -import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score; import org.sleuthkit.autopsy.coreutils.Logger; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 098789597c..c94ad57094 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -132,26 +132,38 @@ public class DataResultFilterNode extends FilterNode { * wrapped node and may filter out some of its children. * * @param node The node to wrap. - * @param em The ExplorerManager for the component that is creating the - * node. + * @param em The ExplorerManager for the component that is creating the + * node. */ public DataResultFilterNode(Node node, ExplorerManager em) { super(node, new DataResultFilterChildren(node, em)); this.sourceEm = em; } + /** + * Refreshes the inner node. If the actual underlying node is an InstanceCountNode, + * refresh() that node, which refreshes the children. + * + */ + public void refresh() { + if (getOriginal() instanceof InstanceCountNode) { + InstanceCountNode innerNode = getLookup().lookup(InstanceCountNode.class); + innerNode.refresh(); + } + } + /** * Constructs a node used to wrap another node before passing it to the * result viewers. The wrapper node defines the actions associated with the * wrapped node and may filter out some of its children. * - * @param node The node to wrap. - * @param em The ExplorerManager for the component that is creating - * the node. + * @param node The node to wrap. + * @param em The ExplorerManager for the component that is creating the + * node. * @param filterKnown Whether or not to filter out children that represent - * known files. + * known files. * @param filterSlack Whether or not to filter out children that represent - * virtual slack space files. + * virtual slack space files. */ private DataResultFilterNode(Node node, ExplorerManager em, boolean filterKnown, boolean filterSlack) { super(node, new DataResultFilterChildren(node, em, filterKnown, filterSlack)); @@ -261,7 +273,7 @@ public class DataResultFilterNode extends FilterNode { * selected. * * @return The child node selection information, or null if no child should - * be selected. + * be selected. */ public NodeSelectionInfo getChildNodeSelectionInfo() { if (getOriginal() instanceof DisplayableItemNode) { @@ -390,14 +402,14 @@ public class DataResultFilterNode extends FilterNode { NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); } // action to go to the source file of the artifact - // action to go to the source file of the artifact + // action to go to the source file of the artifact Content fileContent = ban.getLookup().lookup(AbstractFile.class); if (fileContent == null) { Content content = ban.getLookup().lookup(Content.class); actionsList.add(new ViewContextAction("View Source Content in Directory", content)); } else { actionsList.add(new ViewContextAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban)); + NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban)); } } Content c = ban.getLookup().lookup(File.class); @@ -524,21 +536,21 @@ public class DataResultFilterNode extends FilterNode { */ private class GetPreferredActionsDisplayableItemNodeVisitor extends DisplayableItemNodeVisitor.Default { - @Override - public AbstractAction visit(InstanceCountNode icn){ + @Override + public AbstractAction visit(InstanceCountNode icn) { return null; } - + @Override public AbstractAction visit(CommonAttributeValueNode md5n){ return null; } - + @Override public AbstractAction visit(CaseDBCommonAttributeInstanceNode fin){ return null; } - + @Override public AbstractAction visit(CentralRepoCommonAttributeInstanceNode iccan){ return null; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java index da1caf380d..a425ecbcc8 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java @@ -86,7 +86,7 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); //note that the params false and false are presently meaningless because that feature is not supported yet - AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false); + AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, 0); CommonAttributeSearchResults metadata = builder.findFiles(); @@ -139,7 +139,7 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase { int matchesMustAlsoBeFoundInThisCase = this.utils.getCaseMap().get(CASE2); - AbstractCommonAttributeSearcher builder = new SingleInterCaseCommonAttributeSearcher(matchesMustAlsoBeFoundInThisCase, dataSources, false, false); + AbstractCommonAttributeSearcher builder = new SingleInterCaseCommonAttributeSearcher(matchesMustAlsoBeFoundInThisCase, dataSources, false, false, 0); CommonAttributeSearchResults metadata = builder.findFiles(); @@ -182,4 +182,56 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase { Assert.fail(ex.getMessage()); } } + + /** + * We should be able to observe that certain files o no longer returned + * in the result set since they do not appear frequently enough. + */ + public void testThree(){ + try { + Map dataSources = this.utils.getDataSourceMap(); + + //note that the params false and false are presently meaningless because that feature is not supported yet + AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, 50); + + CommonAttributeSearchResults metadata = builder.findFiles(); + + assertTrue("Results should not be empty", metadata.size() != 0); + + //case 1 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_1, CASE1, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_1, CASE1, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_1, CASE1, 1)); + + //case 1 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_2, CASE1, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_2, CASE1, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_2, CASE1, 1)); + + //case 2 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_PDF, CASE2_DATASET_1, CASE2, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_JPG, CASE2_DATASET_1, CASE2, 0)); + + //case 2 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE2_DATASET_2, CASE2, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE2_DATASET_2, CASE2, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE2_DATASET_2, CASE2, 0)); + + //case 3 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE3_DATASET_1, CASE3, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE3_DATASET_1, CASE3, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_1, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_1, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_JPG, CASE3_DATASET_1, CASE3, 0)); + + //case 3 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_2, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_2, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE3_DATASET_2, CASE3, 0)); + + } catch (TskCoreException | NoCurrentCaseException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex.getMessage()); + } + } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeIntraCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeIntraCaseTests.java index b8746675d7..f717e8a670 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeIntraCaseTests.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeIntraCaseTests.java @@ -99,7 +99,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false); + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false, 0); CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); @@ -140,7 +140,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false); + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false, 0); CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); @@ -181,7 +181,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, true); + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, true, 0); CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); @@ -223,7 +223,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long first = getDataSourceIdByName(SET1, dataSources); - AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false); + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); @@ -265,7 +265,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long first = getDataSourceIdByName(SET1, dataSources); - AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, true, false); + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, true, false, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); @@ -307,7 +307,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long first = getDataSourceIdByName(SET1, dataSources); - AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, true); + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, true, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); @@ -349,7 +349,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long second = getDataSourceIdByName(SET2, dataSources); - AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(second, dataSources, false, false); + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(second, dataSources, false, false, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); @@ -390,7 +390,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long last = getDataSourceIdByName(SET4, dataSources); - AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(last, dataSources, false, false); + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(last, dataSources, false, false, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); @@ -431,7 +431,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long third = getDataSourceIdByName(SET3, dataSources); - AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, false, false); + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, false, false, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypesIntraCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypesIntraCaseTests.java index 1c26248766..dbca68d586 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypesIntraCaseTests.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypesIntraCaseTests.java @@ -101,7 +101,7 @@ public class IngestedWithNoFileTypesIntraCaseTests extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false); + IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false, 0); CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); @@ -125,7 +125,7 @@ public class IngestedWithNoFileTypesIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long third = IntraCaseTestUtils.getDataSourceIdByName(IntraCaseTestUtils.SET3, dataSources); - IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, true, false); + IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, true, false, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/InterCaseTestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/InterCaseTestUtils.java index 303343f496..2791f725a7 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/InterCaseTestUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/InterCaseTestUtils.java @@ -57,50 +57,36 @@ import org.sleuthkit.autopsy.commonfilesearch.CentralRepoCommonAttributeInstance import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; import org.sleuthkit.autopsy.commonfilesearch.DataSourceLoader; import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValue; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValueList; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.datamodel.AbstractFile; /** * Utilities for testing intercase correlation feature. - * - * This will be more useful when we add more flush out the intercase - * correlation features and need to add more tests. In particular, - * testing scenarios where we need different cases to be the current case - * will suggest that we create additional test classes, and we will want to - * import this utility in each new intercase test file. - * - * Description of Test Data: - (Note: files of the same name and extension are identical; - files of the same name and differing extension are not identical.) - - Case 1 - +Data Set 1 - - Hash-0.dat [testFile of size 0] - - Hash-A.jpg - - Hash-A.pdf - +Data Set2 - - Hash-0.dat [testFile of size 0] - - Hash-A.jpg - - Hash-A.pdf - Case 2 - +Data Set 1 - - Hash-B.jpg - - Hash-B.pdf - +Data Set 2 - - Hash-A.jpg - - Hash-A.pdf - - Hash_D.doc - Case 3 - +Data Set 1 - - Hash-A.jpg - - Hash-A.pdf - - Hash-C.jpg - - Hash-C.pdf - - Hash-D.jpg - +Data Set 2 - - Hash-C.jpg - - Hash-C.pdf - - Hash-D.doc + * + * This will be more useful when we add more flush out the intercase correlation + * features and need to add more tests. In particular, testing scenarios where + * we need different cases to be the current case will suggest that we create + * additional test classes, and we will want to import this utility in each new + * intercase test file. + * + * Description of Test Data: (Note: files of the same name and extension are + * identical; files of the same name and differing extension are not identical.) + * + * Case 1 +Data Set 1 - Hash-0.dat [testFile of size 0] - Hash-A.jpg - + * Hash-A.pdf +Data Set2 - Hash-0.dat [testFile of size 0] - Hash-A.jpg - + * Hash-A.pdf Case 2 +Data Set 1 - Hash-B.jpg - Hash-B.pdf +Data Set 2 - + * Hash-A.jpg - Hash-A.pdf - Hash_D.doc Case 3 +Data Set 1 - Hash-A.jpg - + * Hash-A.pdf - Hash-C.jpg - Hash-C.pdf - Hash-D.jpg +Data Set 2 - Hash-C.jpg - + * Hash-C.pdf - Hash-D.doc + * + * Frequency Breakdown (ratio of datasources a given file appears in to total + * number of datasources): + * + * Hash-0.dat - moot; these are always excluded Hash-A.jpg - 4/6 Hash-A.pdf - + * 4/6 Hash-B.jpg - 1/6 Hash-B.pdf - 1/6 Hash-C.jpg - 2/6 Hash-C.pdf - 2/6 + * Hash_D.doc - 2/6 Hash-D.jpg - 1/6 + * */ class InterCaseTestUtils { @@ -173,21 +159,21 @@ class InterCaseTestUtils { this.dataSourceLoader = new DataSourceLoader(); } - void clearTestDir(){ - if(CASE_DIRECTORY_PATH.toFile().exists()){ - try{ - if(EamDb.isEnabled()) { + void clearTestDir() { + if (CASE_DIRECTORY_PATH.toFile().exists()) { + try { + if (EamDb.isEnabled()) { EamDb.getInstance().shutdownConnections(); } FileUtils.deleteDirectory(CASE_DIRECTORY_PATH.toFile()); - } catch(IOException | EamDbException ex){ + } catch (IOException | EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } } CASE_DIRECTORY_PATH.toFile().exists(); } - + Map getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException { return this.dataSourceLoader.getDataSourceMap(); } @@ -224,7 +210,7 @@ class InterCaseTestUtils { if (!crSettings.dbDirectoryExists()) { crSettings.createDbDirectory(); } - + crSettings.initializeDatabaseSchema(); crSettings.insertDefaultDatabaseContent(); @@ -303,70 +289,77 @@ class InterCaseTestUtils { return null; } } - - static boolean verifyInstanceExistanceAndCount(CommonAttributeSearchResults searchDomain, String fileName, String dataSource, String crCase, int instanceCount){ - - int tally = 0; - - for(Map.Entry> entry : searchDomain.getMetadata().entrySet()){ - - for(CommonAttributeValue value : entry.getValue()){ - - for(AbstractCommonAttributeInstance commonAttribute : value.getInstances()){ - - if(commonAttribute instanceof CentralRepoCommonAttributeInstance){ - CentralRepoCommonAttributeInstance results = (CentralRepoCommonAttributeInstance) commonAttribute; - for (DisplayableItemNode din : results.generateNodes()){ - - if(din instanceof CentralRepoCommonAttributeInstanceNode){ - - CentralRepoCommonAttributeInstanceNode node = (CentralRepoCommonAttributeInstanceNode) din; - CorrelationAttributeInstance instance = node.getCorrelationAttributeInstance(); - - final String fullPath = instance.getFilePath(); - final File testFile = new File(fullPath); - final String testCaseName = instance.getCorrelationCase().getDisplayName(); + static boolean verifyInstanceExistanceAndCount(CommonAttributeSearchResults searchDomain, String fileName, String dataSource, String crCase, int instanceCount) { - final String testFileName = testFile.getName(); + try { + int tally = 0; - final String testDataSource = instance.getCorrelationDataSource().getName(); + for (Map.Entry entry : searchDomain.getMetadata().entrySet()) { + entry.getValue().displayDelayedMetadata(); + for (CommonAttributeValue value : entry.getValue().getMetadataList()) { - boolean sameFileName = testFileName.equalsIgnoreCase(fileName); - boolean sameDataSource = testDataSource.equalsIgnoreCase(dataSource); - boolean sameCrCase = testCaseName.equalsIgnoreCase(crCase); + for (AbstractCommonAttributeInstance commonAttribute : value.getInstances()) { - if( sameFileName && sameDataSource && sameCrCase){ - tally++; - } - } - - if(din instanceof CaseDBCommonAttributeInstanceNode){ - - CaseDBCommonAttributeInstanceNode node = (CaseDBCommonAttributeInstanceNode) din; - AbstractFile file = node.getContent(); - - final String testFileName = file.getName(); - final String testCaseName = node.getCase(); - final String testDataSource = node.getDataSource(); - - boolean sameFileName = testFileName.equalsIgnoreCase(fileName); - boolean sameCaseName = testCaseName.equalsIgnoreCase(crCase); - boolean sameDataSource = testDataSource.equalsIgnoreCase(dataSource); - - if(sameFileName && sameDataSource && sameCaseName){ - tally++; + if (commonAttribute instanceof CentralRepoCommonAttributeInstance) { + CentralRepoCommonAttributeInstance results = (CentralRepoCommonAttributeInstance) commonAttribute; + for (DisplayableItemNode din : results.generateNodes()) { + + if (din instanceof CentralRepoCommonAttributeInstanceNode) { + + CentralRepoCommonAttributeInstanceNode node = (CentralRepoCommonAttributeInstanceNode) din; + CorrelationAttributeInstance instance = node.getCorrelationAttributeInstance(); + + final String fullPath = instance.getFilePath(); + final File testFile = new File(fullPath); + + final String testCaseName = instance.getCorrelationCase().getDisplayName(); + + final String testFileName = testFile.getName(); + + final String testDataSource = instance.getCorrelationDataSource().getName(); + + boolean sameFileName = testFileName.equalsIgnoreCase(fileName); + boolean sameDataSource = testDataSource.equalsIgnoreCase(dataSource); + boolean sameCrCase = testCaseName.equalsIgnoreCase(crCase); + + if (sameFileName && sameDataSource && sameCrCase) { + tally++; + } + } + + if (din instanceof CaseDBCommonAttributeInstanceNode) { + + CaseDBCommonAttributeInstanceNode node = (CaseDBCommonAttributeInstanceNode) din; + AbstractFile file = node.getContent(); + + final String testFileName = file.getName(); + final String testCaseName = node.getCase(); + final String testDataSource = node.getDataSource(); + + boolean sameFileName = testFileName.equalsIgnoreCase(fileName); + boolean sameCaseName = testCaseName.equalsIgnoreCase(crCase); + boolean sameDataSource = testDataSource.equalsIgnoreCase(dataSource); + + if (sameFileName && sameDataSource && sameCaseName) { + tally++; + } } } + } else { + Assert.fail("Unable to cast AbstractCommonAttributeInstanceNode to InterCaseCommonAttributeSearchResults."); } - } else { - Assert.fail("Unable to cast AbstractCommonAttributeInstanceNode to InterCaseCommonAttributeSearchResults."); } - } + } } + + return tally == instanceCount; + + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex.getMessage()); + return false; } - - return tally == instanceCount; } /** @@ -374,13 +367,13 @@ class InterCaseTestUtils { * central repo db. */ void tearDown() { - + CaseUtils.closeCurrentCase(false); - - String[] cases = new String[]{CASE1,CASE2,CASE3}; - + + String[] cases = new String[]{CASE1, CASE2, CASE3}; + try { - for(String caze : cases){ + for (String caze : cases) { CaseUtils.deleteCaseDir(new File(caze)); } } catch (IOException ex) { diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseTestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseTestUtils.java index c3fb4745df..7cbae0af36 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseTestUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseTestUtils.java @@ -33,10 +33,12 @@ import junit.framework.Assert; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeInstance; import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; import org.sleuthkit.autopsy.commonfilesearch.DataSourceLoader; import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValue; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValueList; import org.sleuthkit.autopsy.testutils.CaseUtils; import org.sleuthkit.autopsy.testutils.IngestUtils; import org.sleuthkit.datamodel.AbstractFile; @@ -49,24 +51,14 @@ import org.sleuthkit.datamodel.TskCoreException; * * Data set definitions: * - * set 1 - * + file1 - * - IMG_6175.jpg - * + file2 - * - IMG_6175.jpg - * + file3 - * - BasicStyleGuide.doc + * set 1 + file1 - IMG_6175.jpg + file2 - IMG_6175.jpg + file3 - + * BasicStyleGuide.doc * - * set 2 - * - adsf.pdf - * - IMG_6175.jpg + * set 2 - adsf.pdf - IMG_6175.jpg * - * set 3 - * - BasicStyleGuide.doc - * - IMG_6175.jpg + * set 3 - BasicStyleGuide.doc - IMG_6175.jpg * - * set 4 - * - file.dat (empty file) + * set 4 - file.dat (empty file) */ class IntraCaseTestUtils { @@ -91,13 +83,13 @@ class IntraCaseTestUtils { private final DataSourceLoader dataSourceLoader; private final String caseName; - - IntraCaseTestUtils(NbTestCase nbTestCase, String caseName){ + + IntraCaseTestUtils(NbTestCase nbTestCase, String caseName) { this.imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), SET1); this.imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), SET2); this.imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), SET3); this.imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), SET4); - + this.dataSourceLoader = new DataSourceLoader(); this.caseName = caseName; @@ -196,27 +188,33 @@ class IntraCaseTestUtils { } /** - * Create a convenience lookup table mapping file instance object ids to - * the data source they appear in. - * - * @param metadata object returned by the code under test + * Create a convenience lookup table mapping file instance object ids to the + * data source they appear in. + * + * @param metadata object returned by the code under test * @return mapping of objectId to data source name */ static Map mapFileInstancesToDataSources(CommonAttributeSearchResults metadata) { Map instanceIdToDataSource = new HashMap<>(); - for (Map.Entry> entry : metadata.getMetadata().entrySet()) { - for (CommonAttributeValue md : entry.getValue()) { - for (AbstractCommonAttributeInstance fim : md.getInstances()) { - instanceIdToDataSource.put(fim.getAbstractFileObjectId(), fim.getDataSource()); + try { + for (Map.Entry entry : metadata.getMetadata().entrySet()) { + entry.getValue().displayDelayedMetadata(); + for (CommonAttributeValue md : entry.getValue().getMetadataList()) { + for (AbstractCommonAttributeInstance fim : md.getInstances()) { + instanceIdToDataSource.put(fim.getAbstractFileObjectId(), fim.getDataSource()); + } } } - } - return instanceIdToDataSource; + return instanceIdToDataSource; + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex.getMessage()); + return instanceIdToDataSource; + } } - static List getFiles(Set objectIds) { List files = new ArrayList<>(objectIds.size()); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSourcesIntraCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSourcesIntraCaseTests.java index b2c1f14cb4..1105628340 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSourcesIntraCaseTests.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSourcesIntraCaseTests.java @@ -105,7 +105,7 @@ public class MatchesInAtLeastTwoSourcesIntraCaseTests extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false); + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false, 0); CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCasesIntraCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCasesIntraCaseTests.java index 3570d0a11c..dd81ba63c9 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCasesIntraCaseTests.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCasesIntraCaseTests.java @@ -28,7 +28,6 @@ import org.netbeans.junit.NbTestCase; import org.openide.util.Exceptions; import junit.framework.Assert; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.commonfilesearch.AllIntraCaseCommonAttributeSearcher; import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; import org.sleuthkit.autopsy.commonfilesearch.IntraCaseCommonAttributeSearcher; @@ -81,7 +80,7 @@ public class UningestedCasesIntraCaseTests extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false); + IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false, 0); CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); int resultCount = metadata.size(); @@ -101,7 +100,7 @@ public class UningestedCasesIntraCaseTests extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long first = getDataSourceIdByName(SET1, dataSources); - IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false); + IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false, 0); CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); int resultCount = metadata.size(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 8dddd2a3e6..e8500e6ba2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1398,10 +1398,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen AutoIngestJob job; if (nodeData.getVersion() == AutoIngestJobNodeData.getCurrentVersion()) { job = new AutoIngestJob(nodeData); - Path caseDirectory = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); - if (null != caseDirectory) { - job.setCaseDirectoryPath(caseDirectory); - } } else { job = new AutoIngestJob(manifest); job.setPriority(nodeData.getPriority()); // Retain priority, present in all versions of the node data. @@ -1431,10 +1427,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); } } - Path caseDirectory = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); - if (null != caseDirectory) { - job.setCaseDirectoryPath(caseDirectory); - } newPendingJobsList.add(job); } @@ -1595,54 +1587,53 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @throws InterruptedException */ private void addCompletedJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws CoordinationServiceException, InterruptedException, AutoIngestJobException { - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); - if (null != caseDirectoryPath) { - AutoIngestJob job; - if (nodeData.getVersion() == AutoIngestJobNodeData.getCurrentVersion()) { - job = new AutoIngestJob(nodeData); - job.setCaseDirectoryPath(caseDirectoryPath); - } else { - /** - * Use the manifest rather than the node data here to create - * a new AutoIngestJob instance because the AutoIngestJob - * constructor that takes a node data object expects the - * node data to have fields that do not exist in earlier - * versions. - */ - job = new AutoIngestJob(manifest); - job.setCaseDirectoryPath(caseDirectoryPath); + Path caseDirectoryPath = nodeData.getCaseDirectoryPath(); + if (!caseDirectoryPath.toFile().exists()) { + sysLogger.log(Level.WARNING, String.format("Job completed for %s, but cannot find case directory %s, ignoring job", nodeData.getManifestFilePath(), caseDirectoryPath.toString())); + return; + } - /** - * Update the job with the fields that exist in all versions - * of the nodeData. - */ - job.setCompletedDate(nodeData.getCompletedDate()); - job.setErrorsOccurred(nodeData.getErrorsOccurred()); - job.setPriority(nodeData.getPriority()); - job.setNumberOfCrashes(nodeData.getNumberOfCrashes()); - job.setProcessingStage(AutoIngestJob.Stage.COMPLETED, nodeData.getCompletedDate()); - job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); + AutoIngestJob job; + if (nodeData.getVersion() == AutoIngestJobNodeData.getCurrentVersion()) { + job = new AutoIngestJob(nodeData); + job.setCaseDirectoryPath(caseDirectoryPath); + } else { + /** + * Use the manifest rather than the node data here to create a + * new AutoIngestJob instance because the AutoIngestJob + * constructor that takes a node data object expects the node + * data to have fields that do not exist in earlier versions. + */ + job = new AutoIngestJob(manifest); + job.setCaseDirectoryPath(caseDirectoryPath); - /* + /** + * Update the job with the fields that exist in all versions of + * the nodeData. + */ + job.setCompletedDate(nodeData.getCompletedDate()); + job.setErrorsOccurred(nodeData.getErrorsOccurred()); + job.setPriority(nodeData.getPriority()); + job.setNumberOfCrashes(nodeData.getNumberOfCrashes()); + job.setProcessingStage(AutoIngestJob.Stage.COMPLETED, nodeData.getCompletedDate()); + job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); + + /* * Try to upgrade/update the coordination service manifest * node data for the job. It is possible that two hosts will * both try to obtain the lock to do the upgrade operation * at the same time. If this happens, the host that is * holding the lock will complete the upgrade operation. - */ - try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { - if (null != manifestLock) { - updateCoordinationServiceManifestNode(job); - } - } catch (CoordinationServiceException ex) { - sysLogger.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); + */ + try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { + if (null != manifestLock) { + updateCoordinationServiceManifestNode(job); } + } catch (CoordinationServiceException ex) { + sysLogger.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); } - newCompletedJobsList.add(job); - - } else { - sysLogger.log(Level.WARNING, String.format("Job completed for %s, but cannot find case directory, ignoring job", nodeData.getManifestFilePath())); } + newCompletedJobsList.add(job); } /** @@ -2456,6 +2447,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } currentJob.setCaseDirectoryPath(caseDirectoryPath); + updateCoordinationServiceManifestNode(currentJob); // update case directory path Case caseForJob = Case.getCurrentCase(); sysLogger.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); return caseForJob;