From 52fcedb5ea017c4fb740d3b5a226054d0bdeebea Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 7 Jan 2021 14:11:23 -0500 Subject: [PATCH] 7084 bug fixes for feedback --- .../discovery/search/DomainSearch.java | 2 +- .../discovery/ui/ArtifactsListPanel.java | 56 +++++++++++++++-- .../autopsy/discovery/ui/ArtifactsWorker.java | 6 +- .../discovery/ui/Bundle.properties-MERGED | 1 + .../discovery/ui/DiscoveryTopComponent.java | 60 +++++++++++++------ .../discovery/ui/DomainDetailsPanel.java | 14 +++++ .../discovery/ui/DomainSummaryViewer.form | 1 + .../discovery/ui/DomainSummaryViewer.java | 1 + .../discovery/ui/MiniTimelinePanel.java | 2 +- .../discovery/ui/MiniTimelineWorker.java | 4 +- 10 files changed, 119 insertions(+), 28 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java index 0ceda46203..61efc8cfbe 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java @@ -201,7 +201,7 @@ public class DomainSearch { * @throws DiscoveryException if unable to get the artifacts or the date * attributes from an artifact. */ - public List getAllArtifactsForDomain(SleuthkitCase sleuthkitCase, String domain) throws DiscoveryException, InterruptedException { + public List getAllArtifactsForDomain(SleuthkitCase sleuthkitCase, String domain) throws DiscoveryException { List artifacts = new ArrayList<>(); Map> dateMap = new HashMap<>(); if (!StringUtils.isBlank(domain)) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java index 38c0431793..08f84afddc 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java @@ -94,11 +94,15 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { @Override BlackboardArtifact getSelectedArtifact() { - int selectedIndex = artifactsTable.getSelectionModel().getLeadSelectionIndex(); - if (selectedIndex < artifactsTable.getSelectionModel().getMinSelectionIndex() || artifactsTable.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > artifactsTable.getSelectionModel().getMaxSelectionIndex()) { + if (artifactsTable.getModel() instanceof DomainArtifactTableModel) { + int selectedIndex = artifactsTable.getSelectionModel().getLeadSelectionIndex(); + if (selectedIndex < artifactsTable.getSelectionModel().getMinSelectionIndex() || artifactsTable.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > artifactsTable.getSelectionModel().getMaxSelectionIndex()) { + return null; + } + return tableModel.getArtifactByRow(artifactsTable.convertRowIndexToModel(selectedIndex)); + } else { return null; } - return tableModel.getArtifactByRow(artifactsTable.convertRowIndexToModel(selectedIndex)); } @Override @@ -124,7 +128,12 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void addArtifacts(List artifactList) { - tableModel.setContents(artifactList); + if (!artifactList.isEmpty()) { + artifactsTable.setModel(tableModel); + tableModel.setContents(artifactList); + } else { + artifactsTable.setModel(new EmptyTableModel()); + } artifactsTable.validate(); artifactsTable.repaint(); tableModel.fireTableDataChanged(); @@ -359,6 +368,45 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { } } } + + /** + * Table model which displays only that no results were found. + */ + private class EmptyTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + @Override + public int getRowCount() { + return 1; + } + + @Override + public int getColumnCount() { + return 1; + } + + @NbBundle.Messages({"ArtifactsListPanel.noResultsFound.text=No results found"}) + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return Bundle.ArtifactsListPanel_value_noValue(); + } + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return Bundle.ArtifactsListPanel_dateColumn_name(); + case 1: + return Bundle.ArtifactsListPanel_titleColumn_name(); + case 2: + return Bundle.ArtifactsListPanel_mimeTypeColumn_name(); + default: + return ""; + } + } + + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTable artifactsTable; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java index fa9a6c8149..faf23e8b04 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java @@ -61,7 +61,7 @@ class ArtifactsWorker extends SwingWorker, Void> { return domainSearch.getArtifacts(new DomainSearchArtifactsRequest(Case.getCurrentCase().getSleuthkitCase(), domain, artifactType)); } catch (DiscoveryException ex) { if (ex.getCause() instanceof InterruptedException) { - logger.log(Level.INFO, "MiniTimeline search was cancelled or interrupted for domain: {0}", domain); + //ignore the exception as it was cancelled while the cache was performing its get and we support cancellation } else { throw ex; } @@ -73,7 +73,7 @@ class ArtifactsWorker extends SwingWorker, Void> { @Override protected void done() { List listOfArtifacts = new ArrayList<>(); - if (!isCancelled() && isDone()) { + if (!isCancelled()) { try { listOfArtifacts.addAll(get()); DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.ArtifactSearchResultEvent(artifactType, listOfArtifacts)); @@ -84,6 +84,6 @@ class ArtifactsWorker extends SwingWorker, Void> { //Worker was cancelled after previously finishing its background work, exception ignored to cut down on non-helpful logging } } - + } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED index a1a89ad19b..ffa7f36036 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED @@ -3,6 +3,7 @@ ArtifactMenuMouseAdapter_label=Extract Files ArtifactsListPanel.dateColumn.name=Date/Time ArtifactsListPanel.fileNameColumn.name=Name ArtifactsListPanel.mimeTypeColumn.name=MIME Type +ArtifactsListPanel.noResultsFound.text=No results found ArtifactsListPanel.termColumn.name=Term ArtifactsListPanel.titleColumn.name=Title ArtifactsListPanel.urlColumn.name=URL diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java index 773e09f26b..30f7e66a34 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java @@ -40,6 +40,7 @@ import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; +import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils.PopulateDomainTabsEvent; import org.sleuthkit.autopsy.discovery.search.SearchData.Type; import static org.sleuthkit.autopsy.discovery.search.SearchData.Type.DOMAIN; import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter; @@ -60,9 +61,11 @@ public final class DiscoveryTopComponent extends TopComponent { private volatile static int previousDividerLocation = 250; private final GroupListPanel groupListPanel; private final ResultsPanel resultsPanel; + private JPanel detailsPanel = new JPanel(); private String selectedDomainTabName; private Type searchType; private int dividerLocation = JSplitPane.UNDEFINED_CONDITION; + private SwingAnimator animator = null; /** @@ -88,19 +91,19 @@ public final class DiscoveryTopComponent extends TopComponent { rightSplitPane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equalsIgnoreCase(JSplitPane.DIVIDER_LOCATION_PROPERTY)) { + if (evt.getPropertyName().equalsIgnoreCase(JSplitPane.DIVIDER_LOCATION_PROPERTY) + && ((animator == null || !animator.isRunning()) + && evt.getNewValue() instanceof Integer + && evt.getOldValue() instanceof Integer + && ((int) evt.getNewValue() + 5) < (rightSplitPane.getHeight() - rightSplitPane.getDividerSize()) + && (JSplitPane.UNDEFINED_CONDITION != (int) evt.getNewValue()) + && ((int) evt.getOldValue() != JSplitPane.UNDEFINED_CONDITION))) { //Only change the saved location when it was a manual change by the user and not the animation or the window opening initially - if ((animator == null || !animator.isRunning()) - && evt.getNewValue() instanceof Integer - && evt.getOldValue() instanceof Integer - && ((int) evt.getNewValue() + 5) < (rightSplitPane.getHeight() - rightSplitPane.getDividerSize()) - && (JSplitPane.UNDEFINED_CONDITION != (int) evt.getNewValue()) - && ((int) evt.getOldValue() != JSplitPane.UNDEFINED_CONDITION)) { - previousDividerLocation = (int) evt.getNewValue(); - } + previousDividerLocation = (int) evt.getNewValue(); } } - }); + } + ); } @@ -132,7 +135,6 @@ public final class DiscoveryTopComponent extends TopComponent { */ public static DiscoveryTopComponent getTopComponent() { DiscoveryTopComponent discoveryTopComp = (DiscoveryTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); - discoveryTopComp.resetBottomComponent(); return discoveryTopComp; } @@ -169,9 +171,9 @@ public final class DiscoveryTopComponent extends TopComponent { DiscoveryEventUtils.getDiscoveryEventBus().unregister(this); DiscoveryEventUtils.getDiscoveryEventBus().unregister(groupListPanel); DiscoveryEventUtils.getDiscoveryEventBus().unregister(resultsPanel); - DiscoveryEventUtils.getDiscoveryEventBus().unregister(rightSplitPane.getBottomComponent()); - if (rightSplitPane.getBottomComponent() instanceof DomainDetailsPanel) { - selectedDomainTabName = ((DomainDetailsPanel) rightSplitPane.getBottomComponent()).getSelectedTabName(); + DiscoveryEventUtils.getDiscoveryEventBus().unregister(detailsPanel); + if (detailsPanel instanceof DomainDetailsPanel) { + selectedDomainTabName = ((DomainDetailsPanel) detailsPanel).getSelectedTabName(); } resetBottomComponent(); super.componentClosed(); @@ -274,6 +276,25 @@ public final class DiscoveryTopComponent extends TopComponent { .collect(Collectors.toList()); } + /** + * Respond to the PopulateDomainTabsEvent to ensure the bottom area only + * shows when it has data + * + * @param populateDomainTabsEvent The event which indicates the domain tabs + * contents should change. + */ + @Subscribe + private void handlePopulateDomainTabsEvent(PopulateDomainTabsEvent populateDomainTabsEvent) { + if (detailsPanel instanceof DomainDetailsPanel) { + SwingUtilities.invokeLater(() -> { + if (((DomainDetailsPanel) detailsPanel).getCurrentTabStatus() == DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATED + || ((DomainDetailsPanel) detailsPanel).getCurrentTabStatus() == DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATING) { + rightSplitPane.setBottomComponent(detailsPanel); + } + }); + } + } + /** * Subscribe to the DetailsVisible event and animate the panel as it changes * visibility. @@ -311,6 +332,7 @@ public final class DiscoveryTopComponent extends TopComponent { @Subscribe void handleSearchStartedEvent(DiscoveryEventUtils.SearchStartedEvent searchStartedEvent) { SwingUtilities.invokeLater(() -> { + rightSplitPane.setBottomComponent(new JPanel()); newSearchButton.setText(Bundle.DiscoveryTopComponent_cancelButton_text()); progressMessageTextArea.setForeground(Color.red); searchType = searchStartedEvent.getType(); @@ -343,12 +365,16 @@ public final class DiscoveryTopComponent extends TopComponent { } selectedDomainTabName = validateLastSelectedType(searchCompleteEvent); DomainDetailsPanel domainDetailsPanel = new DomainDetailsPanel(); - rightSplitPane.setBottomComponent(domainDetailsPanel); domainDetailsPanel.configureArtifactTabs(selectedDomainTabName); + detailsPanel = domainDetailsPanel; } else { - rightSplitPane.setBottomComponent(new FileDetailsPanel()); + FileDetailsPanel fileDetailsPanel = new FileDetailsPanel(); + DiscoveryEventUtils.getDiscoveryEventBus().register(fileDetailsPanel); + detailsPanel = new FileDetailsPanel(); + rightSplitPane.setBottomComponent(detailsPanel); } - DiscoveryEventUtils.getDiscoveryEventBus().register(rightSplitPane.getBottomComponent()); + + DiscoveryEventUtils.getDiscoveryEventBus().register(detailsPanel); descriptionText += searchCompleteEvent.getFilters().stream().map(AbstractFilter::getDesc).collect(Collectors.joining("; ")); progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchComplete_text(descriptionText)); progressMessageTextArea.setCaretPosition(0); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java index 4f1e10a76c..63f6b0921b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java @@ -104,6 +104,20 @@ final class DomainDetailsPanel extends JPanel { } } + /** + * Get the status of the currently selected tab. + * + * @return The loading status of the currently selected tab. + */ + DomainArtifactsTabPanel.ArtifactRetrievalStatus getCurrentTabStatus() { + if (jTabbedPane1.getSelectedComponent() instanceof MiniTimelinePanel) { + return ((MiniTimelinePanel) jTabbedPane1.getSelectedComponent()).getStatus(); + } else if (jTabbedPane1.getSelectedComponent() instanceof DomainArtifactsTabPanel) { + return ((DomainArtifactsTabPanel) jTabbedPane1.getSelectedComponent()).getStatus(); + } + return null; + } + /** * Run the worker which retrieves the list of artifacts for the domain to * populate the details area. diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form index 9f1e3516b9..ee52e40121 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form @@ -33,6 +33,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java index 66125e36d0..ef3c7d6ad6 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java @@ -64,6 +64,7 @@ public class DomainSummaryViewer extends javax.swing.JPanel { setLayout(new java.awt.BorderLayout()); domainList.setModel(domainListModel); + domainList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); domainList.setCellRenderer(new DomainSummaryPanel()); domainScrollPane.setViewportView(domainList); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java index c976e126ae..28b4858703 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java @@ -102,7 +102,7 @@ final class MiniTimelinePanel extends javax.swing.JPanel { DomainArtifactsTabPanel.ArtifactRetrievalStatus getStatus() { return status; } - + /** * Manually set the status of the panel. * diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java index d3ca601db4..b83c5b0b1a 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java @@ -60,7 +60,7 @@ class MiniTimelineWorker extends SwingWorker, Void> { } catch (DiscoveryException ex) { if (ex.getCause() instanceof InterruptedException) { - logger.log(Level.INFO, "MiniTimeline search was cancelled or interrupted for domain: {0}", domain); + //ignore the exception as it was cancelled while the cache was performing its get and we support cancellation } else { throw ex; } @@ -72,7 +72,7 @@ class MiniTimelineWorker extends SwingWorker, Void> { @Override protected void done() { List results = new ArrayList<>(); - if (!isCancelled() && isDone()) { + if (!isCancelled()) { try { results.addAll(get()); DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.MiniTimelineResultEvent(results));