/*
* Autopsy Forensic Browser
*
* Copyright 2011 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.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.AbstractAction;
import javax.swing.SwingWorker;
import org.openide.nodes.AbstractNode;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.TreeView;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VolumeSystem;
/**
* View the directory content associated with the given Artifact in the
* DataResultViewer.
*
* 1. Expands the Directory Tree to the location of the parent Node of the
* associated Content. 2. Selects the parent Node of the associated Content in
* the Directory Tree, which causes the parent Node's Children to be visible in
* the DataResultViewer. 3. Waits for all the Children to be contentNode in the
* DataResultViewer and selects the Node that represents the Content.
*/
public class ViewContextAction extends AbstractAction {
private Content content;
private static final Logger logger = Logger.getLogger(ViewContextAction.class.getName());
public ViewContextAction(String title, BlackboardArtifactNode node) {
super(title);
this.content = node.getLookup().lookup(Content.class);
}
public ViewContextAction(String title, AbstractFsContentNode<? extends AbstractFile> node) {
super(title);
this.content = node.getLookup().lookup(Content.class);
}
public ViewContextAction(String title, Content content) {
super(title);
this.content = content;
}
@Override
public void actionPerformed(ActionEvent e) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
// create a list of Content objects starting with content's
// Image and ends with content
ReverseHierarchyVisitor vtor = new ReverseHierarchyVisitor();
List<Content> hierarchy = content.accept(vtor);
Collections.reverse(hierarchy);
Node generated = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(hierarchy)), true);
Children genChilds = generated.getChildren();
final DirectoryTreeTopComponent dirTree = DirectoryTreeTopComponent.findInstance();
TreeView dirTreeView = dirTree.getTree();
ExplorerManager dirTreeExplorerManager = dirTree.getExplorerManager();
Node dirTreeRootNode = dirTreeExplorerManager.getRootContext();
Children dirChilds = dirTreeRootNode.getChildren();
Children currentChildren = dirChilds.findChild(DataSourcesNode.NAME).getChildren();
Node dirExplored = null;
// Find the parent node of the content in the directory tree
for (int i = 0; i < genChilds.getNodesCount() - 1; i++) {
Node currentGeneratedNode = genChilds.getNodeAt(i);
for (int j = 0; j < currentChildren.getNodesCount(); j++) {
Node currentDirectoryTreeNode = currentChildren.getNodeAt(j);
if (currentGeneratedNode.getDisplayName().equals(currentDirectoryTreeNode.getDisplayName())) {
dirExplored = currentDirectoryTreeNode;
dirTreeView.expandNode(dirExplored);
currentChildren = currentDirectoryTreeNode.getChildren();
break;
}
}
}
// Set the parent node of the content as the selection in the
// directory tree
try {
if (dirExplored != null) {
dirTreeView.expandNode(dirExplored);
dirTreeExplorerManager.setExploredContextAndSelection(dirExplored, new Node[]{dirExplored});
}
} catch (PropertyVetoException ex) {
logger.log(Level.WARNING, "Couldn't set selected node", ex); //NON-NLS
}
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
DataResultTopComponent dataResultTC = dirTree.getDirectoryListing();
Node currentRootNodeOfDataResultTC = dataResultTC.getRootNode();
Node contentNode = content.accept(new RootContentChildren.CreateSleuthkitNodeVisitor());
new SelectionWorker(dataResultTC, contentNode.getName(), currentRootNodeOfDataResultTC).execute();
}
});
}
});
}
/**
* Waits for a Node's children to be generated, regardless of whether they
* are lazily loaded, then sets the correct selection in a specified
* DataResultTopComponent.
*/
private class SelectionWorker extends SwingWorker<Node[], Integer> {
DataResultTopComponent dataResultTC;
String nameOfNodeToSelect;
Node originalRootNodeOfDataResultTC;
SelectionWorker(DataResultTopComponent dataResult, String nameToSelect, Node originalRoot) {
this.dataResultTC = dataResult;
this.nameOfNodeToSelect = nameToSelect;
this.originalRootNodeOfDataResultTC = originalRoot;
}
@Override
protected Node[] doInBackground() throws Exception {
// Calls to Children::getNodes(true) block until all child Nodes have
// been created, regardless of whether they are created lazily.
// This means that this call will return the actual child Nodes
// and will *NEVER* return a proxy wait Node. This is done on the
// background thread to ensure we are not hanging the ui as it could
// be a lengthy operation.
return originalRootNodeOfDataResultTC.getChildren().getNodes(true);
}
@Override
protected void done() {
Node[] nodesDisplayedInDataResultViewer;
try {
nodesDisplayedInDataResultViewer = get();
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.WARNING, "Failed to get nodes in selection worker.", ex); //NON-NLS
return;
} // catch and ignore if we were cancelled
catch (java.util.concurrent.CancellationException ex) {
return;
}
// It is possible the user selected a different Node to be displayed
// in the DataResultViewer while the child Nodes were being generated.
// In that case, we don't want to set the selection because it the
// nodes returned from get() won't be in the DataResultTopComponent's
// ExplorerManager. If we did call setSelectedNodes, it would clear
// the current selection, which is not good.
if (dataResultTC.getRootNode().equals(originalRootNodeOfDataResultTC) == false) {
return;
}
// Find the correct node to select from the nodes that are displayed
// in the data result viewer and set it as the selection of the
// DataResultTopComponent.
for (Node node : nodesDisplayedInDataResultViewer) {
if (nameOfNodeToSelect.equals(node.getName())) {
dataResultTC.requestActive();
dataResultTC.setSelectedNodes(new Node[]{node});
DirectoryTreeTopComponent.getDefault().fireViewerComplete();
break;
}
}
}
}
/**
* The ReverseHierarchyVisitor class is designed to return a list of Content
* objects starting with the one the user calls 'accept' with and ending at
* the Image object. Please NOTE that Content objects in this hierarchy of
* type VolumeSystem and FileSystem are skipped. This seems to be necessary
* because
* org.sleuthkit.autopsy.datamodel.AbstractContentChildren.CreateSleuthkitNodeVisitor
* does not support these types.
*/
private class ReverseHierarchyVisitor extends ContentVisitor.Default<List<Content>> {
List<Content> ret = new ArrayList<Content>();
private List<Content> visitParentButDontAddMe(Content content) {
Content parent = null;
try {
parent = content.getParent();
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Couldn't get parent of Content object: " + content); //NON-NLS
}
return parent == null ? ret : parent.accept(this);
}
@Override
protected List<Content> defaultVisit(Content content) {
ret.add(content);
Content parent = null;
try {
parent = content.getParent();
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Couldn't get parent of Content object: " + content); //NON-NLS
}
return parent == null ? ret : parent.accept(this);
}
@Override
public List<Content> visit(FileSystem fs) {
return visitParentButDontAddMe(fs);
}
@Override
public List<Content> visit(VolumeSystem vs) {
return visitParentButDontAddMe(vs);
}
}
}