ViewContextAction fixes

This commit is contained in:
Richard Cordovano 2017-05-23 18:29:30 -04:00
parent b31384b025
commit 40b51e77e1
12 changed files with 1016 additions and 964 deletions

View File

@ -26,6 +26,7 @@ import java.awt.Graphics;
import java.awt.dnd.DnDConstants; import java.awt.dnd.DnDConstants;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.beans.PropertyVetoException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -36,6 +37,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.logging.Level;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.swing.JTable; import javax.swing.JTable;
@ -47,7 +49,6 @@ import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener; import javax.swing.event.TableColumnModelListener;
import javax.swing.table.TableCellRenderer; import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
import org.apache.commons.lang3.StringUtils;
import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumn;
import org.netbeans.swing.outline.DefaultOutlineCellRenderer; import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.DefaultOutlineModel;
@ -64,20 +65,22 @@ import org.openide.nodes.NodeMemberEvent;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbPreferences; import org.openide.util.NbPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
/** /**
* DataResult sortable table viewer * A tabular viewer for the results view.
* *
* @@@ Restore implementation of DataResultViewerTable as a DataResultViewer * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
* service provider when DataResultViewers can be made compatible with node * restore implementation of DataResultViewerTable as a DataResultViewer service
* multiple selection actions. * provider.
*/ */
//@ServiceProvider(service = DataResultViewer.class) //@ServiceProvider(service = DataResultViewer.class)
public class DataResultViewerTable extends AbstractDataResultViewer { public class DataResultViewerTable extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
@NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...") @NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...")
@ -234,12 +237,26 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
oldNode.removeNodeListener(pleasewaitNodeListener); oldNode.removeNodeListener(pleasewaitNodeListener);
} }
// if there's no selection node, do nothing
if (hasChildren) { if (hasChildren) {
Node root = selectedNode; currentRoot = selectedNode;
pleasewaitNodeListener.reset(); pleasewaitNodeListener.reset();
root.addNodeListener(pleasewaitNodeListener); currentRoot.addNodeListener(pleasewaitNodeListener);
setupTable(root); setupTable(selectedNode);
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
if (null != selectedChildInfo) {
Node[] childNodes = currentRoot.getChildren().getNodes(true);
for (int i = 0; i < childNodes.length; ++i) {
Node childNode = childNodes[i];
if (selectedChildInfo.matches(childNode)) {
try {
em.setSelectedNodes(new Node[]{childNode});
} catch (PropertyVetoException ex) {
logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
}
break;
}
}
}
} else { } else {
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node em.setRootContext(emptyNode); // make empty node
@ -258,11 +275,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* @param root The parent Node of the ContentNodes * @param root The parent Node of the ContentNodes
*/ */
private void setupTable(final Node root) { private void setupTable(final Node root) {
em.setRootContext(root); em.setRootContext(root);
currentRoot = root; currentRoot = root;
List<Node.Property<?>> props = loadColumnOrder();
/** /**
* OutlineView makes the first column be the result of * OutlineView makes the first column be the result of
@ -275,10 +289,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* duplicates getDisplayName(). The current implementation does not * duplicates getDisplayName(). The current implementation does not
* allow the first property column to be moved. * allow the first property column to be moved.
*/ */
List<Node.Property<?>> props = loadColumnOrder();
if (props.isEmpty() == false) { if (props.isEmpty() == false) {
Node.Property<?> prop = props.remove(0); Node.Property<?> prop = props.remove(0);
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
} }
/* /*
* show the horizontal scroll panel and show all the content & header If * show the horizontal scroll panel and show all the content & header If
* there is only one column (which was removed from props above) Just * there is only one column (which was removed from props above) Just
@ -287,9 +303,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF); outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
assignColumns(props); assignColumns(props);
setColumnWidths(); setColumnWidths();
loadColumnSorting(); loadColumnSorting();
} }

View File

@ -48,22 +48,21 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Thumbnail view of images in data result with paging support. * A thumbnail viewer for the results view, with paging support.
* *
* Paging is added to reduce memory footprint and load only up to (currently) * The paging is intended to reduce memory footprint by load only up to
* 1000 images at a time. This works whether or not the underlying content nodes * (currently) 1000 images at a time. This works whether or not the underlying
* are being lazy loaded or not. * content nodes are being lazy loaded or not.
* *
* TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
* restore implementation of DataResultViewerTable as a DataResultViewer service
* provider.
*/ */
// @@@ Restore implementation of DataResultViewerThumbnail as a DataResultViewer
// service provider when DataResultViewers can be made compatible with node
// multi-selection actions.
//@ServiceProvider(service = DataResultViewer.class) //@ServiceProvider(service = DataResultViewer.class)
final class DataResultViewerThumbnail extends AbstractDataResultViewer { final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
//flag to keep track if images are being loaded
private int curPage; private int curPage;
private int totalPages; private int totalPages;
private int curPageImages; private int curPageImages;
@ -71,8 +70,10 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private final PageUpdater pageUpdater = new PageUpdater(); private final PageUpdater pageUpdater = new PageUpdater();
/** /**
* Creates a DataResultViewerThumbnail object that is compatible with node * Constructs a thumbnail viewer for the results view, with paging support,
* multiple selection actions. * that is compatible with node multiple selection actions.
*
* @param explorerManager The shared ExplorerManager for the result viewers.
*/ */
DataResultViewerThumbnail(ExplorerManager explorerManager) { DataResultViewerThumbnail(ExplorerManager explorerManager) {
super(explorerManager); super(explorerManager);
@ -80,8 +81,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
} }
/** /**
* Creates a DataResultViewerThumbnail object that is NOT compatible with * Constructs a thumbnail viewer for the results view, with paging support,
* node multiple selection actions. * that is NOT compatible with node multiple selection actions.
*/ */
DataResultViewerThumbnail() { DataResultViewerThumbnail() {
initialize(); initialize();
@ -93,7 +94,6 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}) })
private void initialize() { private void initialize() {
initComponents(); initComponents();
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(
@ -311,20 +311,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override @Override
public void setNode(Node givenNode) { public void setNode(Node givenNode) {
// change the cursor to "waiting cursor" for this operation
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
if (givenNode != null) { if (givenNode != null) {
/*
* Wrap the given node in a ThumbnailViewChildren that will
* produce ThumbnailPageNodes with ThumbnailViewNode children
* from the child nodes of the given node.
*/
ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, iconSize); ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, iconSize);
final Node root = new AbstractNode(childNode); final Node root = new AbstractNode(childNode);
pageUpdater.setRoot(root); pageUpdater.setRoot(root);
root.addNodeListener(pageUpdater); root.addNodeListener(pageUpdater);
em.setRootContext(root); em.setRootContext(root);
} else { } else {
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node em.setRootContext(emptyNode);
iconView.setBackground(Color.BLACK); iconView.setBackground(Color.BLACK);
} }
} finally { } finally {
@ -349,21 +351,18 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
this.curPage = -1; this.curPage = -1;
curPageImages = 0; curPageImages = 0;
updateControls(); updateControls();
} }
@Override @Override
public void clearComponent() { public void clearComponent() {
this.iconView.removeAll(); this.iconView.removeAll();
this.iconView = null; this.iconView = null;
super.clearComponent(); super.clearComponent();
} }
private void nextPage() { private void nextPage() {
if (curPage < totalPages) { if (curPage < totalPages) {
curPage++; curPage++;
switchPage(); switchPage();
} }
} }
@ -371,7 +370,6 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private void previousPage() { private void previousPage() {
if (curPage > 1) { if (curPage > 1) {
curPage--; curPage--;
switchPage(); switchPage();
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2016 Basis Technology Corp. * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,12 +23,35 @@ import org.openide.nodes.FilterNode;
import org.openide.nodes.Node; import org.openide.nodes.Node;
/** /**
* A children (child factory) implementation for a TableFilterNode. A * A Children implementation for a TableFilterNode. A TableFilterNode creates at
* TableFilterNode creates at most one layer of child nodes for the node it * most one layer of child nodes for the node it wraps. It is designed to be
* wraps. It is designed to be used for nodes displayed in Autopsy table views. * used in the results view to ensure the individual viewers display only the
* first layer of child nodes.
*/ */
class TableFilterChildren extends FilterNode.Children { class TableFilterChildren extends FilterNode.Children {
/**
* Creates a Children object for a TableFilterNode. A TableFilterNode
* creates at most one layer of child nodes for the node it wraps. It is
* designed to be used in the results view to ensure the individual viewers
* display only the first layer of child nodes.
*
*
* @param wrappedNode The node wrapped by the TableFilterNode.
* @param createChildren True if a children (child factory) object should be
* created for the wrapped node.
*
* @return A children (child factory) object for a node wrapped by a
* TableFilterNode.
*/
public static Children createInstance(Node wrappedNode, boolean createChildren) {
if (createChildren) {
return new TableFilterChildren(wrappedNode);
} else {
return Children.LEAF;
}
}
/** /**
* Constructs a children (child factory) implementation for a * Constructs a children (child factory) implementation for a
* TableFilterNode. A TableFilterNode creates at most one layer of child * TableFilterNode. A TableFilterNode creates at most one layer of child
@ -67,25 +90,4 @@ class TableFilterChildren extends FilterNode.Children {
return new Node[]{this.copyNode(key)}; return new Node[]{this.copyNode(key)};
} }
/**
* Creates a children (child factory) object for a node wrapped in a
* TableFilterNode. A TableFilterNode creates at most one layer of child
* nodes for the node it wraps. It is designed to be used for nodes
* displayed in Autopsy table views.
*
*
* @param wrappedNode The node wrapped by the TableFilterNode.
* @param createChildren True if a children (child factory) object should be
* created for the wrapped node.
*
* @return A children (child factory) object for a node wrapped by a
* TableFilterNode.
*/
public static Children createInstance(Node wrappedNode, boolean createChildren) {
if (createChildren) {
return new TableFilterChildren(wrappedNode);
} else {
return Children.LEAF;
}
}
} }

View File

@ -22,12 +22,13 @@ import org.openide.nodes.FilterNode;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups; import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
/** /**
* A filter node that creates at most one layer of child nodes for the node it * A filter node that creates at most one layer of child nodes for the node it
* wraps. It is designed to be used for nodes displayed in Autopsy table views. * wraps. It is designed to be used in the results view to ensure the individual
* This ensures that the table view for the node will not recursively display * viewers display only the first layer of child nodes.
* child nodes and display only the first layer of child nodes.
*/ */
public class TableFilterNode extends FilterNode { public class TableFilterNode extends FilterNode {
@ -36,37 +37,40 @@ public class TableFilterNode extends FilterNode {
/** /**
* Constructs a filter node that creates at most one layer of child nodes * Constructs a filter node that creates at most one layer of child nodes
* for the node it wraps. It is designed to be used for nodes displayed in * for the node it wraps. It is designed to be used in the results view to
* Autopsy table views. * ensure the individual viewers display only the first layer of child
* nodes.
* *
* @param wrappedNode The node to wrap in the filter node. * @param node The node to wrap in the filter node.
* @param createChildren True if a children (child factory) object should be * @param createChildren True if a Children object should be created for the
* created for the wrapped node. * wrapped node.
* The constructor should include column order key. (See getColumnOrderKey)
*/ */
public TableFilterNode(Node wrappedNode, boolean createChildren) { public TableFilterNode(Node node, boolean createChildren) {
super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren) , Lookups.proxy(wrappedNode)); super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node));
this.createChildren = createChildren; this.createChildren = createChildren;
} }
/** /**
* Constructs a filter node that has information about the node's type. * Constructs a filter node that creates at most one layer of child nodes
* for the node it wraps. It is designed to be used in the results view to
* ensure the individual viewers display only the first layer of child
* nodes.
* *
* @param wrappedNode The node to wrap in the filter node. * @param node The node to wrap in the filter node.
* @param createChildren True if a children (child factory) object should be * @param createChildren True if a Children object should be created for the
* created for the wrapped node. * wrapped node.
* @param columnOrderKey A key that represents the type of the original * @param columnOrderKey A key that represents the type of the original
* wrapped node and what is being displayed under that * wrapped node and what is being displayed under that
* node. * node.
*/ */
public TableFilterNode(Node wrappedNode, boolean createChildren, String columnOrderKey) { public TableFilterNode(Node node, boolean createChildren, String columnOrderKey) {
super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren)); super(node, TableFilterChildren.createInstance(node, createChildren));
this.createChildren = createChildren; this.createChildren = createChildren;
this.columnOrderKey = columnOrderKey; this.columnOrderKey = columnOrderKey;
} }
/** /**
* Returns a display name for the wrapped node, for use in the first column * Gets the display name for the wrapped node, for use in the first column
* of an Autopsy table view. * of an Autopsy table view.
* *
* @return The display name. * @return The display name.
@ -80,6 +84,41 @@ public class TableFilterNode extends FilterNode {
} }
} }
/**
* Adds information about which child node of this node, if any, should be
* selected. Can be null.
*
* @param selectedChildNodeInfo The child node selection information.
*/
public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) {
/*
* Currently, child selection is only supported for nodes selected in
* the tree view and decorated with a DataResultFilterNode.
*/
if (getOriginal() instanceof DataResultFilterNode) {
((DataResultFilterNode) getOriginal()).setChildNodeSelectionInfo(selectedChildNodeInfo);
}
}
/**
* Gets information about which child node of this node, if any, should be
* selected.
*
* @return The child node selection information, or null if no child should
* be selected.
*/
public NodeSelectionInfo getChildNodeSelectionInfo() {
/*
* Currently, child selection is only supported for nodes selected in
* the tree view and decorated with a DataResultFilterNode.
*/
if (getOriginal() instanceof DataResultFilterNode) {
return ((DataResultFilterNode) getOriginal()).getChildNodeSelectionInfo();
} else {
return null;
}
}
/** /**
* @return the column order key, which allows custom column ordering to be * @return the column order key, which allows custom column ordering to be
* written into a properties file and be reloaded for future use in * written into a properties file and be reloaded for future use in
@ -90,4 +129,5 @@ public class TableFilterNode extends FilterNode {
String getColumnOrderKey() { String getColumnOrderKey() {
return columnOrderKey; return columnOrderKey;
} }
} }

View File

@ -40,7 +40,7 @@ public abstract class DisplayableItemNode extends AbstractNode {
* An item type shared by DisplayableItemNodes that can be the parents of * An item type shared by DisplayableItemNodes that can be the parents of
* file nodes. * file nodes.
*/ */
final static String FILE_PARENT_NODE_KEY = "orgsleuthkitautopsydatamodel" + "FileTypeParentNode"; static final String FILE_PARENT_NODE_KEY = "orgsleuthkitautopsydatamodel" + "FileTypeParentNode";
/** /**
* Gets the file, if any, linked to an artifact via a TSK_PATH_ID attribute * Gets the file, if any, linked to an artifact via a TSK_PATH_ID attribute
@ -62,6 +62,8 @@ public abstract class DisplayableItemNode extends AbstractNode {
return null; return null;
} }
private NodeSelectionInfo selectedChildNodeInfo;
/** /**
* Constructs a node that is eligible for display in the tree view or * Constructs a node that is eligible for display in the tree view or
* results view. Capabilitites include accepting a * results view. Capabilitites include accepting a
@ -102,7 +104,8 @@ public abstract class DisplayableItemNode extends AbstractNode {
public abstract <T> T accept(DisplayableItemNodeVisitor<T> visitor); public abstract <T> T accept(DisplayableItemNodeVisitor<T> visitor);
/** /**
* Indicates whether or not the node is a leaf node. * Indicates whether or not the node is capable of having child nodes.
* Should only return true if the node is ALWAYS a leaf node.
* *
* @return True or false. * @return True or false.
*/ */
@ -115,4 +118,25 @@ public abstract class DisplayableItemNode extends AbstractNode {
*/ */
public abstract String getItemType(); public abstract String getItemType();
/**
* Adds information about which child node of this node, if any, should be
* selected. Can be null.
*
* @param selectedChildNodeInfo The child node selection information.
*/
public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) {
this.selectedChildNodeInfo = selectedChildNodeInfo;
}
/**
* Gets information about which child node of this node, if any, should be
* selected.
*
* @return The child node selection information, or null if no child should
* be selected.
*/
public NodeSelectionInfo getChildNodeSelectionInfo() {
return selectedChildNodeInfo;
}
} }

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.datamodel; package org.sleuthkit.autopsy.datamodel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -30,7 +31,6 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction;
@ -42,32 +42,84 @@ import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
/** /**
* This class is the Node for an AbstractFile. It may have derived files * A node for representing an AbstractFile. It may have derived file node
* children. * children.
*/ */
public class FileNode extends AbstractFsContentNode<AbstractFile> { public class FileNode extends AbstractFsContentNode<AbstractFile> {
private static final Logger LOGGER = Logger.getLogger(FileNode.class.getName()); /**
* Gets the path to the icon file that should be used to visually represent
* an AbstractFile, using the file name extension to select the icon.
*
* @param file An AbstractFile.
*
* @return An icon file path.
*/
static String getIconForFileType(AbstractFile file) {
String ext = file.getNameExtension();
if (StringUtils.isBlank(ext)) {
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
} else {
ext = "." + ext;
}
if (ImageUtils.isImageThumbnailSupported(file)
|| FileTypeExtensions.getImageExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/image-file.png"; //NON-NLS
}
if (FileTypeExtensions.getVideoExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/video-file.png"; //NON-NLS
}
if (FileTypeExtensions.getAudioExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/audio-file.png"; //NON-NLS
}
if (FileTypeExtensions.getDocumentExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/doc-file.png"; //NON-NLS
}
if (FileTypeExtensions.getExecutableExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/exe-file.png"; //NON-NLS
}
if (FileTypeExtensions.getTextExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/text-file.png"; //NON-NLS
}
if (FileTypeExtensions.getWebExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/web-file.png"; //NON-NLS
}
if (FileTypeExtensions.getPDFExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/pdf-file.png"; //NON-NLS
}
if (FileTypeExtensions.getArchiveExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/archive-file.png"; //NON-NLS
}
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
}
/** /**
* Constructor * Constructs a node for representing an AbstractFile. It may have derived
* file node children.
* *
* @param file underlying Content * @param file An AbstractFile object.
*/ */
public FileNode(AbstractFile file) { public FileNode(AbstractFile file) {
this(file, true); this(file, true);
setIcon(file); setIcon(file);
} }
/**
* Constructs a node for representing an AbstractFile. It may have derived
* file node children.
*
* @param file An AbstractFile object.
* @param directoryBrowseMode
*/
public FileNode(AbstractFile file, boolean directoryBrowseMode) { public FileNode(AbstractFile file, boolean directoryBrowseMode) {
super(file, directoryBrowseMode); super(file, directoryBrowseMode);
setIcon(file); setIcon(file);
} }
/*
* Sets the icon for the node, based on properties of the AbstractFile.
*/
private void setIcon(AbstractFile file) { private void setIcon(AbstractFile file) {
// set name, display name, and icon
if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) { if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) {
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-icon-16.png"); //NON-NLS this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-icon-16.png"); //NON-NLS
@ -79,110 +131,92 @@ public class FileNode extends AbstractFsContentNode<AbstractFile> {
} }
} }
/**
* Gets the set of actions that are associated with this node. This set is
* used to construct the context menu for the node.
*
* @param context Whether to find actions for context meaning or for the
* node itself.
*
* @return An array of the actions.
*/
@Override @Override
@NbBundle.Messages({ @NbBundle.Messages({
"FileNode.getActions.viewFileInDir.text=View File in Directory", "FileNode.getActions.viewFileInDir.text=View File in Directory",
"FileNode.getActions.viewInNewWin.text=View in New Window", "FileNode.getActions.viewInNewWin.text=View in New Window",
"FileNode.getActions.openInExtViewer.text=Open in External Viewer", "FileNode.getActions.openInExtViewer.text=Open in External Viewer",
"FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash"}) "FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash"})
public Action[] getActions(boolean popup) { public Action[] getActions(boolean context) {
List<Action> actionsList = new ArrayList<>(); List<Action> actionsList = new ArrayList<>();
for (Action a : super.getActions(true)) { actionsList.addAll(Arrays.asList(super.getActions(true)));
actionsList.add(a);
}
if (!this.getDirectoryBrowseMode()) { if (!this.getDirectoryBrowseMode()) {
actionsList.add(new ViewContextAction(Bundle.FileNode_getActions_viewFileInDir_text(), this)); actionsList.add(new ViewContextAction(Bundle.FileNode_getActions_viewFileInDir_text(), this));
actionsList.add(null); // creates a menu separator actionsList.add(null); // Creates a item separator
} }
actionsList.add(new NewWindowViewAction(Bundle.FileNode_getActions_viewInNewWin_text(), this)); actionsList.add(new NewWindowViewAction(Bundle.FileNode_getActions_viewInNewWin_text(), this));
actionsList.add(new ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this)); actionsList.add(new ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this));
actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent())); actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent()));
actionsList.add(null); // Creates a item separator
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance()); actionsList.add(ExtractAction.getInstance());
actionsList.add(new HashSearchAction(Bundle.FileNode_getActions_searchFilesSameMD5_text(), this)); actionsList.add(new HashSearchAction(Bundle.FileNode_getActions_searchFilesSameMD5_text(), this));
actionsList.add(null); // creates a menu separator actionsList.add(null); // Creates a item separator
actionsList.add(AddContentTagAction.getInstance());
actionsList.add(AddContentTagAction.getInstance());
final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
if(selectedFilesList.size() == 1) { if (1 == selectedFilesList.size()) {
actionsList.add(DeleteFileContentTagAction.getInstance()); actionsList.add(DeleteFileContentTagAction.getInstance());
} }
actionsList.addAll(ContextMenuExtensionPoint.getActions()); actionsList.addAll(ContextMenuExtensionPoint.getActions());
return actionsList.toArray(new Action[actionsList.size()]); return actionsList.toArray(new Action[actionsList.size()]);
} }
/**
* Accepts a ContentNodeVisitor.
*
* @param <T> The type parameter of the Visitor.
* @param visitor The Visitor.
*
* @return An object determied by the type parameter of the Visitor.
*/
@Override @Override
public <T> T accept(ContentNodeVisitor<T> v) { public <T> T accept(ContentNodeVisitor<T> visitor) {
return v.visit(this); return visitor.visit(this);
} }
/**
* Accepts a DisplayableItemNodeVisitor.
*
* @param <T> The type parameter of the Visitor.
* @param visitor The Visitor.
*
* @return An object determied by the type parameter of the Visitor.
*/
@Override @Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) { public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
return v.visit(this); return visitor.visit(this);
}
// Given a file, returns the correct icon for said
// file based off it's extension
static String getIconForFileType(AbstractFile file) {
// Get the name, extension
String ext = file.getNameExtension();
if (StringUtils.isBlank(ext)) {
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
} else {
ext = "." + ext;
}
if (ImageUtils.isImageThumbnailSupported(file)
|| FileTypeExtensions.getImageExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/image-file.png"; //NON-NLS
}
// Videos
if (FileTypeExtensions.getVideoExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/video-file.png"; //NON-NLS
}
// Audio Files
if (FileTypeExtensions.getAudioExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/audio-file.png"; //NON-NLS
}
// Documents
if (FileTypeExtensions.getDocumentExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/doc-file.png"; //NON-NLS
}
// Executables / System Files
if (FileTypeExtensions.getExecutableExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/exe-file.png"; //NON-NLS
}
// Text Files
if (FileTypeExtensions.getTextExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/text-file.png"; //NON-NLS
}
// Web Files
if (FileTypeExtensions.getWebExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/web-file.png"; //NON-NLS
}
// PDFs
if (FileTypeExtensions.getPDFExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/pdf-file.png"; //NON-NLS
}
// Archives
if (FileTypeExtensions.getArchiveExtensions().contains(ext)) {
return "org/sleuthkit/autopsy/images/archive-file.png"; //NON-NLS
}
// Else return the default
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
} }
/**
* Indicates whether or not the node is capable of having child nodes.
* Should only return true if the node is ALWAYS a leaf node.
*
* @return True or false.
*/
@Override @Override
public boolean isLeafTypeNode() { public boolean isLeafTypeNode() {
// This seems wrong, but it also seems that it is never called /*
// because the visitor to figure out if there are children or * A FileNode may have FileNodes for derived files as children.
// not will check if it has children using the Content API */
return true; return false;
} }
/**
* Gets the item type string of the node, suitable for use as a key.
*
* @return A String representing the item type of node.
*/
@Override @Override
public String getItemType() { public String getItemType() {
return getClass().getName(); return getClass().getName();

View File

@ -189,7 +189,7 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode<VirtualDirect
@Override @Override
public boolean isLeafTypeNode() { public boolean isLeafTypeNode() {
return true; return false;
} }
/** /**

View File

@ -53,6 +53,7 @@ import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
import org.sleuthkit.autopsy.datamodel.Reports; import org.sleuthkit.autopsy.datamodel.Reports;
import org.sleuthkit.autopsy.datamodel.SlackFileNode; import org.sleuthkit.autopsy.datamodel.SlackFileNode;
import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
@ -208,6 +209,33 @@ public class DataResultFilterNode extends FilterNode {
return propertySets; return propertySets;
} }
/**
* Adds information about which child node of this node, if any, should be
* selected. Can be null.
*
* @param selectedChildNodeInfo The child node selection information.
*/
public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) {
if (getOriginal() instanceof DisplayableItemNode) {
((DisplayableItemNode) getOriginal()).setChildNodeSelectionInfo(selectedChildNodeInfo);
}
}
/**
* Gets information about which child node of this node, if any, should be
* selected.
*
* @return The child node selection information, or null if no child should
* be selected.
*/
public NodeSelectionInfo getChildNodeSelectionInfo() {
if (getOriginal() instanceof DisplayableItemNode) {
return ((DisplayableItemNode) getOriginal()).getChildNodeSelectionInfo();
} else {
return null;
}
}
/** /**
* This class is used for the creation of all the children for the * This class is used for the creation of all the children for the
* DataResultFilterNode that created in the DataResultFilterNode.java. * DataResultFilterNode that created in the DataResultFilterNode.java.

View File

@ -62,7 +62,7 @@ class DirectoryTreeFilterNode extends FilterNode {
DirectoryTreeFilterNode(Node nodeToWrap, boolean createChildren) { DirectoryTreeFilterNode(Node nodeToWrap, boolean createChildren) {
super(nodeToWrap, super(nodeToWrap,
DirectoryTreeFilterChildren.createInstance(nodeToWrap, createChildren), DirectoryTreeFilterChildren.createInstance(nodeToWrap, createChildren),
new ProxyLookup(Lookups.singleton(new OriginalNode(nodeToWrap)), nodeToWrap.getLookup())); new ProxyLookup(Lookups.singleton(nodeToWrap), nodeToWrap.getLookup()));
} }
/** /**
@ -168,17 +168,27 @@ class DirectoryTreeFilterNode extends FilterNode {
return actions.toArray(new Action[actions.size()]); return actions.toArray(new Action[actions.size()]);
} }
//FIXME: this seems like a big hack -jm /**
public static class OriginalNode { * RJCTODO
*
private final Node original; * @return
*/
OriginalNode(Node original) { @Override
this.original = original; public Node getOriginal() {
return super.getOriginal();
} }
Node getNode() { // //FIXME: this seems like a big hack -jm
return original; // public static class OriginalNode {
} //
} // private final Node original;
//
// OriginalNode(Node original) {
// this.original = original;
// }
//
// Node getNode() {
// return original;
// }
// }
} }

