diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java index 5fe65783b0..5161a544ca 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java @@ -26,8 +26,10 @@ import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JOptionPane; +import javax.swing.SwingWorker; import javax.swing.event.ListSelectionEvent; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; @@ -51,12 +53,13 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE); private static final int EXTRA_ROW_HEIGHT = 4; - private List ingestJobs; + private final List ingestJobs = new ArrayList<>(); private final List ingestJobsForSelectedDataSource = new ArrayList<>(); private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel(); private IngestModuleTableModel ingestModuleTableModel = new IngestModuleTableModel(null); private final DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); private DataSource selectedDataSource; + private static SwingWorker refreshWorker = null; /** * Creates new form IngestJobInfoPanel @@ -76,19 +79,19 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { this.ingestModuleTable.setModel(this.ingestModuleTableModel); }); - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST , (PropertyChangeEvent evt) -> { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.STARTED.toString()) || evt.getPropertyName().equals(IngestManager.IngestJobEvent.CANCELLED.toString()) || evt.getPropertyName().equals(IngestManager.IngestJobEvent.COMPLETED.toString())) { refresh(); } }); - + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> { if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) { return; } - + // Check whether we have a case open or case close event. if ((CURRENT_CASE == Case.Events.valueOf(evt.getPropertyName()))) { if (evt.getNewValue() != null) { @@ -102,7 +105,7 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { }); ingestJobTable.setRowHeight(ingestJobTable.getRowHeight() + EXTRA_ROW_HEIGHT); ingestModuleTable.setRowHeight(ingestModuleTable.getRowHeight() + EXTRA_ROW_HEIGHT); - + } /** @@ -133,27 +136,51 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { * Get the updated complete list of ingest jobs. */ private void refresh() { - try { - if (Case.isCaseOpen()) { // Note - this will generally return true when handling a case close event - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - this.ingestJobs = skCase.getIngestJobs(); - setDataSource(selectedDataSource); - } else { - this.ingestJobs = new ArrayList<>(); - setDataSource(null); - } - - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex); - JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); + if (refreshWorker != null && !refreshWorker.isDone()) { + refreshWorker.cancel(true); } + refreshWorker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws Exception { + ingestJobs.clear(); + try { + if (Case.isCaseOpen()) { // Note - this will generally return true when handling a case close event + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + ingestJobs.addAll(skCase.getIngestJobs()); + setDataSource(selectedDataSource); + } else { + setDataSource(null); + } + return true; + } catch (TskCoreException | NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex); + return false; + } + } + + @Override + protected void done() { + try { + if (!get()) { + JOptionPane.showMessageDialog(IngestJobInfoPanel.this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); + } + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.WARNING, "Error getting results from Ingest Job Info Panel's refresh worker", ex); + } + } + }; + refreshWorker.execute(); } - + /** * Reset the panel. */ private void reset() { - this.ingestJobs = new ArrayList<>(); + if (refreshWorker != null) { + refreshWorker.cancel(true); + } + this.ingestJobs.clear(); setDataSource(null); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.java index 21d7deb8bd..f2b99df0dd 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -119,9 +119,9 @@ final class CallLogViewer extends javax.swing.JPanel implements RelationshipsVie updateOutlineViewPanel(); } }); - + TableColumn column = outline.getColumnModel().getColumn(2); - column.setCellRenderer(new NodeTableCellRenderer() ); + column.setCellRenderer(new NodeTableCellRenderer()); } @@ -164,7 +164,9 @@ final class CallLogViewer extends javax.swing.JPanel implements RelationshipsVie @Override public void setSelectionInfo(SelectionInfo info) { + callLogDataViewer.setNode(null); nodeFactory.refresh(info); + } @Override @@ -229,7 +231,7 @@ final class CallLogViewer extends javax.swing.JPanel implements RelationshipsVie } } - + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JScrollPane bottomScrollPane; private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel; diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java index 28666c8fdd..ab980ee101 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java @@ -35,7 +35,8 @@ import org.sleuthkit.datamodel.TskCoreException; /** * ChildFactory for ContactNodes. */ -final class ContactsChildNodeFactory extends ChildFactory{ +final class ContactsChildNodeFactory extends ChildFactory { + private static final Logger logger = Logger.getLogger(ContactsChildNodeFactory.class.getName()); private SelectionInfo selectionInfo; @@ -47,12 +48,13 @@ final class ContactsChildNodeFactory extends ChildFactory{ * accounts */ ContactsChildNodeFactory(SelectionInfo selectionInfo) { - this.selectionInfo = selectionInfo; + this.selectionInfo = selectionInfo; } - + /** - * Updates the current instance of selectionInfo and calls the refresh method. - * + * Updates the current instance of selectionInfo and calls the refresh + * method. + * * @param selectionInfo New instance of the currently selected accounts */ public void refresh(SelectionInfo selectionInfo) { @@ -63,13 +65,15 @@ final class ContactsChildNodeFactory extends ChildFactory{ /** * Creates a list of Keys (BlackboardArtifact) for only contacts of the * currently selected accounts + * * @param list List of BlackboardArtifact to populate + * * @return True on success */ @Override protected boolean createKeys(List list) { - - if(selectionInfo == null) { + + if (selectionInfo == null) { return true; } @@ -80,7 +84,7 @@ final class ContactsChildNodeFactory extends ChildFactory{ logger.log(Level.SEVERE, "Failed to load relationship sources.", ex); //NON-NLS return false; } - + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { BlackboardArtifact bba = (BlackboardArtifact) content; diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java index 1d11b3ef6d..6366d4e517 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,6 @@ import org.openide.nodes.NodeAdapter; import org.openide.nodes.NodeMemberEvent; import org.openide.util.Lookup; import org.openide.util.NbBundle; -import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; @@ -126,6 +125,7 @@ final class ContactsViewer extends JPanel implements RelationshipsViewer { @Override public void setSelectionInfo(SelectionInfo info) { + contactPane.setNode(null); nodeFactory.refresh(info); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java index daffdafb81..63988b200d 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -99,18 +99,19 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM public void setSelectionInfo(SelectionInfo info) { Set relationshipSources; Set artifactList = new HashSet<>(); + contentViewer.setNode(null); + if (info != null) { + try { + relationshipSources = info.getRelationshipSources(); - try { - relationshipSources = info.getRelationshipSources(); + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { + artifactList.add((BlackboardArtifact) content); + }); - relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { - artifactList.add((BlackboardArtifact) content); - }); - - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to update selection.", ex); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to update selection.", ex); + } } - thumbnailViewer.resetComponent(); thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode(new AttachmentThumbnailsChildren(artifactList)), tableEM), true, this.getClass().getName())); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java index 229a0067de..8910472295 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.communications.relationships; -import java.awt.Component; import javax.swing.JPanel; import org.openide.util.Lookup; import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; @@ -37,15 +36,15 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider */ public RelationshipBrowser() { initComponents(); - + MessageViewer messagesViewer = new MessageViewer(); ContactsViewer contactsViewer = new ContactsViewer(); SummaryViewer summaryViewer = new SummaryViewer(); MediaViewer mediaViewer = new MediaViewer(); CallLogViewer callLogViewer = new CallLogViewer(); - + proxyLookup = new ModifiableProxyLookup(messagesViewer.getLookup()); - + tabPane.add(summaryViewer.getDisplayName(), summaryViewer); tabPane.add(messagesViewer.getDisplayName(), messagesViewer); tabPane.add(callLogViewer.getDisplayName(), callLogViewer); @@ -95,13 +94,11 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider }// //GEN-END:initComponents private void tabPaneStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabPaneStateChanged - if(currentSelection != null) { - ((RelationshipsViewer) tabPane.getSelectedComponent()).setSelectionInfo(currentSelection); - } - - Component selectedComponent = tabPane.getSelectedComponent(); - if(selectedComponent instanceof Lookup.Provider) { - Lookup lookup = ((Lookup.Provider)selectedComponent).getLookup(); + RelationshipsViewer viewer = ((RelationshipsViewer) tabPane.getSelectedComponent()); + //clear old values + viewer.setSelectionInfo(currentSelection); + if (viewer instanceof Lookup.Provider) { + Lookup lookup = viewer.getLookup(); proxyLookup.setNewLookups(lookup); } }//GEN-LAST:event_tabPaneStateChanged diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java index 553c139c55..0830e8e6ef 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java @@ -97,30 +97,28 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac public void setArtifact(BlackboardArtifact artifact) { resetComponent(); - if (artifact == null) { - return; - } - CallLogViewData callLogViewData = null; try { callLogViewData = getCallLogViewData(artifact); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Error getting attributes for Calllog artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); } - + List personaSearchDataList = new ArrayList<>(); // update the view with the call log data if (callLogViewData != null) { - List personaSearchDataList = updateView(callLogViewData); - if (!personaSearchDataList.isEmpty()) { - currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this); - currentAccountFetcher.execute(); - } else { - currentAccountFetcher = null; - } + personaSearchDataList.addAll(updateView(callLogViewData)); + + } + if (!personaSearchDataList.isEmpty()) { + currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this); + currentAccountFetcher.execute(); + } else { + currentAccountFetcher = null; } // repaint this.revalidate(); + this.repaint(); } /** @@ -320,7 +318,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac // Display "From" if we have non-local device accounts if (callLogViewData.getFromAccount() != null) { CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_from()); - + // check if this is local account String accountDisplayString = getAccountDisplayString(callLogViewData.getFromAccount(), callLogViewData); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString); @@ -371,8 +369,6 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac this.setLayout(m_gridBagLayout); this.revalidate(); - this.repaint(); - return dataList; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java index b9ebee5c67..2b583ff000 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java @@ -130,19 +130,15 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac // Reset the panel. resetComponent(); - if (artifact == null) { - return; + if (artifact != null) { + try { + extractArtifactData(artifact); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting attributes for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + return; + } + updateView(); } - - try { - extractArtifactData(artifact); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error getting attributes for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); - return; - } - - updateView(); - this.setLayout(this.m_gridBagLayout); this.revalidate(); this.repaint(); @@ -164,6 +160,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * Extracts data from the artifact to be displayed in the panel. * * @param artifact Artifact to show. + * * @throws TskCoreException */ private void extractArtifactData(BlackboardArtifact artifact) throws TskCoreException { @@ -235,7 +232,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac /** * Updates the contact image in the view. * - * @param contactPanelLayout Panel layout. + * @param contactPanelLayout Panel layout. * @param contactPanelConstraints Layout constraints. * */ @@ -267,7 +264,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac /** * Updates the contact name in the view. * - * @param contactPanelLayout Panel layout. + * @param contactPanelLayout Panel layout. * @param contactPanelConstraints Layout constraints. * */ @@ -294,9 +291,9 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * Updates the view by displaying the given list of attributes in the given * section panel. * - * @param sectionAttributesList List of attributes to display. - * @param sectionHeader Section name label. - * @param contactPanelLayout Panel layout. + * @param sectionAttributesList List of attributes to display. + * @param sectionHeader Section name label. + * @param contactPanelLayout Panel layout. * @param contactPanelConstraints Layout constraints. * */ @@ -418,12 +415,12 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac /** * Displays the given persona in the persona panel. * - * @param persona Persona to display. - * @param matchNumber Number of matches. + * @param persona Persona to display. + * @param matchNumber Number of matches. * @param missingAccountsList List of contact accounts this persona may be - * missing. - * @param gridBagLayout Layout to use. - * @param constraints layout constraints. + * missing. + * @param gridBagLayout Layout to use. + * @param constraints layout constraints. * * @throws CentralRepoException */ @@ -567,7 +564,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * @param artifact * * @return Image from a TSK_CONTACT artifact or default image if none was - * found or the artifact is not a TSK_CONTACT + * found or the artifact is not a TSK_CONTACT */ private ImageIcon getImageFromArtifact(BlackboardArtifact artifact) { ImageIcon imageIcon = defaultImage; @@ -617,7 +614,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * Creates a persona searcher task. * * @param accountAttributesList List of attributes that may map to - * accounts. + * accounts. */ ContactPersonaSearcherTask(BlackboardArtifact artifact) { this.artifact = artifact; @@ -647,7 +644,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac } } } - + Collection personaAccounts = PersonaAccount.getPersonaAccountsForAccount(account); if (personaAccounts != null && !personaAccounts.isEmpty()) { // get personas for the account @@ -716,7 +713,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac /** * Constructor. * - * @param personaNameLabel Persona name label. + * @param personaNameLabel Persona name label. * @param personaActionButton Persona action button. */ PersonaUIComponents(JLabel personaNameLabel, JButton personaActionButton) { @@ -784,8 +781,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac for (CentralRepoAccount account : contactUniqueAccountsList) { personaPanel.addAccount(account, Bundle.ContactArtifactViewer_persona_account_justification(), Persona.Confidence.HIGH); } - - if(contactName != null && contactUniqueAccountsList.isEmpty()) { + + if (contactName != null && contactUniqueAccountsList.isEmpty()) { createPersonaDialog.setStartupPopupMessage(Bundle.ContactArtifactViewer_id_not_found_in_cr(contactName)); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java index 796be557fd..cd0b03fd98 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -277,6 +278,8 @@ public class Artifacts { */ private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); private final Category category; + + private final PropertyChangeListener weakPcl; /** * Main constructor. @@ -290,45 +293,50 @@ public class Artifacts { super(); this.filteringDSObjId = filteringDSObjId; this.category = category; - } - - private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { - String eventType = evt.getPropertyName(); - if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { - // case was closed. Remove listeners so that we don't get called with a stale case handle - if (evt.getNewValue() == null) { - removeNotify(); - } - } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { - /** - * This is a stop gap measure until a different way of handling - * the closing of cases is worked out. Currently, remote events - * may be received for a case that is already closed. - */ - try { - Case.getCurrentCaseThrows(); - refresh(false); - } catch (NoCurrentCaseException notUsed) { + + PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { /** - * Case is closed, do nothing. + * This is a stop gap measure until a different way of + * handling the closing of cases is worked out. Currently, + * remote events may be received for a case that is already + * closed. */ + try { + Case.getCurrentCaseThrows(); + refresh(false); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } } - } - }; + }; + weakPcl = WeakListeners.propertyChange(pcl, null); + } + @Override - protected void addNotify() { + protected void addNotify() { + super.addNotify(); refreshThrottler.registerForIngestModuleEvents(); - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); } @Override - protected void removeNotify() { + protected void finalize() throws Throwable { + super.finalize(); refreshThrottler.unregisterEventListener(); - IngestManager.getInstance().removeIngestJobEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); typeNodeMap.clear(); } @@ -624,17 +632,21 @@ public class Artifacts { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Override protected void onAdd() { refreshThrottler.registerForIngestModuleEvents(); - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); } @Override protected void onRemove() { - refreshThrottler.unregisterEventListener(); - IngestManager.getInstance().removeIngestJobEventListener(pcl); + if(refreshThrottler != null) { + refreshThrottler.unregisterEventListener(); + } + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java index 282ed83418..8321b8816a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java @@ -1,6 +1,5 @@ /* * Autopsy Forensic Browser - * * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * @@ -20,7 +19,6 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; @@ -32,6 +30,7 @@ import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; +import org.openide.util.WeakListeners; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -83,16 +82,18 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable extends ChildFactory.D isPageSizeChangeEvent = false; this.filter = filter; } - + @Override protected void addNotify() { onAdd(); } @Override - protected void removeNotify() { + protected void finalize() throws Throwable { + super.finalize(); onRemove(); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 4546290007..e735eca3d9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -31,6 +31,7 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -76,15 +77,18 @@ public class DataSourcesNode extends DisplayableItemNode { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Override protected void addNotify() { - Case.addEventTypeSubscriber(UPDATE_EVTS, pcl); + Case.addEventTypeSubscriber(UPDATE_EVTS, weakPcl); } @Override - protected void removeNotify() { - Case.removeEventTypeSubscriber(UPDATE_EVTS, pcl); + protected void finalize() throws Throwable{ + Case.removeEventTypeSubscriber(UPDATE_EVTS, weakPcl); + super.finalize(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 0acd6a8bf3..9f8d1ee519 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2020 Basis Technology Corp. + * Copyright 2012-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -313,21 +314,24 @@ public class EmailExtracted implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); emailResults.update(); emailResults.addObserver(this); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + protected void finalize() throws Throwable{ + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); emailResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index e98b154ff0..970e546f98 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,6 +39,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -278,21 +279,24 @@ public class HashsetHits implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); hashsetResults.update(); hashsetResults.addObserver(this); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); hashsetResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java index 1f349fdfc4..b05bca24a1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java @@ -66,7 +66,7 @@ public class HostNode extends DisplayableItemNode { private final Host host; private final Function dataSourceToNode; - + /** * Main constructor. * @@ -77,7 +77,7 @@ public class HostNode extends DisplayableItemNode { this.host = host; this.dataSourceToNode = dataSourceToNode; } - + /** * Listener for handling DATA_SOURCE_ADDED / HOST_DELETED events. * A host may have been deleted as part of a merge, which means its data sources could @@ -93,15 +93,19 @@ public class HostNode extends DisplayableItemNode { } } }; - + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(dataSourceAddedPcl, null); + @Override protected void addNotify() { - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), dataSourceAddedPcl); + super.addNotify(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), weakPcl); } @Override - protected void removeNotify() { - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), dataSourceAddedPcl); + protected void finalize() throws Throwable { + super.finalize(); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), weakPcl); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 57f2524ff9..f636264082 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,6 +39,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -271,21 +272,24 @@ public class InterestingHits implements AutopsyVisitableItem { } } }; - + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); interestingResults.update(); interestingResults.addObserver(this); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); interestingResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index 7a9683b89f..4406944fa3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -42,6 +42,7 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -437,7 +438,8 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - protected void removeNotify() { + protected void finalize() throws Throwable { + super.finalize(); keywordResults.deleteObserver(this); } @@ -504,22 +506,24 @@ public class KeywordHits implements AutopsyVisitableItem { } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); keywordResults.update(); super.addNotify(); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); - super.removeNotify(); + protected void finalize() throws Throwable{ + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.finalize(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java index 65875ef0ed..75a6eb971b 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java @@ -137,19 +137,22 @@ public final class OsAccounts implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(listener, null); + @Override + protected void finalize() throws Throwable { + super.finalize(); + Case.removeEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNTS_ADDED), weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + } + @Override protected void addNotify() { Case.addEventTypeSubscriber(EnumSet.of(Case.Events.OS_ACCOUNTS_ADDED, Case.Events.OS_ACCOUNTS_DELETED), listener); Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), listener); } - @Override - protected void removeNotify() { - Case.removeEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNTS_ADDED), listener); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), listener); - } - @Override protected boolean createKeys(List list) { if (skCase != null) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java index 06d52b8662..a6cd30b115 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java @@ -92,6 +92,7 @@ public class PersonNode extends DisplayableItemNode { */ PersonChildren(Person person) { this.person = person; + } /** @@ -111,17 +112,20 @@ public class PersonNode extends DisplayableItemNode { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(hostAddedDeletedPcl, null); @Override protected void addNotify() { - Case.addEventTypeSubscriber(HOST_EVENTS_OF_INTEREST, hostAddedDeletedPcl); + Case.addEventTypeSubscriber(HOST_EVENTS_OF_INTEREST, weakPcl); } - + @Override - protected void removeNotify() { - Case.removeEventTypeSubscriber(HOST_EVENTS_OF_INTEREST, hostAddedDeletedPcl); + protected void finalize() throws Throwable { + super.finalize(); + Case.removeEventTypeSubscriber(HOST_EVENTS_OF_INTEREST, weakPcl); } - + @Override protected HostNode createNodeForKey(HostGrouping key) { return key == null ? null : new HostNode(key); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 6fa6487b5e..8154d5a884 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -213,6 +214,8 @@ public class Tags implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); /** * Constructor @@ -221,21 +224,21 @@ public class Tags implements AutopsyVisitableItem { */ TagNameNodeFactory(long objId) { this.filteringDSObjId = objId; - } - + @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); tagResults.update(); tagResults.addObserver(this); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); tagResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 901ab4aeb8..bf7ee1caba 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,6 +57,7 @@ import org.openide.nodes.NodeOp; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.Utilities; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -218,8 +219,8 @@ final public class Accounts implements AutopsyVisitableItem { abstract void handleDataAdded(ModuleDataEvent event); @Override - protected void removeNotify() { - super.removeNotify(); + protected void finalize() throws Throwable { + super.finalize(); reviewStatusBus.unregister(ObservingChildren.this); } @@ -415,6 +416,8 @@ final public class Accounts implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Subscribe @Override @@ -473,18 +476,18 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); - super.removeNotify(); + protected void finalize() throws Throwable { + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.finalize(); } @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); super.addNotify(); refresh(true); } @@ -550,21 +553,22 @@ final public class Accounts implements AutopsyVisitableItem { } } }; - + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); - super.addNotify(); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); - super.removeNotify(); + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); } @Override @@ -727,6 +731,9 @@ final public class Accounts implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + @Subscribe @Override @@ -739,20 +746,21 @@ final public class Accounts implements AutopsyVisitableItem { void handleDataAdded(ModuleDataEvent event) { refresh(true); } - + @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); super.addNotify(); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); super.removeNotify(); } @@ -881,21 +889,23 @@ final public class Accounts implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); super.addNotify(); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); - super.removeNotify(); + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); } @Subscribe @@ -1095,21 +1105,23 @@ final public class Accounts implements AutopsyVisitableItem { } } }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); @Override protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); super.addNotify(); } @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); - super.removeNotify(); + protected void finalize() throws Throwable{ + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); } @Subscribe diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index c8f5848243..323b340c7f 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -31,6 +31,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; +import org.apache.commons.lang3.StringUtils; import org.openide.nodes.AbstractNode; import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.TreeView; @@ -84,7 +85,7 @@ public class ViewContextAction extends AbstractAction { * parent of the content, selecting the parent in the tree view, then * selecting the content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param artifactNode The artifact node for the artifact. */ public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) { @@ -106,9 +107,9 @@ public class ViewContextAction extends AbstractAction { * parent of the content, selecting the parent in the tree view, then * selecting the content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param fileSystemContentNode The file system content node for the - * content. + * content. */ public ViewContextAction(String displayName, AbstractFsContentNode fileSystemContentNode) { super(displayName); @@ -121,9 +122,9 @@ public class ViewContextAction extends AbstractAction { * content, selecting the parent in the tree view, then selecting the * content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param abstractAbstractFileNode The AbstractAbstractFileNode node for the - * content. + * content. */ public ViewContextAction(String displayName, AbstractAbstractFileNode abstractAbstractFileNode) { super(displayName); @@ -137,7 +138,7 @@ public class ViewContextAction extends AbstractAction { * content in the results view. * * @param displayName The display name for the action. - * @param content The content. + * @param content The content. */ public ViewContextAction(String displayName, Content content) { super(displayName); @@ -149,7 +150,7 @@ public class ViewContextAction extends AbstractAction { * branch of the tree view to the level of the parent of the content, * selecting the parent in the tree view, then selecting the content in the * results view. - * + * * NOTE: This code will likely need updating in the event that the structure * of the nodes is changed (i.e. adding parent levels). Places to look when * changing node structure include: @@ -168,176 +169,224 @@ public class ViewContextAction extends AbstractAction { public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { - /* - * Get the parent content for the content to be selected in the - * results view. If the parent content is null, then the specified - * content is a data source, and the parent tree view node is the - * "Data Sources" node. Otherwise, the tree view needs to be - * searched to find the parent treeview node. - */ - Content parentContent = null; - try { - parentContent = content.getParent(); - } catch (TskCoreException ex) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindDirectory()); - logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS - return; - } - - if ((parentContent != null) - && (parentContent instanceof UnsupportedContent)) { + Content parentContent = getParentContent(this.content); + + if ((parentContent != null) && (parentContent instanceof UnsupportedContent)) { MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_unsupportedParent()); logger.log(Level.WARNING, String.format("Could not navigate to unsupported content with id: %d", parentContent.getId())); //NON-NLS return; } - /* - * Get the "Data Sources" node from the tree view. - */ + // Get the "Data Sources" node from the tree view. DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); + Node parentTreeViewNode = null; - if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { // 'Group by Data Source' view - - SleuthkitCase skCase; - String dsname; - try { - // get the objid/name of the datasource of the selected content. - skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - long contentDSObjid = content.getDataSource().getId(); - DataSource datasource = skCase.getDataSource(contentDSObjid); - dsname = datasource.getName(); - Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); - - if (null != parentContent) { - // the tree view needs to be searched to find the parent treeview node. - /* NOTE: we can't do a lookup by data source name here, becase if there - are multiple data sources with the same name, then "getChildren().findChild(dsname)" - simply returns the first one that it finds. Instead we have to loop over all - data sources with that name, and make sure we find the correct one. - */ - List dataSourceLevelNodes = Stream.of(rootChildren.getNodes()) - .flatMap(rootNode -> getDataSourceLevelNodes(rootNode).stream()) - .collect(Collectors.toList()); - - for (Node treeNode : dataSourceLevelNodes) { - // in the root, look for a data source node with the name of interest - if (!(treeNode.getName().equals(dsname))) { - continue; - } - - // for this data source, get the "Data Sources" child node - Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourceFilesNode.getNameIdentifier()); - - // check whether this is the data source we are looking for - parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode); - if (parentTreeViewNode != null) { - // found the data source node - break; - } - } - } else { - /* If the parent content is null, then the specified - * content is a data source, and the parent tree view node is the - * "Data Sources" node. */ - Node datasourceGroupingNode = rootChildren.findChild(dsname); - if (!Objects.isNull(datasourceGroupingNode)) { - Children dsChildren = datasourceGroupingNode.getChildren(); - parentTreeViewNode = dsChildren.findChild(DataSourceFilesNode.getNameIdentifier()); - } - } - - if (parentTreeViewNode == null) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); - logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS - return; - } - } catch (NoCurrentCaseException | TskDataException | TskCoreException ex) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); - logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS - return; - } - } else { // Classic view - // Start the search at the DataSourcesNode - Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); - Node rootDsNode = rootChildren == null ? null : rootChildren.findChild(DataSourcesNode.getNameIdentifier()); - if (rootDsNode != null) { - for (Node dataSourceLevelNode : getDataSourceLevelNodes(rootDsNode)) { - DataSource dataSource = dataSourceLevelNode.getLookup().lookup(DataSource.class); - if (dataSource != null) { - // the tree view needs to be searched to find the parent treeview node. - Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, dataSourceLevelNode); - if (potentialParentTreeViewNode != null) { - parentTreeViewNode = potentialParentTreeViewNode; - break; - } - } - } + if (parentContent != null) { + if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { + parentTreeViewNode = getParentNodeGroupedByPersonHost(treeViewExplorerMgr, parentContent); + } else { + parentTreeViewNode = getParentNodeGroupedByDataSource(treeViewExplorerMgr, parentContent); } } - /* - * Set the child selection info of the parent tree node, then select - * the parent node in the tree view. The results view will retrieve - * this selection info and use it to complete this action when the - * tree view top component responds to the selection of the parent - * node by pushing it into the results view top component. - */ - DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal(); - undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content)); - if (content instanceof BlackboardArtifact) { - BlackboardArtifact artifact = ((BlackboardArtifact) content); - long associatedId = artifact.getObjectID(); - try { - Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId); - undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent)); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId()); - } + // if no node is found, report error and do nothing + if (parentTreeViewNode == null) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS + return; } - TreeView treeView = treeViewTopComponent.getTree(); - treeView.expandNode(parentTreeViewNode); - if (treeViewTopComponent.getSelectedNode().equals(parentTreeViewNode)) { - //In the case where our tree view already has the destination directory selected - //due to an optimization in the ExplorerManager.setExploredContextAndSelection method - //the property change we listen for to call DirectoryTreeTopComponent.respondSelection - //will not be sent so we call it manually ourselves after making - //the directory listing the active tab. - treeViewTopComponent.setDirectoryListingActive(); - treeViewTopComponent.respondSelection(treeViewExplorerMgr.getSelectedNodes(), new Node[]{parentTreeViewNode}); - } else { - try { - treeViewExplorerMgr.setExploredContextAndSelection(parentTreeViewNode, new Node[]{parentTreeViewNode}); - } catch (PropertyVetoException ex) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotSelectDirectory()); - logger.log(Level.SEVERE, "Failed to select the parent node in the tree view", ex); //NON-NLS - } - } + setNodeSelection(this.content, parentTreeViewNode, treeViewTopComponent, treeViewExplorerMgr); }); } + /** + * Get the parent content for the content to be selected in the results + * view. If the parent content is null, then the specified content is a data + * source, and the parent tree view node is the "Data Sources" node. + * Otherwise, the tree view needs to be searched to find the parent treeview + * node. + * + * @param content The content whose parent will be returned. If this item is + * a datasource, it will be returned. + * + * @return The content if content is a data source or the parent of this + * content. + */ + private Content getParentContent(Content content) { + + try { + return (content instanceof DataSource) + ? content + : content.getParent(); + } catch (TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindDirectory()); + logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS + return null; + } + } + + /** + * Returns the node in the tree related to the parentContent or null if + * can't be found. This method should be used when view is grouped by data + * source. + * + * @param treeViewExplorerMgr The explorer manager. + * @param parentContent The content whose equivalent node will be + * returned if found. + * + * @return The node if found or null. + */ + private Node getParentNodeGroupedByDataSource(ExplorerManager treeViewExplorerMgr, Content parentContent) { + // Classic view + // Start the search at the DataSourcesNode + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + Node rootDsNode = rootChildren == null ? null : rootChildren.findChild(DataSourcesNode.getNameIdentifier()); + if (rootDsNode != null) { + for (Node dataSourceLevelNode : getDataSourceLevelNodes(rootDsNode)) { + DataSource dataSource = dataSourceLevelNode.getLookup().lookup(DataSource.class); + if (dataSource != null) { + // the tree view needs to be searched to find the parent treeview node. + Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, dataSourceLevelNode); + if (potentialParentTreeViewNode != null) { + return potentialParentTreeViewNode; + } + } + } + } + + return null; + } + + /** + * Returns the node in the tree related to the parentContent or null if + * can't be found. This method should be used when view is grouped by + * hosts/persons. + * + * @param treeViewExplorerMgr The explorer manager. + * @param parentContent The content whose equivalent node will be + * returned if found. + * + * @return The node if found or null. + */ + private Node getParentNodeGroupedByPersonHost(ExplorerManager treeViewExplorerMgr, Content parentContent) { + // 'Group by Data Source' view + + SleuthkitCase skCase; + String dsname; + try { + // get the objid/name of the datasource of the selected content. + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + long contentDSObjid = parentContent.getDataSource().getId(); + DataSource datasource = skCase.getDataSource(contentDSObjid); + dsname = datasource.getName(); + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + + // the tree view needs to be searched to find the parent treeview node. + /* NOTE: we can't do a lookup by data source name here, becase if there + are multiple data sources with the same name, then "getChildren().findChild(dsname)" + simply returns the first one that it finds. Instead we have to loop over all + data sources with that name, and make sure we find the correct one. + */ + List dataSourceLevelNodes = Stream.of(rootChildren.getNodes(true)) + .flatMap(rootNode -> getDataSourceLevelNodes(rootNode).stream()) + .collect(Collectors.toList()); + + for (Node treeNode : dataSourceLevelNodes) { + // in the root, look for a data source node with the name of interest + if (!(treeNode.getName().equals(dsname))) { + continue; + } + + // for this data source, get the "Data Sources" child node + Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourceFilesNode.getNameIdentifier()); + + // check whether this is the data source we are looking for + Node parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode); + if (parentTreeViewNode != null) { + // found the data source node + return parentTreeViewNode; + } + } + } catch (NoCurrentCaseException | TskDataException | TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS + } + + return null; + } + + /** + * Set the node selection in the tree. + * @param content The content to select. + * @param parentTreeViewNode The node that is the parent of the content. + * @param treeViewTopComponent The DirectoryTreeTopComponent. + * @param treeViewExplorerMgr The ExplorerManager. + */ + private void setNodeSelection(Content content, Node parentTreeViewNode, DirectoryTreeTopComponent treeViewTopComponent, ExplorerManager treeViewExplorerMgr) { + /* + * Set the child selection info of the parent tree node, then select + * the parent node in the tree view. The results view will retrieve + * this selection info and use it to complete this action when the + * tree view top component responds to the selection of the parent + * node by pushing it into the results view top component. + */ + DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal(); + undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content)); + if (content instanceof BlackboardArtifact) { + BlackboardArtifact artifact = ((BlackboardArtifact) content); + long associatedId = artifact.getObjectID(); + try { + Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId); + undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId()); + } + } + + TreeView treeView = treeViewTopComponent.getTree(); + treeView.expandNode(parentTreeViewNode); + if (treeViewTopComponent.getSelectedNode().equals(parentTreeViewNode)) { + //In the case where our tree view already has the destination directory selected + //due to an optimization in the ExplorerManager.setExploredContextAndSelection method + //the property change we listen for to call DirectoryTreeTopComponent.respondSelection + //will not be sent so we call it manually ourselves after making + //the directory listing the active tab. + treeViewTopComponent.setDirectoryListingActive(); + treeViewTopComponent.respondSelection(treeViewExplorerMgr.getSelectedNodes(), new Node[]{parentTreeViewNode}); + } else { + try { + treeViewExplorerMgr.setExploredContextAndSelection(parentTreeViewNode, new Node[]{parentTreeViewNode}); + } catch (PropertyVetoException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotSelectDirectory()); + logger.log(Level.SEVERE, "Failed to select the parent node in the tree view", ex); //NON-NLS + } + } + } + /** * If the node has lookup of host or person, returns children. If not, just * returns itself. * * @param node The node. + * * @return The child nodes that are at the data source level. */ private List getDataSourceLevelNodes(Node node) { if (node == null) { return Collections.emptyList(); - } else if (node.getLookup().lookup(Host.class) != null || - node.getLookup().lookup(Person.class) != null || - DataSourcesNode.getNameIdentifier().equals(node.getLookup().lookup(String.class)) || - PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) { + } else if (node.getLookup().lookup(Host.class) != null + || node.getLookup().lookup(Person.class) != null + || DataSourcesNode.getNameIdentifier().equals(node.getLookup().lookup(String.class)) + || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) { Children children = node.getChildren(); - Node[] childNodes = children == null ? null : children.getNodes(); + Node[] childNodes = children == null ? null : children.getNodes(true); if (childNodes == null) { return Collections.emptyList(); } - return Stream.of(node.getChildren().getNodes()) + return Stream.of(node.getChildren().getNodes(true)) .flatMap(parent -> getDataSourceLevelNodes(parent).stream()) .collect(Collectors.toList()); } else { @@ -350,7 +399,8 @@ public class ViewContextAction extends AbstractAction { * of the specified content. * * @param parentContent parent content for the content to be searched for - * @param node Node tree to search + * @param node Node tree to search + * * @return Node object of the matching parent, NULL if not found */ private Node findParentNodeInTree(Content parentContent, Node node) { @@ -377,6 +427,11 @@ public class ViewContextAction extends AbstractAction { Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true); Children ancestorChildren = dummyRootNode.getChildren(); + // if content is the data source provided, return that. + if (ancestorChildren.getNodesCount() == 1 && StringUtils.equals(ancestorChildren.getNodeAt(0).getName(), node.getName())) { + return node; + } + /* * Search the tree for the parent node. Note that this algorithm * simply discards "extra" ancestor nodes not shown in the tree, @@ -387,8 +442,9 @@ public class ViewContextAction extends AbstractAction { Node parentTreeViewNode = null; for (int i = 0; i < ancestorChildren.getNodesCount(); i++) { Node ancestorNode = ancestorChildren.getNodeAt(i); - for (int j = 0; j < treeNodeChildren.getNodesCount(); j++) { - Node treeNode = treeNodeChildren.getNodeAt(j); + Node[] treeNodeChilds = treeNodeChildren.getNodes(true); + for (int j = 0; j < treeNodeChilds.length; j++) { + Node treeNode = treeNodeChilds[j]; if (ancestorNode.getName().equals(treeNode.getName())) { parentTreeViewNode = treeNode; treeNodeChildren = treeNode.getChildren();