/* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> 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.directorytree; import org.sleuthkit.autopsy.datamodel.EmptyNode; import java.awt.Cursor; import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.prefs.PreferenceChangeEvent; import java.util.prefs.PreferenceChangeListener; import javax.swing.Action; import javax.swing.SwingUtilities; import javax.swing.tree.TreeSelectionModel; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.explorer.view.BeanTreeView; import org.openide.explorer.view.TreeView; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.NodeNotFoundException; import org.openide.nodes.NodeOp; import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.BlackboardResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.CoreComponentControl; import org.sleuthkit.autopsy.corecomponentinterfaces.DataExplorer; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DataSources; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.ExtractedContent; import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.KeywordHits; import org.sleuthkit.autopsy.datamodel.KnownFileFilterNode; import org.sleuthkit.autopsy.datamodel.Reports; import org.sleuthkit.autopsy.datamodel.Results; import org.sleuthkit.autopsy.datamodel.ResultsNode; import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.autopsy.datamodel.SlackFileFilterNode; import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.datamodel.Views; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; /** * Top component which displays something. */ // Registered as a service provider for DataExplorer in layer.xml public final class DirectoryTreeTopComponent extends TopComponent implements DataExplorer, ExplorerManager.Provider, BlackboardResultViewer { private final transient ExplorerManager em = new ExplorerManager(); private static DirectoryTreeTopComponent instance; private final DataResultTopComponent dataResult = new DataResultTopComponent(true, NbBundle.getMessage(this.getClass(), "DirectoryTreeTopComponent.title.text")); private final LinkedList<String[]> backList; private final LinkedList<String[]> forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); private RootContentChildren contentChildren; /** * the constructor */ private DirectoryTreeTopComponent() { initComponents(); // only allow one item to be selected at a time ((BeanTreeView) jScrollPane1).setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); // remove the close button putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE); setName(NbBundle.getMessage(DirectoryTreeTopComponent.class, "CTL_DirectoryTreeTopComponent")); setToolTipText(NbBundle.getMessage(DirectoryTreeTopComponent.class, "HINT_DirectoryTreeTopComponent")); subscribeToChangeEvents(); associateLookup(ExplorerUtils.createLookup(em, getActionMap())); // set the back & forward list and also disable the back & forward button this.backList = new LinkedList<>(); this.forwardList = new LinkedList<>(); backButton.setEnabled(false); forwardButton.setEnabled(false); } /** * Make this TopComponent a listener to various change events. */ private void subscribeToChangeEvents() { UserPreferences.addChangeListener(new PreferenceChangeListener() { @Override public void preferenceChange(PreferenceChangeEvent evt) { switch (evt.getKey()) { case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE: case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE: refreshContentTreeSafe(); break; case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE: case UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE: // TODO: Need a way to refresh the Views subtree break; } } }); Case.addEventSubscriber(new HashSet<>(Arrays.asList(Case.Events.CURRENT_CASE.toString(), Case.Events.DATA_SOURCE_ADDED.toString())), this); this.em.addPropertyChangeListener(this); IngestManager.getInstance().addIngestJobEventListener(this); IngestManager.getInstance().addIngestModuleEventListener(this); } public void setDirectoryListingActive() { this.dataResult.requestActive(); } public void openDirectoryListing() { this.dataResult.open(); } public DataResultTopComponent getDirectoryListing() { return this.dataResult; } /** * 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 * regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jScrollPane1 = new BeanTreeView(); backButton = new javax.swing.JButton(); forwardButton = new javax.swing.JButton(); showRejectedCheckBox = new javax.swing.JCheckBox(); jScrollPane1.setBorder(null); backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.backButton.text")); // NOI18N backButton.setBorderPainted(false); backButton.setContentAreaFilled(false); backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N backButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); backButton.setMaximumSize(new java.awt.Dimension(55, 100)); backButton.setMinimumSize(new java.awt.Dimension(5, 5)); backButton.setPreferredSize(new java.awt.Dimension(23, 23)); backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N backButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { backButtonActionPerformed(evt); } }); forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(forwardButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.forwardButton.text")); // NOI18N forwardButton.setBorderPainted(false); forwardButton.setContentAreaFilled(false); forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N forwardButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); forwardButton.setMaximumSize(new java.awt.Dimension(55, 100)); forwardButton.setMinimumSize(new java.awt.Dimension(5, 5)); forwardButton.setPreferredSize(new java.awt.Dimension(23, 23)); forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N forwardButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { forwardButtonActionPerformed(evt); } }); org.openide.awt.Mnemonics.setLocalizedText(showRejectedCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.showRejectedCheckBox.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addGap(5, 5, 5) .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE) .addComponent(showRejectedCheckBox) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(5, 5, 5) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(showRejectedCheckBox)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 838, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); }// </editor-fold>//GEN-END:initComponents private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // the end is the current place, String[] currentNodePath = backList.pollLast(); forwardList.addLast(currentNodePath); forwardButton.setEnabled(true); /* * We peek instead of poll because we use its existence in the list * later on so that we do not reset the forward list after the selection * occurs. */ String[] newCurrentNodePath = backList.peekLast(); // enable / disable the back and forward button if (backList.size() > 1) { backButton.setEnabled(true); } else { backButton.setEnabled(false); } // update the selection on directory tree setSelectedNode(newCurrentNodePath, null); this.setCursor(null); }//GEN-LAST:event_backButtonActionPerformed private void forwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_forwardButtonActionPerformed // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); String[] newCurrentNodePath = forwardList.pollLast(); if (!forwardList.isEmpty()) { forwardButton.setEnabled(true); } else { forwardButton.setEnabled(false); } backList.addLast(newCurrentNodePath); backButton.setEnabled(true); // update the selection on directory tree setSelectedNode(newCurrentNodePath, null); this.setCursor(null); }//GEN-LAST:event_forwardButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton backButton; private javax.swing.JButton forwardButton; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JCheckBox showRejectedCheckBox; // End of variables declaration//GEN-END:variables /** * Gets default instance. Do not use directly: reserved for *.settings files * only, i.e. deserialization routines; otherwise you could get a * non-deserialized instance. To obtain the singleton instance, use * {@link #findInstance}. */ public static synchronized DirectoryTreeTopComponent getDefault() { if (instance == null) { instance = new DirectoryTreeTopComponent(); } return instance; } /** * Obtain the DirectoryTreeTopComponent instance. Never call * {@link #getDefault} directly! */ public static synchronized DirectoryTreeTopComponent findInstance() { WindowManager winManager = WindowManager.getDefault(); TopComponent win = winManager.findTopComponent(PREFERRED_ID); if (win == null) { LOGGER.warning( "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system."); //NON-NLS return getDefault(); } if (win instanceof DirectoryTreeTopComponent) { return (DirectoryTreeTopComponent) win; } LOGGER.warning( "There seem to be multiple components with the '" + PREFERRED_ID //NON-NLS + "' ID. That is a potential source of errors and unexpected behavior."); //NON-NLS return getDefault(); } /** * Overwrite when you want to change default persistence type. Default * persistence type is PERSISTENCE_ALWAYS * * @return TopComponent.PERSISTENCE_ALWAYS */ @Override public int getPersistenceType() { return TopComponent.PERSISTENCE_NEVER; } /** * Called only when top component was closed on all workspaces before and * now is opened for the first time on some workspace. The intent is to * provide subclasses information about TopComponent's life cycle across all * existing workspaces. Subclasses will usually perform initializing tasks * here. */ @Override public void componentOpened() { // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { if (Case.isCaseOpen()) { Case currentCase = Case.getCurrentCase(); // close the top component if there's no image in this case if (currentCase.hasData() == false) { //this.close(); ((BeanTreeView) this.jScrollPane1).setRootVisible(false); // hide the root } else { // if there's at least one image, load the image and open the top component List<Object> items = new ArrayList<>(); final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); items.add(new DataSources()); items.add(new Views(tskCase)); items.add(new Results(tskCase)); items.add(new Tags()); items.add(new Reports()); contentChildren = new RootContentChildren(items); Node root = new AbstractNode(contentChildren) { /** * to override the right click action in the white blank * space area on the directory tree window */ @Override public Action[] getActions(boolean popup) { return new Action[]{}; } // Overide the AbstractNode use of DefaultHandle to return // a handle which can be serialized without a parent @Override public Node.Handle getHandle() { return new Node.Handle() { @Override public Node getNode() throws IOException { return em.getRootContext(); } }; } }; root = new DirectoryTreeFilterNode(root, true); em.setRootContext(root); em.getRootContext().setName(currentCase.getName()); em.getRootContext().setDisplayName(currentCase.getName()); ((BeanTreeView) this.jScrollPane1).setRootVisible(false); // hide the root // Reset the forward and back lists because we're resetting the root context resetHistory(); Children childNodes = em.getRootContext().getChildren(); TreeView tree = getTree(); Node results = childNodes.findChild(ResultsNode.NAME); tree.expandNode(results); Children resultsChilds = results.getChildren(); tree.expandNode(resultsChilds.findChild(KeywordHits.NAME)); tree.expandNode(resultsChilds.findChild(ExtractedContent.NAME)); Accounts accounts = resultsChilds.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); showRejectedCheckBox.setSelected(false); Node views = childNodes.findChild(ViewsNode.NAME); Children viewsChilds = views.getChildren(); for (Node n : viewsChilds.getNodes()) { tree.expandNode(n); } tree.collapseNode(views); // if the dataResult is not opened if (!dataResult.isOpened()) { dataResult.open(); // open the data result top component as well when the directory tree is opened } // select the first image node, if there is one // (this has to happen after dataResult is opened, because the event // of changing the selected node fires a handler that tries to make // dataResult active) if (childNodes.getNodesCount() > 0) { try { em.setSelectedNodes(new Node[]{childNodes.getNodeAt(0)}); } catch (Exception ex) { LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS } } } } } finally { this.setCursor(null); } } /** * Called only when top component was closed so that now it is closed on all * workspaces in the system. The intent is to provide subclasses information * about TopComponent's life cycle across workspaces. Subclasses will * usually perform cleaning tasks here. */ @Override public void componentClosed() { //@@@ push the selection node to null? contentChildren = null; } void writeProperties(java.util.Properties p) { // better to version settings since initial version as advocated at // http://wiki.apidesign.org/wiki/PropertyFiles p.setProperty("version", "1.0"); // TODO store your settings } Object readProperties(java.util.Properties p) { if (instance == null) { instance = this; } instance.readPropertiesImpl(p); return instance; } private void readPropertiesImpl(java.util.Properties p) { String version = p.getProperty("version"); // TODO read your settings according to their version } /** * Returns the unique ID of this TopComponent * * @return PREFERRED_ID the unique ID of this TopComponent */ @Override protected String preferredID() { return PREFERRED_ID; } @Override public boolean canClose() { return !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case } /** * Gets the explorer manager. * * @return the explorer manager */ @Override public ExplorerManager getExplorerManager() { return this.em; } /** * Right click action for this top component window * * @return actions the list of actions */ @Override public Action[] getActions() { return new Action[]{}; } /** * Gets the original selected node on the explorer manager * * @return node the original selected Node */ public Node getSelectedNode() { Node result = null; Node[] selectedNodes = this.getExplorerManager().getSelectedNodes(); if (selectedNodes.length > 0) { result = selectedNodes[0]; } return result; } /** * The "listener" that listens to any changes made in the Case.java class. * It will do something based on the changes in the Case.java class. * * @param evt the property change event */ @Override public void propertyChange(PropertyChangeEvent evt) { if (RuntimeProperties.coreComponentsAreActive()) { String changed = evt.getPropertyName(); if (changed.equals(Case.Events.CURRENT_CASE.toString())) { // changed current case // When a case is closed, the old value of this property is the // closed Case object and the new value is null. When a case is // opened, the old value is null and the new value is the new Case // object. // @@@ This needs to be revisited. Perhaps case closed and case // opened events instead of property change events would be a better // solution. Either way, more probably needs to be done to clean up // data model objects when a case is closed. if (evt.getOldValue() != null && evt.getNewValue() == null) { // The current case has been closed. Reset the ExplorerManager. SwingUtilities.invokeLater(() -> { Node emptyNode = new AbstractNode(Children.LEAF); em.setRootContext(emptyNode); }); } else if (evt.getNewValue() != null) { // A new case has been opened. Reset the ExplorerManager. Case newCase = (Case) evt.getNewValue(); final String newCaseName = newCase.getName(); SwingUtilities.invokeLater(() -> { em.getRootContext().setName(newCaseName); em.getRootContext().setDisplayName(newCaseName); // Reset the forward and back // buttons. Note that a call to CoreComponentControl.openCoreWindows() // by the new Case object will lead to a componentOpened() call // that will repopulate the tree. // @@@ The repopulation of the tree in this fashion also merits // reconsideration. resetHistory(); }); } } // if the image is added to the case else if (changed.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { /** * Checking for a current case 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 currentCase = Case.getCurrentCase(); // We only need to trigger openCoreWindows() when the // first data source is added. if (currentCase.getDataSources().size() == 1) { SwingUtilities.invokeLater(() -> { CoreComponentControl.openCoreWindows(); }); } } catch (IllegalStateException | TskCoreException notUsed) { /** * Case is closed, do nothing. */ } } // change in node selection else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) { SwingUtilities.invokeLater(() -> { respondSelection((Node[]) evt.getOldValue(), (Node[]) evt.getNewValue()); }); } else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { // nothing to do here. // all nodes should be listening for these events and update accordingly. } } } @NbBundle.Messages("DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.") /** * Event handler to run when selection changed * * TODO this needs to be revised * * @param oldNodes * @param newNodes */ private void respondSelection(final Node[] oldNodes, final Node[] newNodes) { if (!Case.isCaseOpen()) { //handle in-between condition when case is being closed //and legacy selection events are pumped return; } // Some lock that prevents certain Node operations is set during the // ExplorerManager selection-change, so we must handle changes after the // selection-change event is processed. //TODO find a different way to refresh data result viewer, scheduling this //to EDT breaks loading of nodes in the background EventQueue.invokeLater(new Runnable() { @Override public void run() { // change the cursor to "waiting cursor" for this operation DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode(); if (treeNode != null) { DirectoryTreeFilterNode.OriginalNode origin = treeNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class); if (origin == null) { return; } Node originNode = origin.getNode(); //set node, wrap in filter node first to filter out children Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); Node kffn = new KnownFileFilterNode(drfn, KnownFileFilterNode.getSelectionContext(originNode)); Node sffn = new SlackFileFilterNode(kffn, SlackFileFilterNode.getSelectionContext(originNode)); // Create a TableFilterNode with knowledge of the node's type to allow for column order settings //Special case for when File Type Identification has not yet been run and //there are no mime types to populate Files by Mime Type Tree if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) { EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text()); Node emptyDrfn = new DataResultFilterNode(emptyNode, DirectoryTreeTopComponent.this.em); Node emptyKffn = new KnownFileFilterNode(emptyDrfn, KnownFileFilterNode.getSelectionContext(emptyNode)); Node emptySffn = new SlackFileFilterNode(emptyKffn, SlackFileFilterNode.getSelectionContext(originNode)); dataResult.setNode(new TableFilterNode(emptySffn, true, "This Node Is Empty")); //NON-NLS } else if (originNode instanceof DisplayableItemNode) { dataResult.setNode(new TableFilterNode(sffn, true, ((DisplayableItemNode) originNode).getItemType())); } else { dataResult.setNode(new TableFilterNode(sffn, true)); } String displayName = ""; Content content = originNode.getLookup().lookup(Content.class); if (content != null) { try { displayName = content.getUniquePath(); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: " + originNode); //NON-NLS } } else if (originNode.getLookup().lookup(String.class) != null) { displayName = originNode.getLookup().lookup(String.class); } dataResult.setPath(displayName); } // set the directory listing to be active if (oldNodes != null && newNodes != null && (oldNodes.length == newNodes.length)) { boolean sameNodes = true; for (int i = 0; i < oldNodes.length; i++) { sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName()); } if (!sameNodes) { dataResult.requestActive(); } } } finally { setCursor(null); } } }); // update the back and forward list updateHistory(em.getSelectedNodes()); } private void updateHistory(Node[] selectedNodes) { if (selectedNodes.length == 0) { return; } Node selectedNode = selectedNodes[0]; String selectedNodeName = selectedNode.getName(); /* * get the previous entry to make sure we don't duplicate it. Motivation * for this is also that if we used the back button, then we already * added the 'current' node to 'back' and we will detect that and not * reset the forward list. */ String[] currentLast = backList.peekLast(); String lastNodeName = null; if (currentLast != null) { lastNodeName = currentLast[currentLast.length - 1]; } if (currentLast == null || !selectedNodeName.equals(lastNodeName)) { //add to the list if the last if not the same as current final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); backList.addLast(selectedPath); // add the node to the "backList" if (backList.size() > 1) { backButton.setEnabled(true); } else { backButton.setEnabled(false); } forwardList.clear(); // clear the "forwardList" forwardButton.setEnabled(false); // disable the forward Button } } /** * Resets the back and forward list, and also disable the back and forward * buttons. */ private void resetHistory() { // clear the back and forward list backList.clear(); forwardList.clear(); backButton.setEnabled(false); forwardButton.setEnabled(false); } /** * Gets the tree on this DirectoryTreeTopComponent. * * @return tree the BeanTreeView */ public BeanTreeView getTree() { return (BeanTreeView) this.jScrollPane1; } /** * Refresh the content node part of the dir tree safely in the EDT thread */ public void refreshContentTreeSafe() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { refreshDataSourceTree(); } }); } /** * Refreshes changed content nodes */ private void refreshDataSourceTree() { Node selectedNode = getSelectedNode(); final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); Children rootChildren = em.getRootContext().getChildren(); Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME); if (dataSourcesFilterNode == null) { LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS return; } DirectoryTreeFilterNode.OriginalNode imagesNodeOrig = dataSourcesFilterNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class); if (imagesNodeOrig == null) { LOGGER.log(Level.SEVERE, "Cannot find data sources node, won't refresh the content tree"); //NON-NLS return; } Node imagesNode = imagesNodeOrig.getNode(); DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) imagesNode.getChildren(); contentRootChildren.refreshContentKeys(); //final TreeView tree = getTree(); //tree.expandNode(imagesNode); setSelectedNode(selectedPath, DataSourcesNode.NAME); } /** * Set the selected node using a path to a previously selected node. * * @param previouslySelectedNodePath Path to a previously selected node. * @param rootNodeName Name of the root node to match, may be null. */ private void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName) { if (previouslySelectedNodePath == null) { return; } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (previouslySelectedNodePath.length > 0 && (rootNodeName == null || previouslySelectedNodePath[0].equals(rootNodeName))) { Node selectedNode = null; ArrayList<String> selectedNodePath = new ArrayList<>(Arrays.asList(previouslySelectedNodePath)); while (null == selectedNode && !selectedNodePath.isEmpty()) { try { selectedNode = NodeOp.findPath(em.getRootContext(), selectedNodePath.toArray(new String[0])); } catch (NodeNotFoundException ex) { // The selected node may have been deleted (e.g., a deleted tag), so truncate the path and try again. if (selectedNodePath.size() > 1) { selectedNodePath.remove(selectedNodePath.size() - 1); } else { StringBuilder nodePath = new StringBuilder(); for (int i = 0; i < previouslySelectedNodePath.length; ++i) { nodePath.append(previouslySelectedNodePath[i]).append("/"); } LOGGER.log(Level.WARNING, "Failed to find any nodes to select on path " + nodePath.toString(), ex); //NON-NLS break; } } } if (null != selectedNode) { if (rootNodeName != null) { //called from tree auto refresh context //remove last from backlist, because auto select will result in duplication backList.pollLast(); } try { em.setExploredContextAndSelection(selectedNode, new Node[]{selectedNode}); } catch (PropertyVetoException ex) { LOGGER.log(Level.WARNING, "Property veto from ExplorerManager setting selection to " + selectedNode.getName(), ex); //NON-NLS } } } } }); } @Override public TopComponent getTopComponent() { return this; } @Override public boolean hasMenuOpenAction() { return false; } @Override public void viewArtifact(final BlackboardArtifact art) { int typeID = art.getArtifactTypeID(); String typeName = art.getArtifactTypeName(); Children rootChilds = em.getRootContext().getChildren(); Node treeNode = null; Node resultsNode = rootChilds.findChild(ResultsNode.NAME); Children resultsChilds = resultsNode.getChildren(); if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { Node hashsetRootNode = resultsChilds.findChild(typeName); Children hashsetRootChilds = hashsetRootNode.getChildren(); try { String setName = null; List<BlackboardAttribute> attributes = art.getAttributes(); for (BlackboardAttribute att : attributes) { int typeId = att.getAttributeType().getTypeID(); if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { setName = att.getValueString(); } } treeNode = hashsetRootChilds.findChild(setName); } catch (TskException ex) { LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS } } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { Node keywordRootNode = resultsChilds.findChild(typeName); Children keywordRootChilds = keywordRootNode.getChildren(); try { String listName = null; String keywordName = null; List<BlackboardAttribute> attributes = art.getAttributes(); for (BlackboardAttribute att : attributes) { int typeId = att.getAttributeType().getTypeID(); if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { listName = att.getValueString(); } else if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) { keywordName = att.getValueString(); } } Node listNode = keywordRootChilds.findChild(listName); if (listNode == null) { return; } Children listChildren = listNode.getChildren(); if (listChildren == null) { return; } treeNode = listChildren.findChild(keywordName); } catch (TskException ex) { LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS } } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() || typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { Node interestingItemsRootNode = resultsChilds.findChild(typeName); Children interestingItemsRootChildren = interestingItemsRootNode.getChildren(); try { String setName = null; List<BlackboardAttribute> attributes = art.getAttributes(); for (BlackboardAttribute att : attributes) { int typeId = att.getAttributeType().getTypeID(); if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { setName = att.getValueString(); } } treeNode = interestingItemsRootChildren.findChild(setName); } catch (TskException ex) { LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS } } else { Node extractedContent = resultsChilds.findChild(ExtractedContent.NAME); Children extractedChilds = extractedContent.getChildren(); if (extractedChilds == null) { return; } treeNode = extractedChilds.findChild(typeName); } if (treeNode == null) { return; } try { em.setExploredContextAndSelection(treeNode, new Node[]{treeNode}); } catch (PropertyVetoException ex) { LOGGER.log(Level.WARNING, "Property Veto: ", ex); //NON-NLS } // Another thread is needed because we have to wait for dataResult to populate EventQueue.invokeLater(new Runnable() { @Override public void run() { Children resultChilds = dataResult.getRootNode().getChildren(); Node select = resultChilds.findChild(Long.toString(art.getArtifactID())); if (select != null) { dataResult.requestActive(); dataResult.setSelectedNodes(new Node[]{select}); fireViewerComplete(); } } }); } @Override public void viewArtifactContent(BlackboardArtifact art) { new ViewContextAction( NbBundle.getMessage(this.getClass(), "DirectoryTreeTopComponent.action.viewArtContent.text"), new BlackboardArtifactNode(art)).actionPerformed(null); } @Override public void addOnFinishedListener(PropertyChangeListener l) { DirectoryTreeTopComponent.this.addPropertyChangeListener(l); } void fireViewerComplete() { try { firePropertyChange(BlackboardResultViewer.FINISHED_DISPLAY_EVT, 0, 1); } catch (Exception e) { LOGGER.log(Level.SEVERE, "DirectoryTreeTopComponent listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "DirectoryTreeTopComponent.moduleErr"), NbBundle.getMessage(this.getClass(), "DirectoryTreeTopComponent.moduleErr.msg"), MessageNotifyUtil.MessageType.ERROR); } } }