diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 993af4bf6e..987b2ffe78 100755 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -244,6 +244,46 @@ org.netbeans.libs.junit4 + + org.netbeans.modules.jellytools.java + + + + org.netbeans.modules.jellytools.platform + + + + org.netbeans.modules.jemmy + + + + org.netbeans.modules.nbjunit + + + + + qa-functional + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.jellytools.java + + + + org.netbeans.modules.jellytools.platform + + + + org.netbeans.modules.jemmy + + + + org.netbeans.modules.nbjunit + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form index 179bc23bb8..18d99fa0dc 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form @@ -99,11 +99,9 @@ - - diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index 7e87ce8947..d0676e1966 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -23,14 +23,18 @@ import java.awt.Desktop; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.logging.Level; import javax.swing.JDialog; import javax.swing.JOptionPane; +import javax.swing.RowSorter; +import javax.swing.SortOrder; import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; +import javax.swing.table.TableRowSorter; import org.sleuthkit.autopsy.casemodule.MultiUserCaseManager.MultiUserCase; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; @@ -146,6 +150,7 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { casesTable.removeColumn(casesTable.getColumn(OUTPUT_FOLDER_HEADER)); casesTable.removeColumn(casesTable.getColumn(METADATA_FILE_HEADER)); + casesTable.setRowSorter(new RowSorter<>(caseTableModel)); /* * Listen for row selection changes and set button state for the current @@ -313,6 +318,36 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { return ((currentTime - inputTime) / (1000 * 60 * 60 * 24)) < (numberOfUnits * multiplier); } + /** + * RowSorter which makes columns whose type is Date to be sorted first in + * Descending order then in Ascending order + */ + private static class RowSorter extends TableRowSorter { + + RowSorter(M tModel) { + super(tModel); + } + + @Override + public void toggleSortOrder(int column) { + if (!this.getModel().getColumnClass(column).equals(Date.class)) { + super.toggleSortOrder(column); //if it isn't a date column perform the regular sorting + } else { + ArrayList sortKeys = new ArrayList<>(getSortKeys()); + if (sortKeys.isEmpty() || sortKeys.get(0).getColumn() != column) { //sort descending + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); + } else if (sortKeys.get(0).getSortOrder() == SortOrder.ASCENDING) { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); + } else { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.ASCENDING)); + } + setSortKeys(sortKeys); + } + } + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -345,9 +380,7 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { } }); - casesTable.setAutoCreateRowSorter(true); casesTable.setModel(caseTableModel); - casesTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); casesTable.setRowHeight(20); casesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); casesTable.addMouseListener(new java.awt.event.MouseAdapter() { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form index e00c4a966f..50706d661f 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form @@ -3,7 +3,7 @@
- + @@ -32,19 +32,24 @@ + + + + + - + - + @@ -55,7 +60,7 @@ - + @@ -81,13 +86,18 @@ - + + + + + + @@ -103,7 +113,7 @@ - + @@ -114,7 +124,7 @@ - + @@ -137,6 +147,11 @@ + + + + + @@ -149,42 +164,35 @@ - - - - - - - - - - - + + + + - + - + - - - + + + - + @@ -224,6 +232,15 @@ + + + + + + + + + @@ -237,6 +254,15 @@ + + + + + + + + + @@ -250,6 +276,15 @@ + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java index b2497baed8..f87c6b4e33 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java @@ -93,13 +93,17 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme editRuleButton = new javax.swing.JButton(); deleteRuleButton = new javax.swing.JButton(); - setPreferredSize(new java.awt.Dimension(750, 500)); + setPreferredSize(new java.awt.Dimension(701, 453)); + + jPanel1.setPreferredSize(new java.awt.Dimension(701, 453)); org.openide.awt.Mnemonics.setLocalizedText(externalViewerTitleLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text")); // NOI18N - jSplitPane1.setDividerLocation(350); + jSplitPane1.setDividerLocation(365); jSplitPane1.setDividerSize(1); + exePanel.setPreferredSize(new java.awt.Dimension(311, 224)); + org.openide.awt.Mnemonics.setLocalizedText(exePathLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(exePathNameLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathNameLabel.text")); // NOI18N @@ -113,7 +117,7 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme .addGroup(exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(exePathLabel) .addComponent(exePathNameLabel)) - .addContainerGap(159, Short.MAX_VALUE)) + .addContainerGap(47, Short.MAX_VALUE)) ); exePanelLayout.setVerticalGroup( exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -122,17 +126,22 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme .addComponent(exePathLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(exePathNameLabel) - .addContainerGap(408, Short.MAX_VALUE)) + .addContainerGap(361, Short.MAX_VALUE)) ); jSplitPane1.setRightComponent(exePanel); + rulesPanel.setPreferredSize(new java.awt.Dimension(365, 406)); + org.openide.awt.Mnemonics.setLocalizedText(ruleListLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ruleListLabel.text")); // NOI18N rulesScrollPane.setViewportView(rulesList); newRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(newRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.newRuleButton.text")); // NOI18N + newRuleButton.setMaximumSize(new java.awt.Dimension(111, 25)); + newRuleButton.setMinimumSize(new java.awt.Dimension(111, 25)); + newRuleButton.setPreferredSize(new java.awt.Dimension(111, 25)); newRuleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { newRuleButtonActionPerformed(evt); @@ -141,6 +150,9 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme editRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/edit16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(editRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.editRuleButton.text")); // NOI18N + editRuleButton.setMaximumSize(new java.awt.Dimension(111, 25)); + editRuleButton.setMinimumSize(new java.awt.Dimension(111, 25)); + editRuleButton.setPreferredSize(new java.awt.Dimension(111, 25)); editRuleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { editRuleButtonActionPerformed(evt); @@ -149,6 +161,9 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.deleteRuleButton.text")); // NOI18N + deleteRuleButton.setMaximumSize(new java.awt.Dimension(111, 25)); + deleteRuleButton.setMinimumSize(new java.awt.Dimension(111, 25)); + deleteRuleButton.setPreferredSize(new java.awt.Dimension(111, 25)); deleteRuleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { deleteRuleButtonActionPerformed(evt); @@ -162,20 +177,16 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme .addGroup(rulesPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(rulesPanelLayout.createSequentialGroup() - .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(ruleListLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(rulesPanelLayout.createSequentialGroup() - .addComponent(rulesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 311, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) - .addGroup(rulesPanelLayout.createSequentialGroup() - .addComponent(newRuleButton) + .addComponent(ruleListLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(rulesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 345, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, rulesPanelLayout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(editRuleButton) + .addComponent(editRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(deleteRuleButton) - .addGap(0, 0, Short.MAX_VALUE)))) + .addComponent(deleteRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) ); rulesPanelLayout.setVerticalGroup( rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -183,12 +194,12 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme .addContainerGap() .addComponent(ruleListLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(rulesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(rulesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 328, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(newRuleButton) - .addComponent(editRuleButton) - .addComponent(deleteRuleButton)) + .addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(editRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(deleteRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); @@ -202,12 +213,12 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() - .addComponent(externalViewerTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 777, Short.MAX_VALUE) + .addComponent(externalViewerTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 681, Short.MAX_VALUE) .addContainerGap()) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 777, Short.MAX_VALUE) + .addComponent(jScrollPane1) .addContainerGap())) ); jPanel1Layout.setVerticalGroup( @@ -215,7 +226,7 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() .addComponent(externalViewerTitleLabel) - .addContainerGap(475, Short.MAX_VALUE)) + .addContainerGap(428, Short.MAX_VALUE)) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addGap(32, 32, 32) diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java index 970dafacfc..6aab063952 100755 --- a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java @@ -73,25 +73,4 @@ public class DurationCellRenderer extends GrayableCellRenderer { return this; } - void grayCellIfTableNotEnabled(JTable table, boolean isSelected) { - if (table.isEnabled()) { - /* - * The table is enabled, make the foreground and background the - * normal selected or unselected color. - */ - if (isSelected) { - setBackground(table.getSelectionBackground()); - setForeground(table.getSelectionForeground()); - } else { - setBackground(table.getBackground()); - setForeground(table.getForeground()); - } - } else { - /* - * The table is disabled, make the foreground and background gray. - */ - setBackground(Color.lightGray); - setForeground(Color.darkGray); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/GrayableCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/GrayableCellRenderer.java index 53031faae3..6fbf9d5133 100755 --- a/Core/src/org/sleuthkit/autopsy/guiutils/GrayableCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/GrayableCellRenderer.java @@ -45,7 +45,7 @@ public class GrayableCellRenderer extends DefaultTableCellRenderer { return this; } - void grayCellIfTableNotEnabled(JTable table, boolean isSelected) { + public void grayCellIfTableNotEnabled(JTable table, boolean isSelected) { if (table.isEnabled()) { /* * The table is enabled, make the foreground and background the diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/ShortDateCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/ShortDateCellRenderer.java index 299880f1c0..d9fabe7854 100755 --- a/Core/src/org/sleuthkit/autopsy/guiutils/ShortDateCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/ShortDateCellRenderer.java @@ -47,26 +47,5 @@ class ShortDateCellRenderer extends GrayableCellRenderer { grayCellIfTableNotEnabled(table, isSelected); return this; } - - void grayCellIfTableNotEnabled(JTable table, boolean isSelected) { - if (table.isEnabled()) { - /* - * The table is enabled, make the foreground and background the - * normal selected or unselected color. - */ - if (isSelected) { - setBackground(table.getSelectionBackground()); - setForeground(table.getSelectionForeground()); - } else { - setBackground(table.getBackground()); - setForeground(table.getForeground()); - } - } else { - /* - * The table is disabled, make the foreground and background gray. - */ - setBackground(Color.lightGray); - setForeground(Color.darkGray); - } - } + } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java index 1c761e8578..40f3d1c298 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java @@ -140,9 +140,35 @@ public class IngestOptionsPanel extends IngestModuleGlobalSettingsPanel implemen @Override public void addPropertyChangeListener(PropertyChangeListener l) { - filterPanel.addPropertyChangeListener(l); - settingsPanel.addPropertyChangeListener(l); - profilePanel.addPropertyChangeListener(l); + super.addPropertyChangeListener(l); + /* + * There is at least one look and feel library that follows the bad + * practice of calling overrideable methods in a constructor, e.g.: + * + * at + * javax.swing.plaf.synth.SynthPanelUI.installListeners(SynthPanelUI.java:83) + * at + * javax.swing.plaf.synth.SynthPanelUI.installUI(SynthPanelUI.java:63) + * at javax.swing.JComponent.setUI(JComponent.java:666) at + * javax.swing.JPanel.setUI(JPanel.java:153) at + * javax.swing.JPanel.updateUI(JPanel.java:126) at + * javax.swing.JPanel.(JPanel.java:86) at + * javax.swing.JPanel.(JPanel.java:109) at + * javax.swing.JPanel.(JPanel.java:117) + * + * When this happens, the following child components of this JPanel + * subclass have not been constructed yet, since this panel's + * constructor has not been called yet. + */ + if (null != filterPanel) { + filterPanel.addPropertyChangeListener(l); + } + if (null != settingsPanel) { + settingsPanel.addPropertyChangeListener(l); + } + if (null != profilePanel) { + profilePanel.addPropertyChangeListener(l); + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java index 249ded2020..8040893fcf 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java @@ -177,7 +177,29 @@ class ProfilePanel extends IngestModuleGlobalSettingsPanel { @Override public void addPropertyChangeListener(PropertyChangeListener l) { - ingestSettingsPanel.addPropertyChangeListener(l); + super.addPropertyChangeListener(l); + /* + * There is at least one look and feel library that follows the bad + * practice of calling overrideable methods in a constructor, e.g.: + * + * at + * javax.swing.plaf.synth.SynthPanelUI.installListeners(SynthPanelUI.java:83) + * at + * javax.swing.plaf.synth.SynthPanelUI.installUI(SynthPanelUI.java:63) + * at javax.swing.JComponent.setUI(JComponent.java:666) at + * javax.swing.JPanel.setUI(JPanel.java:153) at + * javax.swing.JPanel.updateUI(JPanel.java:126) at + * javax.swing.JPanel.(JPanel.java:86) at + * javax.swing.JPanel.(JPanel.java:109) at + * javax.swing.JPanel.(JPanel.java:117) + * + * When this happens, the following child components of this JPanel + * subclass have not been constructed yet, since this panel's + * constructor has not been called yet. + */ + if (null != ingestSettingsPanel) { + ingestSettingsPanel.addPropertyChangeListener(l); + } } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel jPanel1; diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form index 162aef8994..f8ffc03428 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form @@ -1,6 +1,11 @@ + + + + + @@ -16,12 +21,12 @@ - + - + @@ -29,7 +34,7 @@ - + @@ -37,36 +42,45 @@ - - - + + + - + - + + + + - + + + + + + + @@ -76,23 +90,26 @@ - + - + + + + + - + - + - + - @@ -101,14 +118,14 @@ - - - + + + - + - + @@ -142,6 +159,15 @@ + + + + + + + + + @@ -166,6 +192,11 @@ + + + + + @@ -198,16 +229,16 @@ - + - - - + + + - + @@ -221,6 +252,12 @@ + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java index 1d5470b4df..85cf916f2c 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java @@ -154,11 +154,18 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel removeExtButton = new javax.swing.JButton(); extHeaderLabel = new javax.swing.JLabel(); - jPanel1.setPreferredSize(new java.awt.Dimension(687, 450)); + setPreferredSize(new java.awt.Dimension(718, 430)); - jSplitPane1.setDividerLocation(430); + jPanel1.setPreferredSize(new java.awt.Dimension(718, 430)); + + jScrollPane1.setRequestFocusEnabled(false); + + jSplitPane1.setDividerLocation(365); jSplitPane1.setDividerSize(1); + mimePanel.setPreferredSize(new java.awt.Dimension(369, 424)); + mimePanel.setRequestFocusEnabled(false); + jLabel1.setText(org.openide.util.NbBundle.getMessage(FileExtMismatchSettingsPanel.class, "FileExtMismatchSettingsPanel.jLabel1.text")); // NOI18N mimeTable.setModel(mimeTableModel); @@ -166,6 +173,9 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel newTypeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N newTypeButton.setText(org.openide.util.NbBundle.getMessage(FileExtMismatchSettingsPanel.class, "FileExtMismatchSettingsPanel.newTypeButton.text")); // NOI18N + newTypeButton.setMaximumSize(new java.awt.Dimension(111, 25)); + newTypeButton.setMinimumSize(new java.awt.Dimension(111, 25)); + newTypeButton.setPreferredSize(new java.awt.Dimension(111, 25)); newTypeButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { newTypeButtonActionPerformed(evt); @@ -188,16 +198,18 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel .addGroup(mimePanelLayout.createSequentialGroup() .addContainerGap() .addGroup(mimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(mimePanelLayout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(jLabel1) + .addGap(286, 286, 286)) .addGroup(mimePanelLayout.createSequentialGroup() .addGroup(mimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel1) + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 349, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(mimePanelLayout.createSequentialGroup() - .addComponent(newTypeButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(newTypeButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(10, 10, 10) .addComponent(removeTypeButton))) - .addGap(0, 191, Short.MAX_VALUE))) - .addContainerGap()) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) ); mimePanelLayout.setVerticalGroup( mimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -205,18 +217,22 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel .addContainerGap() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 427, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 348, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(mimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(newTypeButton) + .addComponent(newTypeButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(removeTypeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); jSplitPane1.setLeftComponent(mimePanel); + extensionPanel.setPreferredSize(new java.awt.Dimension(344, 424)); + newExtButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N newExtButton.setText(org.openide.util.NbBundle.getMessage(FileExtMismatchSettingsPanel.class, "FileExtMismatchSettingsPanel.newExtButton.text")); // NOI18N + newExtButton.setMaximumSize(new java.awt.Dimension(111, 25)); + newExtButton.setMinimumSize(new java.awt.Dimension(111, 25)); newExtButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { newExtButtonActionPerformed(evt); @@ -248,7 +264,7 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel .addGroup(extensionPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(extHeaderLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 324, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(extensionPanelLayout.createSequentialGroup() - .addComponent(newExtButton) + .addComponent(newExtButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(removeExtButton))) .addGap(0, 0, Short.MAX_VALUE))) @@ -260,10 +276,10 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel .addContainerGap() .addComponent(extHeaderLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 427, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 348, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(extensionPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(newExtButton) + .addComponent(newExtButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(removeExtButton)) .addContainerGap()) ); @@ -277,27 +293,27 @@ final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 797, Short.MAX_VALUE) - .addContainerGap()) + .addGap(0, 0, 0) + .addComponent(jScrollPane1) + .addGap(0, 0, 0)) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE) - .addContainerGap()) + .addGap(0, 0, 0) + .addComponent(jScrollPane1) + .addGap(0, 0, 0)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 817, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 526, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); }// //GEN-END:initComponents diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java new file mode 100755 index 0000000000..843e9e53fa --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -0,0 +1,71 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 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.ingest; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import junit.framework.TestCase; +import org.netbeans.junit.NbModuleSuite; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; +import junit.framework.Test; +import org.apache.commons.io.FileUtils; +import org.openide.util.Exceptions; + +public class IngestFileFiltersTest extends TestCase { + + private static final Path caseDirectoryPath = Paths.get(System.getProperty("java.io.tmpdir"), "IngestFileFiltersTest"); + private static final File CASE_DIR = new File(caseDirectoryPath.toString()); + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestFileFiltersTest.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + @Override + public void setUp() { + try { + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), new CaseDetails("IngestFiltersTest")); + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + } + assertTrue(CASE_DIR.exists()); + } + + @Override + public void tearDown() { + try { + Case.closeCurrentCase(); + FileUtils.deleteDirectory(CASE_DIR); + + } catch (CaseActionException | IOException ex) { + Exceptions.printStackTrace(ex); + } + assertFalse(CASE_DIR.exists()); + } + + public void testFilter() { + System.out.println("testFilter"); + } +} diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/testutils/UnitTestDspCallback.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/FunctionalTestDspCallback.java similarity index 97% rename from Core/test/unit/src/org/sleuthkit/autopsy/testutils/UnitTestDspCallback.java rename to Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/FunctionalTestDspCallback.java index c6adb1f24c..cfa9f3dc59 100755 --- a/Core/test/unit/src/org/sleuthkit/autopsy/testutils/UnitTestDspCallback.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/FunctionalTestDspCallback.java @@ -31,7 +31,7 @@ import org.sleuthkit.datamodel.Content; * thread. */ @Immutable -public class UnitTestDspCallback extends DataSourceProcessorCallback { +public class FunctionalTestDspCallback extends DataSourceProcessorCallback { private final Object monitor; private final List errorMessages = new ArrayList<>(); @@ -46,7 +46,7 @@ public class UnitTestDspCallback extends DataSourceProcessorCallback { * @param monitor A monitor for the callback to signal when the data source * processor completes its processing. */ - UnitTestDspCallback(Object monitor) { + FunctionalTestDspCallback(Object monitor) { this.monitor = monitor; } diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/testutils/UnitTestDspProgressMonitor.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/FunctionalTestDspProgressMonitor.java similarity index 95% rename from Core/test/unit/src/org/sleuthkit/autopsy/testutils/UnitTestDspProgressMonitor.java rename to Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/FunctionalTestDspProgressMonitor.java index 1f856e6500..03d26eb9b2 100755 --- a/Core/test/unit/src/org/sleuthkit/autopsy/testutils/UnitTestDspProgressMonitor.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/FunctionalTestDspProgressMonitor.java @@ -25,7 +25,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress * A data source processor progress monitor for unit testing. */ @Immutable -public class UnitTestDspProgressMonitor implements DataSourceProcessorProgressMonitor { +public class FunctionalTestDspProgressMonitor implements DataSourceProcessorProgressMonitor { /** * Switches the progress indicator to indeterminate mode (the total number diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/ingest/IntestFileFiltersTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java similarity index 94% rename from Core/test/unit/src/org/sleuthkit/autopsy/ingest/IntestFileFiltersTest.java rename to Core/test/unit/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index b952462de8..29618e30ec 100755 --- a/Core/test/unit/src/org/sleuthkit/autopsy/ingest/IntestFileFiltersTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -23,9 +23,9 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -public class IntestFileFiltersTest { +public class IngestFileFiltersTest { - public IntestFileFiltersTest() { + public IngestFileFiltersTest() { } @BeforeClass diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form index ad525e92ee..cec9d21492 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.form @@ -169,7 +169,6 @@ - @@ -193,7 +192,6 @@ - @@ -217,7 +215,6 @@ - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index fa4476d6f5..7b893ae0b9 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -39,7 +39,6 @@ import javax.swing.DefaultListSelectionModel; import java.awt.Color; import java.beans.PropertyChangeEvent; import java.io.File; -import java.util.Collections; import java.util.logging.Logger; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -141,6 +140,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { private static final int GENERIC_COL_MAX_WIDTH = 2000; private static final int PENDING_TABLE_COL_PREFERRED_WIDTH = 280; private static final int RUNNING_TABLE_COL_PREFERRED_WIDTH = 175; + private static final int PRIORITY_COLUMN_PREFERRED_WIDTH = 60; + private static final int PRIORITY_COLUMN_MAX_WIDTH = 150; private static final int ACTIVITY_TIME_COL_MIN_WIDTH = 250; private static final int ACTIVITY_TIME_COL_MAX_WIDTH = 450; private static final int TIME_COL_MIN_WIDTH = 30; @@ -180,6 +181,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * ordinal or a column header string. */ @Messages({ + "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Priority=Prioritized", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Case=Case", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.ImageFolder=Data Source", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.HostName=Host Name", @@ -206,8 +208,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { STATUS(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Status")), CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.CaseFolder")), IS_LOCAL_JOB(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.LocalJob")), - MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.ManifestFilePath")); - + MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.ManifestFilePath")), + PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Priority")); private final String header; private JobsTableModelColumns(String header) { @@ -230,7 +232,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { STAGE_TIME.getColumnHeader(), CASE_DIRECTORY_PATH.getColumnHeader(), IS_LOCAL_JOB.getColumnHeader(), - MANIFEST_FILE_PATH.getColumnHeader()}; + MANIFEST_FILE_PATH.getColumnHeader(), + PRIORITY.getColumnHeader()}; } /** @@ -262,32 +265,11 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { manager = AutoIngestManager.getInstance(); - pendingTableModel = new DefaultTableModel(JobsTableModelColumns.headers, 0) { - private static final long serialVersionUID = 1L; + pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - }; + runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - runningTableModel = new DefaultTableModel(JobsTableModelColumns.headers, 0) { - private static final long serialVersionUID = 1L; - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - }; - - completedTableModel = new DefaultTableModel(JobsTableModelColumns.headers, 0) { - private static final long serialVersionUID = 1L; - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - }; + completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); initComponents(); // Generated code. setServicesStatusMessage(); @@ -295,7 +277,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { initRunningJobsTable(); initCompletedJobsTable(); initButtons(); - + completedTable.getRowSorter().toggleSortOrder(JobsTableModelColumns.COMPLETED_TIME.ordinal()); /* * Must set this flag, otherwise pop up menus don't close properly. */ @@ -415,10 +397,16 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); column.setWidth(TIME_COL_PREFERRED_WIDTH); + column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()); + column.setCellRenderer(new PrioritizedIconCellRenderer()); + column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH); + column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); + column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); + /** - * Prevent sorting when a column header is clicked. + * Allow sorting when a column header is clicked. */ - pendingTable.setAutoCreateRowSorter(false); + pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel)); /* * Create a row selection listener to enable/disable the prioritize @@ -457,7 +445,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.IS_LOCAL_JOB.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - + runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); /* * Set up a column to display the cases associated with the jobs. */ @@ -527,9 +515,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { } private void updateRunningTableButtonsBasedOnSelectedRow() { - int row = runningTable.getSelectedRow(); + int row = runningTable.convertRowIndexToModel(runningTable.getSelectedRow()); if (row >= 0 && row < runningTable.getRowCount()) { - if ((boolean) runningTableModel.getValueAt(row, JobsTableModelColumns.IS_LOCAL_JOB.ordinal())) { + if ((boolean) runningTable.getModel().getValueAt(row, JobsTableModelColumns.IS_LOCAL_JOB.ordinal())) { enableRunningTableButtons(true); return; } @@ -547,13 +535,13 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * does not remove the columns from the model, just from this table. */ completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); + completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.IS_LOCAL_JOB.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - + completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); /* * Set up a column to display the cases associated with the jobs. */ @@ -606,9 +594,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { column.setWidth(STATUS_COL_PREFERRED_WIDTH); /* - * Prevent sorting when a column header is clicked. + * Allow sorting when a column header is clicked. */ - completedTable.setAutoCreateRowSorter(false); + completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel)); /* * Create a row selection listener to enable/disable the delete case and @@ -985,7 +973,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { List completedJobs = new ArrayList<>(); manager.getJobs(pendingJobs, runningJobs, completedJobs); // Sort the completed jobs list by completed date - Collections.sort(completedJobs, new AutoIngestJob.CompletedDateDescendingComparator()); EventQueue.invokeLater(new RefreshComponentsTask(pendingJobs, runningJobs, completedJobs)); } } @@ -1033,9 +1020,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { */ if (null != pendingJobs) { - Path currentRow = getSelectedEntry(pendingTable, pendingTableModel); - refreshTable(pendingJobs, pendingTableModel, null); - setSelectedEntry(pendingTable, pendingTableModel, currentRow); + Path currentRow = getSelectedEntry(pendingTable); + refreshTable(pendingJobs, (DefaultTableModel) pendingTable.getModel(), null); + setSelectedEntry(pendingTable, currentRow); } if (null != runningJobs) { @@ -1044,15 +1031,15 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { } else { updateRunningTableButtonsBasedOnSelectedRow(); } - Path currentRow = getSelectedEntry(runningTable, runningTableModel); - refreshTable(runningJobs, runningTableModel, null); - setSelectedEntry(runningTable, runningTableModel, currentRow); + Path currentRow = getSelectedEntry(runningTable); + refreshTable(runningJobs, (DefaultTableModel) runningTable.getModel(), null); + setSelectedEntry(runningTable, currentRow); } if (null != completedJobs) { - Path currentRow = getSelectedEntry(completedTable, completedTableModel); - refreshTable(completedJobs, completedTableModel, null); - setSelectedEntry(completedTable, completedTableModel, currentRow); + Path currentRow = getSelectedEntry(completedTable); + refreshTable(completedJobs, (DefaultTableModel) completedTable.getModel(), null); + setSelectedEntry(completedTable, currentRow); } } @@ -1090,12 +1077,12 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * * @return a path representing the current selection */ - Path getSelectedEntry(JTable table, DefaultTableModel tableModel) { + Path getSelectedEntry(JTable table) { try { - int currentlySelectedRow = table.getSelectedRow(); + int currentlySelectedRow = table.convertRowIndexToModel(table.getSelectedRow()); if (currentlySelectedRow >= 0 && currentlySelectedRow < table.getRowCount()) { - return Paths.get(tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.CASE.ordinal()).toString(), - tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); + return Paths.get(table.getModel().getValueAt(currentlySelectedRow, JobsTableModelColumns.CASE.ordinal()).toString(), + table.getModel().getValueAt(currentlySelectedRow, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); } } catch (Exception ignored) { return null; @@ -1111,12 +1098,12 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * @param tableModel The tableModel of the table to set * @param path The path of the item to set */ - void setSelectedEntry(JTable table, DefaultTableModel tableModel, Path path) { + void setSelectedEntry(JTable table, Path path) { if (path != null) { try { for (int row = 0; row < table.getRowCount(); ++row) { - Path temp = Paths.get(tableModel.getValueAt(row, JobsTableModelColumns.CASE.ordinal()).toString(), - tableModel.getValueAt(row, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); + Path temp = Paths.get(table.getModel().getValueAt(row, JobsTableModelColumns.CASE.ordinal()).toString(), + table.getModel().getValueAt(row, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); if (temp.compareTo(path) == 0) { // found it table.setRowSelectionInterval(row, row); return; @@ -1159,7 +1146,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH job.getProcessingHostName().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB - job.getManifest().getFilePath()}); // MANIFEST_FILE_PATH + job.getManifest().getFilePath(), // MANIFEST_FILE_PATH + job.getPriority()}); // PRIORITY } } catch (Exception ex) { SYS_LOGGER.log(Level.SEVERE, "Dashboard error refreshing table", ex); @@ -1171,9 +1159,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { */ private void refreshTables() { JobsSnapshot jobsSnapshot = manager.getCurrentJobsSnapshot(); - refreshTable(jobsSnapshot.getCompletedJobs(), completedTableModel, null); - refreshTable(jobsSnapshot.getPendingJobs(), pendingTableModel, null); - refreshTable(jobsSnapshot.getRunningJobs(), runningTableModel, null); + refreshTable(jobsSnapshot.getCompletedJobs(), (DefaultTableModel) completedTable.getModel(), null); + refreshTable(jobsSnapshot.getPendingJobs(), (DefaultTableModel) pendingTable.getModel(), null); + refreshTable(jobsSnapshot.getRunningJobs(), (DefaultTableModel) runningTable.getModel(), null); } /** @@ -1214,7 +1202,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { pendingTable.setModel(pendingTableModel); pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.pendingTable.toolTipText")); // NOI18N - pendingTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); pendingTable.setRowHeight(20); pendingTable.setSelectionModel(new DefaultListSelectionModel() { private static final long serialVersionUID = 1L; @@ -1232,7 +1219,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { runningTable.setModel(runningTableModel); runningTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.runningTable.toolTipText")); // NOI18N - runningTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); runningTable.setRowHeight(20); runningTable.setSelectionModel(new DefaultListSelectionModel() { private static final long serialVersionUID = 1L; @@ -1250,7 +1236,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { completedTable.setModel(completedTableModel); completedTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.completedTable.toolTipText")); // NOI18N - completedTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); completedTable.setRowHeight(20); completedTable.setSelectionModel(new DefaultListSelectionModel() { private static final long serialVersionUID = 1L; @@ -1428,10 +1413,10 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { .addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 920, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) - .addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, 116, Short.MAX_VALUE) - .addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) - .addComponent(bnDeleteCase, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) + .addComponent(bnCancelJob, javax.swing.GroupLayout.PREFERRED_SIZE, 117, Short.MAX_VALUE) + .addComponent(bnShowProgress, javax.swing.GroupLayout.PREFERRED_SIZE, 116, Short.MAX_VALUE) + .addComponent(bnCancelModule, javax.swing.GroupLayout.PREFERRED_SIZE, 117, Short.MAX_VALUE) + .addComponent(bnDeleteCase, javax.swing.GroupLayout.PREFERRED_SIZE, 117, Short.MAX_VALUE) .addComponent(bnShowCaseLog, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(bnReprocessJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addGroup(layout.createSequentialGroup() @@ -1538,11 +1523,11 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { "AutoIngestControlPanel.DeletionFailed=Deletion failed for job" }) private void bnDeleteCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDeleteCaseActionPerformed - if (completedTableModel.getRowCount() < 0 || completedTable.getSelectedRow() < 0) { + if (completedTable.getModel().getRowCount() < 0 || completedTable.getSelectedRow() < 0) { return; } - String caseName = (String) completedTable.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal()); + String caseName = (String) completedTable.getModel().getValueAt(completedTable.convertRowIndexToModel(completedTable.getSelectedRow()), JobsTableModelColumns.CASE.ordinal()); Object[] options = { org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.Delete"), org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DoNotDelete") @@ -1559,8 +1544,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { if (reply == JOptionPane.YES_OPTION) { bnDeleteCase.setEnabled(false); bnShowCaseLog.setEnabled(false); - if (completedTableModel.getRowCount() > 0 && completedTable.getSelectedRow() >= 0) { - Path caseDirectoryPath = (Path) completedTableModel.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal()); + if (completedTable.getModel().getRowCount() > 0 && completedTable.getSelectedRow() >= 0) { + Path caseDirectoryPath = (Path) completedTable.getModel().getValueAt(completedTable.convertRowIndexToModel(completedTable.getSelectedRow()), JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal()); completedTable.clearSelection(); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); CaseDeletionResult result = manager.deleteCase(caseName, caseDirectoryPath); @@ -1706,9 +1691,10 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { */ @Messages({"AutoIngestControlPanel.casePrioritization.errorMessage=An error occurred when prioritizing the case. Some or all jobs may not have been prioritized."}) private void bnPrioritizeCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnPrioritizeCaseActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { + if (pendingTable.getModel().getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); + + String caseName = (pendingTable.getModel().getValueAt(pendingTable.convertRowIndexToModel(pendingTable.getSelectedRow()), JobsTableModelColumns.CASE.ordinal())).toString(); try { manager.prioritizeCase(caseName); } catch (AutoIngestManager.AutoIngestManagerException ex) { @@ -1734,9 +1720,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { }) private void bnShowCaseLogActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnShowCaseLogActionPerformed try { - int selectedRow = completedTable.getSelectedRow(); + int selectedRow = completedTable.convertRowIndexToModel(completedTable.getSelectedRow()); if (selectedRow != -1) { - Path caseDirectoryPath = (Path) completedTableModel.getValueAt(selectedRow, JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal()); + Path caseDirectoryPath = (Path) completedTable.getModel().getValueAt(selectedRow, JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal()); if (null != caseDirectoryPath) { Path pathToLog = AutoIngestJobLogger.getLogPath(caseDirectoryPath); if (pathToLog.toFile().exists()) { @@ -1765,9 +1751,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { @Messages({"AutoIngestControlPanel.jobPrioritization.errorMessage=An error occurred when prioritizing the job."}) private void bnPrioritizeJobActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnPrioritizeJobActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { + if (pendingTable.getModel().getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - Path manifestFilePath = (Path) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.MANIFEST_FILE_PATH.ordinal())); + Path manifestFilePath = (Path) (pendingTable.getModel().getValueAt(pendingTable.convertRowIndexToModel(pendingTable.getSelectedRow()), JobsTableModelColumns.MANIFEST_FILE_PATH.ordinal())); try { manager.prioritizeJob(manifestFilePath); } catch (AutoIngestManager.AutoIngestManagerException ex) { @@ -1794,11 +1780,11 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { }//GEN-LAST:event_bnOpenLogDirActionPerformed private void bnReprocessJobActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnReprocessJobActionPerformed - if (completedTableModel.getRowCount() < 0 || completedTable.getSelectedRow() < 0) { + if (completedTable.getModel().getRowCount() < 0 || completedTable.getSelectedRow() < 0) { return; } this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - Path manifestPath = (Path) completedTableModel.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.MANIFEST_FILE_PATH.ordinal()); + Path manifestPath = (Path) completedTable.getModel().getValueAt(completedTable.convertRowIndexToModel(completedTable.getSelectedRow()), JobsTableModelColumns.MANIFEST_FILE_PATH.ordinal()); manager.reprocessJob(manifestPath); refreshTables(); AutoIngestControlPanel.this.setCursor(Cursor.getDefaultCursor()); @@ -1833,4 +1819,33 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { private javax.swing.JTextField tbStatusMessage; // End of variables declaration//GEN-END:variables + private class AutoIngestTableModel extends DefaultTableModel { + + private static final long serialVersionUID = 1L; + + private AutoIngestTableModel(String[] headers, int i) { + super(headers, i); + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) { + return Integer.class; + } else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal() + || columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal() + || columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal() + || columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) { + return Date.class; + } else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) { + return Boolean.class; + } else { + return super.getColumnClass(columnIndex); + } + } + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 9a599877f8..78ae8fb2a4 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -106,7 +106,6 @@ - @@ -130,7 +129,6 @@ - @@ -154,7 +152,6 @@ - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index feafba3f4f..d53503a72f 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -51,13 +51,15 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; /** * A dashboard for monitoring an automated ingest cluster. */ -public final class AutoIngestDashboard extends JPanel implements Observer { +final class AutoIngestDashboard extends JPanel implements Observer { private static final long serialVersionUID = 1L; private static final int GENERIC_COL_MIN_WIDTH = 30; private static final int GENERIC_COL_MAX_WIDTH = 2000; private static final int PENDING_TABLE_COL_PREFERRED_WIDTH = 280; private static final int RUNNING_TABLE_COL_PREFERRED_WIDTH = 175; + private static final int PRIORITY_COLUMN_PREFERRED_WIDTH = 60; + private static final int PRIORITY_COLUMN_MAX_WIDTH = 150; private static final int STAGE_TIME_COL_MIN_WIDTH = 250; private static final int STAGE_TIME_COL_MAX_WIDTH = 450; private static final int TIME_COL_MIN_WIDTH = 30; @@ -103,32 +105,11 @@ public final class AutoIngestDashboard extends JPanel implements Observer { * Constructs a panel for monitoring an automated ingest cluster. */ private AutoIngestDashboard() { - pendingTableModel = new DefaultTableModel(JobsTableModelColumns.headers, 0) { - private static final long serialVersionUID = 1L; + pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - }; + runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - runningTableModel = new DefaultTableModel(JobsTableModelColumns.headers, 0) { - private static final long serialVersionUID = 1L; - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - }; - - completedTableModel = new DefaultTableModel(JobsTableModelColumns.headers, 0) { - private static final long serialVersionUID = 1L; - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - }; + completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); initComponents(); setServicesStatusMessage(); @@ -246,10 +227,15 @@ public final class AutoIngestDashboard extends JPanel implements Observer { column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); column.setWidth(TIME_COL_PREFERRED_WIDTH); - /** - * Prevent sorting when a column header is clicked. + column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()); + column.setCellRenderer(new PrioritizedIconCellRenderer()); + column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH); + column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); + column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); + /* + * Allow sorting when a column header is clicked. */ - pendingTable.setAutoCreateRowSorter(false); + pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel)); /* * Create a row selection listener to enable/disable the Prioritize @@ -260,8 +246,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { return; } int row = pendingTable.getSelectedRow(); - - boolean enablePrioritizeButtons = (row >= 0 && row < pendingTable.getRowCount()); + + boolean enablePrioritizeButtons = (row >= 0 && row < pendingTable.getRowCount()); this.prioritizeJobButton.setEnabled(enablePrioritizeButtons); this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons); }); @@ -283,7 +269,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - + runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); /* * Set up a column to display the cases associated with the jobs. */ @@ -357,7 +343,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - + completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); /* * Set up a column to display the cases associated with the jobs. */ @@ -408,11 +394,10 @@ public final class AutoIngestDashboard extends JPanel implements Observer { column.setMaxWidth(STATUS_COL_MAX_WIDTH); column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH); column.setWidth(STATUS_COL_PREFERRED_WIDTH); - /* - * Prevent sorting when a column header is clicked. + * Allow sorting when a column header is clicked. */ - completedTable.setAutoCreateRowSorter(false); + completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel)); } /** @@ -479,6 +464,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // STAGE_TIME job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH job.getManifest().getFilePath(), // MANIFEST_FILE_PATH + job.getPriority(), // PRIORITY job }); } @@ -544,6 +530,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { * described by either an enum ordinal or a column header string. */ private enum JobsTableModelColumns { + @Messages({"AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority=Prioritized"}) CASE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Case")), DATA_SOURCE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder")), @@ -556,6 +543,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { STATUS(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Status")), CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder")), MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath")), + PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority")), JOB(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Job")); private final String header; @@ -580,6 +568,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { STAGE_TIME.getColumnHeader(), CASE_DIRECTORY_PATH.getColumnHeader(), MANIFEST_FILE_PATH.getColumnHeader(), + PRIORITY.getColumnHeader(), JOB.getColumnHeader() }; }; @@ -671,7 +660,6 @@ public final class AutoIngestDashboard extends JPanel implements Observer { pendingTable.setModel(pendingTableModel); pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N - pendingTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); pendingTable.setRowHeight(20); pendingTable.setSelectionModel(new DefaultListSelectionModel() { private static final long serialVersionUID = 1L; @@ -689,7 +677,6 @@ public final class AutoIngestDashboard extends JPanel implements Observer { runningTable.setModel(runningTableModel); runningTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.runningTable.toolTipText")); // NOI18N - runningTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); runningTable.setRowHeight(20); runningTable.setSelectionModel(new DefaultListSelectionModel() { private static final long serialVersionUID = 1L; @@ -707,7 +694,6 @@ public final class AutoIngestDashboard extends JPanel implements Observer { completedTable.setModel(completedTableModel); completedTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.completedTable.toolTipText")); // NOI18N - completedTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); completedTable.setRowHeight(20); completedTable.setSelectionModel(new DefaultListSelectionModel() { private static final long serialVersionUID = 1L; @@ -909,4 +895,33 @@ public final class AutoIngestDashboard extends JPanel implements Observer { private javax.swing.JTextField tbServicesStatusMessage; // End of variables declaration//GEN-END:variables + private class AutoIngestTableModel extends DefaultTableModel { + + private static final long serialVersionUID = 1L; + + private AutoIngestTableModel(String[] headers, int i) { + super(headers, i); + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) { + return Integer.class; + } else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal() + || columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal() + || columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal() + || columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) { + return Date.class; + } else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) { + return Boolean.class; + } else { + return super.getColumnClass(columnIndex); + } + } + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index e6187877c3..1cbe9af5a6 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -498,6 +498,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } SYS_LOGGER.log(Level.INFO, "Starting input scan of {0}", rootInputDirectory); InputDirScanner scanner = new InputDirScanner(); + scanner.scan(); SYS_LOGGER.log(Level.INFO, "Completed input scan of {0}", rootInputDirectory); } @@ -553,10 +554,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (!prioritizedJobs.isEmpty()) { ++maxPriority; for (AutoIngestJob job : prioritizedJobs) { + int oldPriority = job.getPriority(); + job.setPriority(maxPriority); try { this.updateCoordinationServiceManifestNode(job); - job.setPriority(maxPriority); } catch (CoordinationServiceException | InterruptedException ex) { + job.setPriority(oldPriority); throw new AutoIngestManagerException("Error updating case priority", ex); } } @@ -607,12 +610,14 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen */ if (null != prioritizedJob) { ++maxPriority; + int oldPriority = prioritizedJob.getPriority(); + prioritizedJob.setPriority(maxPriority); try { this.updateCoordinationServiceManifestNode(prioritizedJob); } catch (CoordinationServiceException | InterruptedException ex) { + prioritizedJob.setPriority(oldPriority); throw new AutoIngestManagerException("Error updating job priority", ex); } - prioritizedJob.setPriority(maxPriority); } Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); @@ -1237,10 +1242,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @param manifest The manifest for upgrading the node. * @param jobNodeData The auto ingest job node data. * - * @throws InterruptedException if the thread running the input - * directory scan task is interrupted while - * blocked, i.e., if auto ingest is - * shutting down. + * @throws InterruptedException if the thread running the input + * directory scan task is interrupted + * while blocked, i.e., if auto ingest is + * shutting down. + * @throws AutoIngestJobException if there is an issue creating a new + * AutoIngestJob object. */ private void doRecoveryIfCrashed(Manifest manifest, AutoIngestJobNodeData jobNodeData) throws InterruptedException, AutoIngestJobException { /* @@ -1253,49 +1260,35 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (null != manifestLock) { SYS_LOGGER.log(Level.SEVERE, "Attempting crash recovery for {0}", manifestPath); try { + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); + /* * Create the recovery job. */ AutoIngestJob job = new AutoIngestJob(jobNodeData); int numberOfCrashes = job.getNumberOfCrashes(); - ++numberOfCrashes; - job.setNumberOfCrashes(numberOfCrashes); - job.setCompletedDate(new Date(0)); - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); + if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { + ++numberOfCrashes; + job.setNumberOfCrashes(numberOfCrashes); + if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { + job.setCompletedDate(new Date(0)); + } else { + job.setCompletedDate(Date.from(Instant.now())); + } + } + if (null != caseDirectoryPath) { job.setCaseDirectoryPath(caseDirectoryPath); job.setErrorsOccurred(true); - } else { - job.setErrorsOccurred(false); - } - - /* - * Update the coordination service manifest node for the - * job. If this fails, leave the recovery to another - * host. - */ - try { - updateCoordinationServiceManifestNode(job); - if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { - newPendingJobsList.add(job); - } else { - newCompletedJobsList.add(new AutoIngestJob(jobNodeData)); - } - } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifestPath), ex); - return; - } - - /* - * Update the case node data and do the logging. - */ - if (null != caseDirectoryPath) { try { setCaseNodeDataErrorsOccurred(caseDirectoryPath); } catch (CaseNodeData.InvalidDataException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to get case node data for %s", caseDirectoryPath), ex); } + } else { + job.setErrorsOccurred(false); } + if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { job.setProcessingStatus(AutoIngestJob.ProcessingStatus.PENDING); if (null != caseDirectoryPath) { @@ -1309,13 +1302,32 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); if (null != caseDirectoryPath) { try { - new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), jobNodeData.getCaseDirectoryPath()).logCrashRecoveryNoRetry(); + new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryNoRetry(); } catch (AutoIngestJobLoggerException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); } } } + /* + * Update the coordination service node for the job. If + * this fails, leave the recovery to another host. + */ + try { + updateCoordinationServiceManifestNode(job); + } catch (CoordinationServiceException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifestPath), ex); + return; + } + + jobNodeData = new AutoIngestJobNodeData(job); + + if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { + newPendingJobsList.add(job); + } else { + newCompletedJobsList.add(new AutoIngestJob(jobNodeData)); + } + } finally { try { manifestLock.release(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java new file mode 100644 index 0000000000..31ecd5f4ca --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java @@ -0,0 +1,57 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.util.ArrayList; +import java.util.Date; +import javax.swing.RowSorter; +import javax.swing.SortOrder; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableRowSorter; + +/** + * RowSorter which makes columns whose type is Date to be sorted first in + * Descending order then in Ascending order + */ +class AutoIngestRowSorter extends TableRowSorter { + + AutoIngestRowSorter(M tModel) { + super(tModel); + } + + @Override + public void toggleSortOrder(int column) { + if (!this.getModel().getColumnClass(column).equals(Date.class) && !this.getModel().getColumnClass(column).equals(Integer.class)) { + //currently the only Integer column this sorter is being applied to is the Priority column + super.toggleSortOrder(column); //if it isn't a date or Integer column perform the regular sorting + } else { + ArrayList sortKeys = new ArrayList<>(getSortKeys()); + if (sortKeys.isEmpty() || sortKeys.get(0).getColumn() != column) { //sort descending + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); + } else if (sortKeys.get(0).getSortOrder() == SortOrder.ASCENDING) { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); + } else { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.ASCENDING)); + } + setSortKeys(sortKeys); + } + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizedIconCellRenderer.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizedIconCellRenderer.java new file mode 100644 index 0000000000..a4066e6c15 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizedIconCellRenderer.java @@ -0,0 +1,60 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.Component; +import javax.swing.ImageIcon; +import javax.swing.JTable; +import static javax.swing.SwingConstants.CENTER; +import org.openide.util.ImageUtilities; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.guiutils.GrayableCellRenderer; +import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; + +/** + * A JTable cell renderer that represents whether the priority value of a job + * has ever been increased, tick if prioritized nothing if not. + */ +class PrioritizedIconCellRenderer extends GrayableCellRenderer { + + + @Messages({ + "PrioritizedIconCellRenderer.prioritized.tooltiptext=This job has been prioritized. The most recently prioritized job should be processed next.", + "PrioritizedIconCellRenderer.notPrioritized.tooltiptext=This job has not been prioritized." + }) + private static final long serialVersionUID = 1L; + static final ImageIcon checkedIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/experimental/images/tick.png", false)); + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + setHorizontalAlignment(CENTER); + if ((value instanceof Integer)) { + if ((int) value == 0) { + setIcon(null); + setToolTipText(org.openide.util.NbBundle.getMessage(PrioritizedIconCellRenderer.class, "PrioritizedIconCellRenderer.notPrioritized.tooltiptext")); + } else { + setIcon(checkedIcon); + setToolTipText(org.openide.util.NbBundle.getMessage(PrioritizedIconCellRenderer.class, "PrioritizedIconCellRenderer.prioritized.tooltiptext")); + } + } + grayCellIfTableNotEnabled(table, isSelected); + + return this; + } +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java index e28cd75654..862635ad4a 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java @@ -139,8 +139,31 @@ final class GlobalListSettingsPanel extends javax.swing.JPanel implements Option @Override public void addPropertyChangeListener(PropertyChangeListener l) { super.addPropertyChangeListener(l); - listsManagementPanel.addPropertyChangeListener(l); - editListPanel.addPropertyChangeListener(l); + /* + * There is at least one look and feel library that follows the bad + * practice of calling overrideable methods in a constructor, e.g.: + * + * at + * javax.swing.plaf.synth.SynthPanelUI.installListeners(SynthPanelUI.java:83) + * at + * javax.swing.plaf.synth.SynthPanelUI.installUI(SynthPanelUI.java:63) + * at javax.swing.JComponent.setUI(JComponent.java:666) at + * javax.swing.JPanel.setUI(JPanel.java:153) at + * javax.swing.JPanel.updateUI(JPanel.java:126) at + * javax.swing.JPanel.(JPanel.java:86) at + * javax.swing.JPanel.(JPanel.java:109) at + * javax.swing.JPanel.(JPanel.java:117) + * + * When this happens, the following child components of this JPanel + * subclass have not been constructed yet, since this panel's + * constructor has not been called yet. + */ + if (null != listsManagementPanel) { + listsManagementPanel.addPropertyChangeListener(l); + } + if (null != editListPanel) { + editListPanel.addPropertyChangeListener(l); + } } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java index 8e820b4c74..c34e672a1c 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java @@ -52,9 +52,34 @@ final class KeywordSearchGlobalSettingsPanel extends IngestModuleGlobalSettingsP @Override public void addPropertyChangeListener(PropertyChangeListener l) { super.addPropertyChangeListener(l); - listsPanel.addPropertyChangeListener(l); - languagesPanel.addPropertyChangeListener(l); - generalPanel.addPropertyChangeListener(l); + /* + * There is at least one look and feel library that follows the bad + * practice of calling overrideable methods in a constructor, e.g.: + * + * at + * javax.swing.plaf.synth.SynthPanelUI.installListeners(SynthPanelUI.java:83) + * at + * javax.swing.plaf.synth.SynthPanelUI.installUI(SynthPanelUI.java:63) + * at javax.swing.JComponent.setUI(JComponent.java:666) at + * javax.swing.JPanel.setUI(JPanel.java:153) at + * javax.swing.JPanel.updateUI(JPanel.java:126) at + * javax.swing.JPanel.(JPanel.java:86) at + * javax.swing.JPanel.(JPanel.java:109) at + * javax.swing.JPanel.(JPanel.java:117) + * + * When this happens, the following child components of this JPanel + * subclass have not been constructed yet, since this panel's + * constructor has not been called yet. + */ + if (null != listsPanel) { + listsPanel.addPropertyChangeListener(l); + } + if (null != languagesPanel) { + languagesPanel.addPropertyChangeListener(l); + } + if (null != generalPanel) { + generalPanel.addPropertyChangeListener(l); + } } @Override diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 2922cd2054..0de39782ca 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Thu, 07 Sep 2017 13:53:53 -0400 +#Wed, 08 Nov 2017 17:45:11 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 4.4.2 +currentVersion=Autopsy 4.5.0 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 2ac51b0cbd..fa55dddb62 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Thu, 07 Sep 2017 13:53:53 -0400 -CTL_MainWindow_Title=Autopsy 4.4.2 -CTL_MainWindow_Title_No_Project=Autopsy 4.4.2 +#Wed, 08 Nov 2017 17:45:11 -0500 +CTL_MainWindow_Title=Autopsy 4.5.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.5.0