Package org.sleuthkit.autopsy.directorytree

Source Code of org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent

/*
* Autopsy Forensic Browser
*
* Copyright 2011-2014 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 java.awt.Cursor;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
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.JPanel;
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.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.BlackboardResultViewer;
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.ExtractedContent;
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.Views;
import org.sleuthkit.autopsy.datamodel.ViewsNode;
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 transient ExplorerManager em = new ExplorerManager();
    private static DirectoryTreeTopComponent instance;
    private DataResultTopComponent dataResult = new DataResultTopComponent(true, NbBundle.getMessage(this.getClass(),
            "DirectoryTreeTopComponent.title.text"));
    private LinkedList<String[]> backList;
    private LinkedList<String[]> forwardList;
    /**
     * path to the icon used by the component and its open action
     */
//    static final String ICON_PATH = "SET/PATH/TO/ICON/HERE";
    private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS
    private PropertyChangeSupport pcs;
    // for error handling
    private JPanel caller;
    private String className = this.getClass().toString();
    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()));


        this.pcs = new PropertyChangeSupport(this);

        // 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_SOURCES_TREE:
                        refreshContentTreeSafe();
                        break;
                    case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE:
                        // TODO: Need a way to refresh the Views subtree
                        break;
                }
            }
        });
        Case.addPropertyChangeListener(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();
        jSeparator1 = new javax.swing.JSeparator();

        jScrollPane1.setBorder(null);

        backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N NON-NLS
        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 NON-NLS
        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 NON-NLS
        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 NON-NLS
        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 NON-NLS
        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 NON-NLS
        forwardButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                forwardButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .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)
                .addContainerGap(206, Short.MAX_VALUE))
            .addComponent(jSeparator1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(0, 0, 0)
                .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 1, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(0, 0, 0)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 860, Short.MAX_VALUE)
                .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.JSeparator jSeparator1;
    // 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.existsCurrentCase()) {
                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 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));


                    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.existsCurrentCase() || 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) {
        String changed = evt.getPropertyName();
        Object oldValue = evt.getOldValue();
        Object newValue = evt.getNewValue();

        // change in the case name
        if (changed.equals(Case.Events.NAME.toString())) {
            // set the main title of the window
            String oldCaseName = oldValue.toString();
            String newCaseName = newValue.toString();


            // update the case name
            if ((!oldCaseName.equals("")) && (!newCaseName.equals(""))) {
                // change the root name and display name
                em.getRootContext().setName(newCaseName);
                em.getRootContext().setDisplayName(newCaseName);
            }
        } // changed current case
        else if (changed.equals(Case.Events.CURRENT_CASE.toString())) {
            // 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 (oldValue != null && newValue == null) {
                // The current case has been closed. Reset the ExplorerManager.
                Node emptyNode = new AbstractNode(Children.LEAF);
                em.setRootContext(emptyNode);
            } else if (newValue != null) {
                // A new case has been opened. 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())) {
            componentOpened();
        }
        // change in node selection
        else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
            respondSelection((Node[]) oldValue, (Node[]) newValue);
        }
        else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
            // nothing to do here.
            // all nodes should be listening for these events and update accordingly.
        } else if (changed.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
                || changed.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    refreshDataSourceTree();
                }
            });
        } else if (changed.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    refreshDataSourceTree();
                }
            });
        }
    }

    /**
     * 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 {

                    // make sure dataResult is open, redundant?
                    //dataResult.open();

                    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));
                        dataResult.setNode(new TableFilterNode(kffn, 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);
    }

    @Override
    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }

    @Override
    public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }

    /**
     * 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();

        RootContentChildren contentRootChildren = (RootContentChildren) imagesNode.getChildren();
        contentRootChildren.refreshContentKeys();

        //final TreeView tree = getTree();
        //tree.expandNode(imagesNode);

        setSelectedNode(selectedPath, DataSourcesNode.NAME);

    }

    /**
     * Refreshes the nodes in the tree to reflect updates in the database should
     * be called in the gui thread
     */
//    public void refreshResultsTree(final BlackboardArtifact.ARTIFACT_TYPE... types) {
//        //save current selection
//        Node selectedNode = getSelectedNode();
//        final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext());
//
//        //TODO: instead, we should choose a specific key to refresh? Maybe?
//        //contentChildren.refreshKeys();
//
//        Children dirChilds = em.getRootContext().getChildren();
//
//        Node results = dirChilds.findChild(ResultsNode.NAME);
//        if (results == null) {
//            logger.log(Level.SEVERE, "Cannot find Results filter node, won't refresh the bb tree"); //NON-NLS
//            return;
//        }
//       
//        OriginalNode original = results.getLookup().lookup(OriginalNode.class);
//        ResultsNode resultsNode = (ResultsNode) original.getNode();
//        RootContentChildren resultsNodeChilds = (RootContentChildren) resultsNode.getChildren();
//        resultsNodeChilds.refreshKeys(types);
//
//       
//        final TreeView tree = getTree();
//        // @@@ tree.expandNode(results);
//
//        Children resultsChilds = results.getChildren();
//        if (resultsChilds == null) {
//            return;
//        }
//
//        Node childNode = resultsChilds.findChild(KeywordHits.NAME);
//        if (childNode == null) {
//            return;
//        }
//        // @@@tree.expandNode(childNode);
//
//        childNode = resultsChilds.findChild(ExtractedContent.NAME);
//        if (childNode == null) {
//            return;
//        }
//        tree.expandNode(childNode);
//
//        //restores selection if it was under the Results node
//        //@@@ setSelectedNode(selectedPath, ResultsNode.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) {
        BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(art.getArtifactTypeID());
        Children rootChilds = em.getRootContext().getChildren();
        Node treeNode = null;
        Node resultsNode = rootChilds.findChild(ResultsNode.NAME);
        Children resultsChilds = resultsNode.getChildren();
        if (type.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT)) {
            Node hashsetRootNode = resultsChilds.findChild(type.getLabel());
            Children hashsetRootChilds = hashsetRootNode.getChildren();
            try {
                String setName = null;
                List<BlackboardAttribute> attributes = art.getAttributes();
                for (BlackboardAttribute att : attributes) {
                    int typeId = att.getAttributeTypeID();
                    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 (type.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT)) {
            Node keywordRootNode = resultsChilds.findChild(type.getLabel());
            Children keywordRootChilds = keywordRootNode.getChildren();
            try {
                String listName = null;
                String keywordName = null;
                List<BlackboardAttribute> attributes = art.getAttributes();
                for (BlackboardAttribute att : attributes) {
                    int typeId = att.getAttributeTypeID();
                    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);
                Children listChildren = listNode.getChildren();
                treeNode = listChildren.findChild(keywordName);
            } catch (TskException ex) {
                logger.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
            }
        } else if (type.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)
                || type.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT)) {
            Node interestingItemsRootNode = resultsChilds.findChild(type.getLabel());
            Children interestingItemsRootChildren = interestingItemsRootNode.getChildren();
            try {
                String setName = null;
                List<BlackboardAttribute> attributes = art.getAttributes();
                for (BlackboardAttribute att : attributes) {
                    int typeId = att.getAttributeTypeID();
                    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();
            treeNode = extractedChilds.findChild(type.getLabel());
        }
        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);
    }

//    private class HistoryManager<T> {
//        private Stack<T> past, future;
//
//    }
    @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);
        }
    }
}
TOP

Related Classes of org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.