View File

@ -613,7 +613,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} }
} }
@NbBundle.Messages("DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.")
/** /**
* Event handler to run when selection changed * Event handler to run when selection changed
* *
@ -622,10 +621,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* @param oldNodes * @param oldNodes
* @param newNodes * @param newNodes
*/ */
@NbBundle.Messages("DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.")
private void respondSelection(final Node[] oldNodes, final Node[] newNodes) { private void respondSelection(final Node[] oldNodes, final Node[] newNodes) {
if (!Case.isCaseOpen()) { if (!Case.isCaseOpen()) {
//handle in-between condition when case is being closed
//and legacy selection events are pumped
return; return;
} }
@ -634,25 +632,15 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
// selection-change event is processed. // selection-change event is processed.
//TODO find a different way to refresh data result viewer, scheduling this //TODO find a different way to refresh data result viewer, scheduling this
//to EDT breaks loading of nodes in the background //to EDT breaks loading of nodes in the background
EventQueue.invokeLater(new Runnable() { EventQueue.invokeLater(() -> {
@Override
public void run() {
// change the cursor to "waiting cursor" for this operation // change the cursor to "waiting cursor" for this operation
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode(); Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
if (treeNode != null) { if (treeNode != null) {
DirectoryTreeFilterNode.OriginalNode origin = treeNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class); Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal();
if (origin == null) {
return;
}
Node originNode = origin.getNode();
//set node, wrap in filter node first to filter out children //set node, wrap in filter node first to filter out children
Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
// Create a TableFilterNode with knowledge of the node's type to allow for column order settings // Create a TableFilterNode with knowledge of the node's type to allow for column order settings
if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) { if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) {
//Special case for when File Type Identification has not yet been run and //Special case for when File Type Identification has not yet been run and
@ -664,7 +652,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} else { } else {
dataResult.setNode(new TableFilterNode(drfn, true)); dataResult.setNode(new TableFilterNode(drfn, true));
} }
String displayName = ""; String displayName = "";
Content content = originNode.getLookup().lookup(Content.class); Content content = originNode.getLookup().lookup(Content.class);
if (content != null) { if (content != null) {
@ -678,7 +665,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} }
dataResult.setPath(displayName); dataResult.setPath(displayName);
} }
// set the directory listing to be active // set the directory listing to be active
if (oldNodes != null && newNodes != null if (oldNodes != null && newNodes != null
&& (oldNodes.length == newNodes.length)) { && (oldNodes.length == newNodes.length)) {
@ -693,7 +679,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} finally { } finally {
setCursor(null); setCursor(null);
} }
}
}); });
// update the back and forward list // update the back and forward list
@ -774,29 +759,16 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private void refreshDataSourceTree() { private void refreshDataSourceTree() {
Node selectedNode = getSelectedNode(); Node selectedNode = getSelectedNode();
final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext());
Children rootChildren = em.getRootContext().getChildren(); Children rootChildren = em.getRootContext().getChildren();
Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME); Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME);
if (dataSourcesFilterNode == null) { if (dataSourcesFilterNode == null) {
LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS
return; return;
} }
DirectoryTreeFilterNode.OriginalNode imagesNodeOrig = dataSourcesFilterNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class); Node dataSourcesNode = ((DirectoryTreeFilterNode) dataSourcesFilterNode).getOriginal();
DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) dataSourcesNode.getChildren();
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(); contentRootChildren.refreshContentKeys();
//final TreeView tree = getTree();
//tree.expandNode(imagesNode);
setSelectedNode(selectedPath, DataSourcesNode.NAME); setSelectedNode(selectedPath, DataSourcesNode.NAME);
} }
/** /**
@ -963,6 +935,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
}); });
} }
// RJCTODO: What is this all about?
@Override @Override
public void viewArtifactContent(BlackboardArtifact art) { public void viewArtifactContent(BlackboardArtifact art) {
new ViewContextAction( new ViewContextAction(

View File

@ -24,49 +24,46 @@ import java.beans.PropertyVetoException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.SwingWorker;
import org.openide.nodes.AbstractNode; import org.openide.nodes.AbstractNode;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.TreeView; import org.openide.explorer.view.TreeView;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo;
import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.autopsy.datamodel.RootContentChildren;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.FileSystem; import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.VolumeSystem; import org.sleuthkit.datamodel.VolumeSystem;
/** /**
* An action that displays the context for some content by expanding the data * An action that displays the context for some content by expanding the data
* sources branch of the tree view to the level of the parent of the content, * sources 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 * selecting the parent in the tree view, then selecting the content in the
* results view. This is commonly called "view file in directory." * results view.
*/ */
public final class ViewContextAction extends AbstractAction { public final class ViewContextAction extends AbstractAction {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final long ROOT_DIR_META_ADDR = 5L;
private static final Logger logger = Logger.getLogger(ViewContextAction.class.getName()); private static final Logger logger = Logger.getLogger(ViewContextAction.class.getName());
private Content content; private final Content content;
/** /**
* An action that displays the context for the source content of an artifact * An action that displays the context for the source content of an artifact
* by expanding the data sources branch of the tree view to the level of the * by expanding the data sources branch of the tree view to the level of the
* parent of the content, selecting the parent in the tree view, then * parent of the content, selecting the parent in the tree view, then
* selecting the content in the results view. This is commonly called "view * selecting the content in the results view.
* file in directory."
* *
* @param displayName The display name for the action. * @param displayName The display name for the action.
* @param artifactNode The artifact node for the artifact. * @param artifactNode The artifact node for the artifact.
@ -74,14 +71,20 @@ public final class ViewContextAction extends AbstractAction {
public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) { public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) {
super(displayName); super(displayName);
this.content = artifactNode.getLookup().lookup(Content.class); this.content = artifactNode.getLookup().lookup(Content.class);
if (content instanceof AbstractFile) {
AbstractFile file = (AbstractFile) content;
if ((TskData.FileKnown.KNOWN == file.getKnown() && UserPreferences.hideKnownFilesInDataSourcesTree())
|| (TskData.TSK_DB_FILES_TYPE_ENUM.SLACK == file.getType() && UserPreferences.hideSlackFilesInDataSourcesTree())) {
this.setEnabled(false);
}
}
} }
/** /**
* An action that displays the context for some file system content by * An action that displays the context for some file system content by
* expanding the data sources branch of the tree view to the level of the * expanding the data sources branch of the tree view to the level of the
* parent of the content, selecting the parent in the tree view, then * parent of the content, selecting the parent in the tree view, then
* selecting the content in the results view. This is commonly called "view * selecting the content in the results view.
* file in directory."
* *
* @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 * @param fileSystemContentNode The file system content node for the
@ -96,8 +99,7 @@ public final class ViewContextAction extends AbstractAction {
* An action that displays the context for some content by expanding the * An action that displays the context for some content by expanding the
* data sources branch of the tree view to the level of the parent of the * data sources 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, selecting the parent in the tree view, then selecting the
* content in the results view. This is commonly called "view file in * content in the results view.
* directory."
* *
* @param displayName The display name for the action. * @param displayName The display name for the action.
* @param content The content. * @param content The content.
@ -108,259 +110,153 @@ public final class ViewContextAction extends AbstractAction {
} }
/** /**
* Invoked when the action occurs. * Displays the context for some content by expanding the data sources
* 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.
* *
* @param event * @param event The action event.
*/ */
@Override @Override
public void actionPerformed(ActionEvent event) { public void actionPerformed(ActionEvent event) {
/*
* Ensure that the action manipulates the tree view in the GUI event
* thread.
*/
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
/* /*
* Create a flattened copy of the branch of the tree view that leads * Get the "Data Sources" node from the tree view.
* to the specified content, starting with the data source of the
* content, which actually could be the content itself. Note that
* the "dummy" root node used to create the branch needs to be
* wrapped in a DirectoryTreeFilterNode so that its child nodes will
* also be wrapped in DirectoryTreeFilterNodes via
* DirectoryTreeFilterNodeChildren. Otherwise, the display names of
* the nodes in the branch will not have child node counts, and will
* not match the display names of the corresponding nodes in the
* actual tree view.
*/
LeafToRootContentBranchVisitor branchBuilder = new LeafToRootContentBranchVisitor();
List<Content> branch = content.accept(branchBuilder);
Collections.reverse(branch);
Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(branch)), true);
Children branchChildren = dummyRootNode.getChildren();
/*
* Use the flattened copy of the branch of the tree view that leads
* to the specified content to do a depth-first search for the
* parent node of the content in the actual tree view, starting from
* the tree view's "Data Sources" node.
*/ */
DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance();
TreeView treeView = treeViewTopComponent.getTree();
ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager();
Node treeViewRootNode = treeViewExplorerMgr.getRootContext(); Node parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME);
Children treeViewRootNodeChildren = treeViewRootNode.getChildren();
Node dataSourcesNode = treeViewRootNodeChildren.findChild(DataSourcesNode.NAME); /*
Children currentTreeViewNodeChildren = dataSourcesNode.getChildren(); * Get the parent content for the content to be selected in the
Node contentParentNode = null; * results view. If the parent content is null, then the specified
for (int i = 0; i < branchChildren.getNodesCount(); i++) { * content is a data source, and the parent tree view node is the
Node currentBranchNode = branchChildren.getNodeAt(i); * "Data Sources" node. Otherwise, the tree view needs to be
for (int j = 0; j < currentTreeViewNodeChildren.getNodesCount(); j++) { * searched to find the parent treeview node.
Node currentTreeViewNode = currentTreeViewNodeChildren.getNodeAt(j); */
if (currentBranchNode.getDisplayName().equals(currentTreeViewNode.getDisplayName())) { Content parentContent = null;
contentParentNode = currentTreeViewNode; try {
treeView.expandNode(contentParentNode); parentContent = content.getParent();
currentTreeViewNodeChildren = currentTreeViewNode.getChildren(); } catch (TskCoreException ex) {
// RJCTODO: Pop up
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
return;
}
if (null != parentContent) {
/*
* Get an ordered list of the ancestors of the specified
* content, starting with its data source.
*
*/
AncestorVisitor ancestorVisitor = new AncestorVisitor();
List<Content> contentBranch = parentContent.accept(ancestorVisitor);
Collections.reverse(contentBranch);
/**
* Convert the list of ancestors into a list of tree nodes.
*
* IMPORTANT: The "dummy" root node used to create this single
* layer of children needs to be wrapped in a
* DirectoryTreeFilterNode so that its child nodes will also be
* wrapped in DirectoryTreeFilterNodes, via
* DirectoryTreeFilterNodeChildren. Otherwise, the display names
* of the nodes in the branch will not have child node counts
* and will not match the display names of the corresponding
* nodes in the actual tree view.
*/
Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true);
Children ancestorChildren = dummyRootNode.getChildren();
/*
* Search the tree for the parent node. Note that this algorithm
* simply discards "extra" ancestor nodes not shown in the tree,
* such as the root directory of the file system for file system
* content.
*/
Children treeNodeChildren = parentTreeViewNode.getChildren();
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);
if (ancestorNode.getDisplayName().equals(treeNode.getDisplayName())) {
parentTreeViewNode = treeNode;
treeNodeChildren = treeNode.getChildren();
break; break;
} }
} }
} }
if (null == contentParentNode) {
logger.log(Level.SEVERE, "Failed to find the parent node of Content node to be selected in the results view in the tree view"); //NON-NLS
return;
}
if (branchChildren.getNodesCount() != 1) {
DirectoryTreeFilterNode contentParentFilterNode = (DirectoryTreeFilterNode) contentParentNode;
DirectoryTreeFilterNode.OriginalNode decoratedNodeHolder = contentParentFilterNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class);
if (decoratedNodeHolder == null) {
logger.log(Level.SEVERE, "Failed to extract decorated node holder of the DirectoryTreeFilterNode decorator of the parent node of Content node to be selected in the results view"); //NON-NLS
return;
}
Node originNode = decoratedNodeHolder.getNode();
} }
/* /*
* Select the parent node of content node to be selected in the * Set the child selection info of the parent tree node, then select
* results view in the tree view. * 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));
TreeView treeView = treeViewTopComponent.getTree();
treeView.expandNode(parentTreeViewNode);
try { try {
treeView.expandNode(contentParentNode); // RJCTODO: Pop up
treeViewExplorerMgr.setExploredContextAndSelection(contentParentNode, new Node[]{contentParentNode}); treeViewExplorerMgr.setExploredContextAndSelection(parentTreeViewNode, new Node[]{parentTreeViewNode});
} catch (PropertyVetoException ex) { } catch (PropertyVetoException ex) {
logger.log(Level.SEVERE, "Failed to select the parent node of Content node to be selected in the results view in the tree view", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to select the parent node in the tree view", ex); //NON-NLS
return;
}
if (branchChildren.getNodesCount() != 1) {
/*
* The target content is not a data source, queue another task
* for the GUI event thread to get the current root node of the
* result view top component, get the display name of the
* content node to be selected in the results view, and then
* dispatch a ResultViewNodeSelectionTask (a SwingWorker).
*
* TODO (JIRA-1655): This participates in a race condition.
*/
// EventQueue.invokeLater(() -> {
// DataResultTopComponent resultViewTopComponent = treeViewTopComponent.getDirectoryListing();
// Node currentRootNodeOfResultsView = resultViewTopComponent.getRootNode();
// Node contentNode = content.accept(new RootContentChildren.CreateSleuthkitNodeVisitor());
// new ResultViewNodeSelectionTask(resultViewTopComponent, contentNode.getName(), currentRootNodeOfResultsView).execute();
// });
} }
}); });
} }
/** /**
* Makes a clone of this action. * A ContentVisitor that returns a list of content objects by starting with
* * a given content and following its chain of ancestors to the root content
* @return The cloned action. * of the lineage.
*
* @throws CloneNotSupportedException Exception thrown if there is a problem
* creating the clone.
*/ */
@Override private static class AncestorVisitor extends ContentVisitor.Default<List<Content>> {
public Object clone() throws CloneNotSupportedException {
ViewContextAction clone = (ViewContextAction) super.clone();
clone.setContent(this.content);
return clone;
}
/** List<Content> lineage = new ArrayList<>();
* Sets the content object associated with the action.
*
* @param content A content object.
*/
private void setContent(Content content) {
this.content = content;
}
/**
* 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 ResultViewNodeSelectionTask extends SwingWorker<Node[], Integer> {
DataResultTopComponent resultViewTopComponent;
String nameOfNodeToSelect;
Node currentRootNodeOfResultsView;
ResultViewNodeSelectionTask(DataResultTopComponent resultViewTopComponent, String nameToSelect, Node currentRootNodeOfResultsView) {
this.resultViewTopComponent = resultViewTopComponent;
this.nameOfNodeToSelect = nameToSelect;
this.currentRootNodeOfResultsView = currentRootNodeOfResultsView;
}
@Override @Override
protected Node[] doInBackground() throws Exception { protected List<Content> defaultVisit(Content content) {
/* lineage.add(content);
* Return all of the child nodes of the current root node of the Content parent = null;
* results view. Note that calls to Children.getNodes(true) block
* until all child nodes have been created, regardless of whether
* they are created lazily). There are two ideas here: 1) avoid
* getting a proxy "wait" node, and 2) do this in the background to
* avoid monopolizing the EDT for this potentially lengthy
* operation.
*
* RJCTODO: Is this all true? What if the user selects another node
* in the tree while this is going on?
*/
return currentRootNodeOfResultsView.getChildren().getNodes(true);
}
@Override
protected void done() {
Node[] nodesDisplayedInResultsView;
try { try {
nodesDisplayedInResultsView = get(); parent = content.getParent();
} catch (InterruptedException | CancellationException | ExecutionException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "ResultViewNodeSelectionTask failed to get nodes displayed in results view.", ex); //NON-NLS logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
return;
} }
return parent == null ? lineage : parent.accept(this);
// 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 (resultViewTopComponent.getRootNode().equals(currentRootNodeOfResultsView) == 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 : nodesDisplayedInResultsView) {
if (nameOfNodeToSelect.equals(node.getName())) {
resultViewTopComponent.requestActive();
resultViewTopComponent.setSelectedNodes(new Node[]{node});
DirectoryTreeTopComponent.getDefault().fireViewerComplete();
break;
}
}
}
}
/**
* A ReverseHierarchyVisitor is a ContentVisitor that returns a list of
* content objects by starting with a given content and following its chain
* of ancestors to the root content of the hierarchy.
*/
private class LeafToRootContentBranchVisitor extends ContentVisitor.Default<List<Content>> {
List<Content> hierarchy = new ArrayList<>();
@Override @Override
public List<Content> visit(VolumeSystem volumeSystem) { public List<Content> visit(VolumeSystem volumeSystem) {
/* /*
* Volume systems are not shown in the tree view. * Volume systems are not shown in the tree view. This is not
* strictly necesssary given the algorithm above, but it is a simple
* optimization.
*/ */
return visitParentButDontAddMe(volumeSystem); return skipToParent(volumeSystem);
} }
@Override @Override
public List<Content> visit(FileSystem fileSystem) { public List<Content> visit(FileSystem fileSystem) {
/* /*
* File systems are not shown in the tree view. * File systems are not shown in the tree view. This is not strictly
* necesssary given the algorithm above, but it is a simple
* optimization.
*/ */
return visitParentButDontAddMe(fileSystem); return skipToParent(fileSystem);
} }
@Override private List<Content> skipToParent(Content content) {
public List<Content> visit(Directory directory) {
/*
* File system root directories are not shown in the tree view.
*/
if (ROOT_DIR_META_ADDR == directory.getMetaAddr()) {
return visitParentButDontAddMe(directory);
} else {
return defaultVisit(directory);
}
}
@Override
protected List<Content> defaultVisit(Content content) {
hierarchy.add(content);
Content parent = null; Content parent = null;
try { try {
parent = content.getParent(); parent = content.getParent();
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
} }
return parent == null ? hierarchy : parent.accept(this); return parent == null ? lineage : parent.accept(this);
} }
private List<Content> visitParentButDontAddMe(Content content) {
Content parent = null;
try {
parent = content.getParent();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
}
return parent == null ? hierarchy : parent.accept(this);
} }
} }
}