mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-13 00:16:16 +00:00
ViewContextAction fixes
This commit is contained in:
parent
b31384b025
commit
40b51e77e1
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@ import java.awt.Graphics;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -36,6 +37,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.stream.Stream;
|
||||
import javax.swing.JTable;
|
||||
@ -47,7 +49,6 @@ import javax.swing.event.TableColumnModelEvent;
|
||||
import javax.swing.event.TableColumnModelListener;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.netbeans.swing.etable.ETableColumn;
|
||||
import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
|
||||
import org.netbeans.swing.outline.DefaultOutlineModel;
|
||||
@ -64,20 +65,22 @@ import org.openide.nodes.NodeMemberEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbPreferences;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
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
|
||||
* service provider when DataResultViewers can be made compatible with node
|
||||
* multiple selection actions.
|
||||
* TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
|
||||
* restore implementation of DataResultViewerTable as a DataResultViewer service
|
||||
* provider.
|
||||
*/
|
||||
//@ServiceProvider(service = DataResultViewer.class)
|
||||
public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
|
||||
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
|
||||
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
|
||||
@NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...")
|
||||
@ -234,12 +237,26 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
oldNode.removeNodeListener(pleasewaitNodeListener);
|
||||
}
|
||||
|
||||
// if there's no selection node, do nothing
|
||||
if (hasChildren) {
|
||||
Node root = selectedNode;
|
||||
currentRoot = selectedNode;
|
||||
pleasewaitNodeListener.reset();
|
||||
root.addNodeListener(pleasewaitNodeListener);
|
||||
setupTable(root);
|
||||
currentRoot.addNodeListener(pleasewaitNodeListener);
|
||||
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 {
|
||||
Node emptyNode = new AbstractNode(Children.LEAF);
|
||||
em.setRootContext(emptyNode); // make empty node
|
||||
@ -258,11 +275,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
* @param root The parent Node of the ContentNodes
|
||||
*/
|
||||
private void setupTable(final Node root) {
|
||||
|
||||
em.setRootContext(root);
|
||||
|
||||
currentRoot = root;
|
||||
List<Node.Property<?>> props = loadColumnOrder();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* allow the first property column to be moved.
|
||||
*/
|
||||
List<Node.Property<?>> props = loadColumnOrder();
|
||||
if (props.isEmpty() == false) {
|
||||
Node.Property<?> prop = props.remove(0);
|
||||
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
|
||||
}
|
||||
|
||||
/*
|
||||
* show the horizontal scroll panel and show all the content & header If
|
||||
* 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);
|
||||
|
||||
assignColumns(props);
|
||||
|
||||
setColumnWidths();
|
||||
|
||||
loadColumnSorting();
|
||||
}
|
||||
|
||||
|
@ -48,22 +48,21 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
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)
|
||||
* 1000 images at a time. This works whether or not the underlying content nodes
|
||||
* are being lazy loaded or not.
|
||||
* The paging is intended to reduce memory footprint by load only up to
|
||||
* (currently) 1000 images at a time. This works whether or not the underlying
|
||||
* 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)
|
||||
final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
|
||||
//flag to keep track if images are being loaded
|
||||
private int curPage;
|
||||
private int totalPages;
|
||||
private int curPageImages;
|
||||
@ -71,8 +70,10 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
private final PageUpdater pageUpdater = new PageUpdater();
|
||||
|
||||
/**
|
||||
* Creates a DataResultViewerThumbnail object that is compatible with node
|
||||
* multiple selection actions.
|
||||
* Constructs a thumbnail viewer for the results view, with paging support,
|
||||
* that is compatible with node multiple selection actions.
|
||||
*
|
||||
* @param explorerManager The shared ExplorerManager for the result viewers.
|
||||
*/
|
||||
DataResultViewerThumbnail(ExplorerManager explorerManager) {
|
||||
super(explorerManager);
|
||||
@ -80,8 +81,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DataResultViewerThumbnail object that is NOT compatible with
|
||||
* node multiple selection actions.
|
||||
* Constructs a thumbnail viewer for the results view, with paging support,
|
||||
* that is NOT compatible with node multiple selection actions.
|
||||
*/
|
||||
DataResultViewerThumbnail() {
|
||||
initialize();
|
||||
@ -93,7 +94,6 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
})
|
||||
private void initialize() {
|
||||
initComponents();
|
||||
|
||||
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
|
||||
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(
|
||||
@ -311,20 +311,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
|
||||
@Override
|
||||
public void setNode(Node givenNode) {
|
||||
// change the cursor to "waiting cursor" for this operation
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
try {
|
||||
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);
|
||||
|
||||
final Node root = new AbstractNode(childNode);
|
||||
pageUpdater.setRoot(root);
|
||||
root.addNodeListener(pageUpdater);
|
||||
em.setRootContext(root);
|
||||
} else {
|
||||
Node emptyNode = new AbstractNode(Children.LEAF);
|
||||
em.setRootContext(emptyNode); // make empty node
|
||||
|
||||
em.setRootContext(emptyNode);
|
||||
iconView.setBackground(Color.BLACK);
|
||||
}
|
||||
} finally {
|
||||
@ -349,21 +351,18 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
this.curPage = -1;
|
||||
curPageImages = 0;
|
||||
updateControls();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearComponent() {
|
||||
this.iconView.removeAll();
|
||||
this.iconView = null;
|
||||
|
||||
super.clearComponent();
|
||||
}
|
||||
|
||||
private void nextPage() {
|
||||
if (curPage < totalPages) {
|
||||
curPage++;
|
||||
|
||||
switchPage();
|
||||
}
|
||||
}
|
||||
@ -371,7 +370,6 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
private void previousPage() {
|
||||
if (curPage > 1) {
|
||||
curPage--;
|
||||
|
||||
switchPage();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,12 +23,35 @@ import org.openide.nodes.FilterNode;
|
||||
import org.openide.nodes.Node;
|
||||
|
||||
/**
|
||||
* A children (child factory) implementation for 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.
|
||||
* A Children implementation 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.
|
||||
*/
|
||||
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
|
||||
* 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)};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,13 @@ import org.openide.nodes.FilterNode;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
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
|
||||
* wraps. It is designed to be used for nodes displayed in Autopsy table views.
|
||||
* This ensures that the table view for the node will not recursively display
|
||||
* child nodes and display only the first layer of child nodes.
|
||||
* wraps. It is designed to be used in the results view to ensure the individual
|
||||
* viewers display only the first layer of child nodes.
|
||||
*/
|
||||
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
|
||||
* for the node it wraps. It is designed to be used for nodes displayed in
|
||||
* Autopsy table views.
|
||||
* 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 createChildren True if a children (child factory) object should be
|
||||
* created for the wrapped node.
|
||||
* The constructor should include column order key. (See getColumnOrderKey)
|
||||
* @param node The node to wrap in the filter node.
|
||||
* @param createChildren True if a Children object should be created for the
|
||||
* wrapped node.
|
||||
*/
|
||||
public TableFilterNode(Node wrappedNode, boolean createChildren) {
|
||||
super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren) , Lookups.proxy(wrappedNode));
|
||||
public TableFilterNode(Node node, boolean createChildren) {
|
||||
super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node));
|
||||
this.createChildren = createChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a filter node that has information about the node's type.
|
||||
*
|
||||
* @param wrappedNode The node to wrap in the filter node.
|
||||
* @param createChildren True if a children (child factory) object should be
|
||||
* created for the wrapped node.
|
||||
* 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 node The node to wrap in the filter node.
|
||||
* @param createChildren True if a Children object should be created for the
|
||||
* wrapped node.
|
||||
* @param columnOrderKey A key that represents the type of the original
|
||||
* wrapped node and what is being displayed under that
|
||||
* node.
|
||||
*/
|
||||
public TableFilterNode(Node wrappedNode, boolean createChildren, String columnOrderKey) {
|
||||
super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren));
|
||||
public TableFilterNode(Node node, boolean createChildren, String columnOrderKey) {
|
||||
super(node, TableFilterChildren.createInstance(node, createChildren));
|
||||
this.createChildren = createChildren;
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
* written into a properties file and be reloaded for future use in
|
||||
@ -90,4 +129,5 @@ public class TableFilterNode extends FilterNode {
|
||||
String getColumnOrderKey() {
|
||||
return columnOrderKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
||||
* An item type shared by DisplayableItemNodes that can be the parents of
|
||||
* 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
|
||||
@ -62,6 +62,8 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
private NodeSelectionInfo selectedChildNodeInfo;
|
||||
|
||||
/**
|
||||
* Constructs a node that is eligible for display in the tree view or
|
||||
* results view. Capabilitites include accepting a
|
||||
@ -102,7 +104,8 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
||||
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.
|
||||
*/
|
||||
@ -115,4 +118,25 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -30,7 +31,6 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
|
||||
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
|
||||
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
|
||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class FileNode extends AbstractFsContentNode<AbstractFile> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(FileNode.class.getName());
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* 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 underlying Content
|
||||
* @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
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a node for representing an AbstractFile. It may have derived
|
||||
* file node children.
|
||||
*
|
||||
* @param file An AbstractFile object.
|
||||
*/
|
||||
public FileNode(AbstractFile file) {
|
||||
this(file, true);
|
||||
|
||||
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) {
|
||||
super(file, directoryBrowseMode);
|
||||
|
||||
setIcon(file);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the icon for the node, based on properties of the AbstractFile.
|
||||
*/
|
||||
private void setIcon(AbstractFile file) {
|
||||
// set name, display name, and icon
|
||||
if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
|
||||
if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) {
|
||||
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
|
||||
@NbBundle.Messages({
|
||||
"FileNode.getActions.viewFileInDir.text=View File in Directory",
|
||||
"FileNode.getActions.viewInNewWin.text=View in New Window",
|
||||
"FileNode.getActions.openInExtViewer.text=Open in External Viewer",
|
||||
"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<>();
|
||||
for (Action a : super.getActions(true)) {
|
||||
actionsList.add(a);
|
||||
}
|
||||
actionsList.addAll(Arrays.asList(super.getActions(true)));
|
||||
if (!this.getDirectoryBrowseMode()) {
|
||||
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 ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this));
|
||||
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(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());
|
||||
|
||||
final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
if(selectedFilesList.size() == 1) {
|
||||
if (1 == selectedFilesList.size()) {
|
||||
actionsList.add(DeleteFileContentTagAction.getInstance());
|
||||
}
|
||||
|
||||
actionsList.addAll(ContextMenuExtensionPoint.getActions());
|
||||
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
|
||||
public <T> T accept(ContentNodeVisitor<T> v) {
|
||||
return v.visit(this);
|
||||
public <T> T accept(ContentNodeVisitor<T> visitor) {
|
||||
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
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
|
||||
return v.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
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
// not will check if it has children using the Content API
|
||||
return true;
|
||||
/*
|
||||
* A FileNode may have FileNodes for derived files as children.
|
||||
*/
|
||||
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
|
||||
public String getItemType() {
|
||||
return getClass().getName();
|
||||
|
@ -189,7 +189,7 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode<VirtualDirect
|
||||
|
||||
@Override
|
||||
public boolean isLeafTypeNode() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +53,7 @@ import org.sleuthkit.autopsy.datamodel.FileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
|
||||
import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.LocalFileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
|
||||
import org.sleuthkit.autopsy.datamodel.Reports;
|
||||
import org.sleuthkit.autopsy.datamodel.SlackFileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
|
||||
@ -208,6 +209,33 @@ public class DataResultFilterNode extends FilterNode {
|
||||
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
|
||||
* DataResultFilterNode that created in the DataResultFilterNode.java.
|
||||
|
@ -62,7 +62,7 @@ class DirectoryTreeFilterNode extends FilterNode {
|
||||
DirectoryTreeFilterNode(Node nodeToWrap, boolean createChildren) {
|
||||
super(nodeToWrap,
|
||||
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()]);
|
||||
}
|
||||
|
||||
//FIXME: this seems like a big hack -jm
|
||||
public static class OriginalNode {
|
||||
|
||||
private final Node original;
|
||||
|
||||
OriginalNode(Node original) {
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
Node getNode() {
|
||||
return original;
|
||||
}
|
||||
/**
|
||||
* RJCTODO
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Node getOriginal() {
|
||||
return super.getOriginal();
|
||||
}
|
||||
|
||||
// //FIXME: this seems like a big hack -jm
|
||||
// public static class OriginalNode {
|
||||
//
|
||||
// private final Node original;
|
||||
//
|
||||
// OriginalNode(Node original) {
|
||||
// this.original = original;
|
||||
// }
|
||||
//
|
||||
// Node getNode() {
|
||||
// return original;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -605,7 +605,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
||||
}
|
||||
} // change in node selection
|
||||
else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
|
||||
respondSelection((Node[]) evt.getOldValue(), (Node[]) evt.getNewValue());
|
||||
respondSelection((Node[]) evt.getOldValue(), (Node[]) evt.getNewValue());
|
||||
} else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
||||
// nothing to do here.
|
||||
// all nodes should be listening for these events and update accordingly.
|
||||
@ -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
|
||||
*
|
||||
@ -622,10 +621,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
||||
* @param oldNodes
|
||||
* @param newNodes
|
||||
*/
|
||||
@NbBundle.Messages("DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.")
|
||||
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;
|
||||
}
|
||||
|
||||
@ -634,65 +632,52 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
||||
// selection-change event is processed.
|
||||
//TODO find a different way to refresh data result viewer, scheduling this
|
||||
//to EDT breaks loading of nodes in the background
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// change the cursor to "waiting cursor" for this operation
|
||||
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
try {
|
||||
|
||||
Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
|
||||
if (treeNode != null) {
|
||||
DirectoryTreeFilterNode.OriginalNode origin = treeNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class);
|
||||
if (origin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Node originNode = origin.getNode();
|
||||
|
||||
//set node, wrap in filter node first to filter out children
|
||||
Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
|
||||
|
||||
// Create a TableFilterNode with knowledge of the node's type to allow for column order settings
|
||||
if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) {
|
||||
//Special case for when File Type Identification has not yet been run and
|
||||
//there are no mime types to populate Files by Mime Type Tree
|
||||
EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text());
|
||||
dataResult.setNode(new TableFilterNode(emptyNode, true, "This Node Is Empty")); //NON-NLS
|
||||
} else if (originNode instanceof DisplayableItemNode) {
|
||||
dataResult.setNode(new TableFilterNode(drfn, true, ((DisplayableItemNode) originNode).getItemType()));
|
||||
} else {
|
||||
dataResult.setNode(new TableFilterNode(drfn, 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: {0}", originNode); //NON-NLS
|
||||
}
|
||||
} else if (originNode.getLookup().lookup(String.class) != null) {
|
||||
displayName = originNode.getLookup().lookup(String.class);
|
||||
}
|
||||
dataResult.setPath(displayName);
|
||||
EventQueue.invokeLater(() -> {
|
||||
// change the cursor to "waiting cursor" for this operation
|
||||
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
try {
|
||||
Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
|
||||
if (treeNode != null) {
|
||||
Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal();
|
||||
//set node, wrap in filter node first to filter out children
|
||||
Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
|
||||
// Create a TableFilterNode with knowledge of the node's type to allow for column order settings
|
||||
if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) {
|
||||
//Special case for when File Type Identification has not yet been run and
|
||||
//there are no mime types to populate Files by Mime Type Tree
|
||||
EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text());
|
||||
dataResult.setNode(new TableFilterNode(emptyNode, true, "This Node Is Empty")); //NON-NLS
|
||||
} else if (originNode instanceof DisplayableItemNode) {
|
||||
dataResult.setNode(new TableFilterNode(drfn, true, ((DisplayableItemNode) originNode).getItemType()));
|
||||
} else {
|
||||
dataResult.setNode(new TableFilterNode(drfn, true));
|
||||
}
|
||||
|
||||
// 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();
|
||||
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: {0}", originNode); //NON-NLS
|
||||
}
|
||||
} else if (originNode.getLookup().lookup(String.class) != null) {
|
||||
displayName = originNode.getLookup().lookup(String.class);
|
||||
}
|
||||
} finally {
|
||||
setCursor(null);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -774,29 +759,16 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
||||
private void refreshDataSourceTree() {
|
||||
Node selectedNode = getSelectedNode();
|
||||
final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext());
|
||||
|
||||
Children rootChildren = em.getRootContext().getChildren();
|
||||
Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME);
|
||||
if (dataSourcesFilterNode == null) {
|
||||
LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS
|
||||
return;
|
||||
}
|
||||
DirectoryTreeFilterNode.OriginalNode imagesNodeOrig = dataSourcesFilterNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class);
|
||||
|
||||
if (imagesNodeOrig == null) {
|
||||
LOGGER.log(Level.SEVERE, "Cannot find data sources node, won't refresh the content tree"); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
Node imagesNode = imagesNodeOrig.getNode();
|
||||
|
||||
DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) imagesNode.getChildren();
|
||||
Node dataSourcesNode = ((DirectoryTreeFilterNode) dataSourcesFilterNode).getOriginal();
|
||||
DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) dataSourcesNode.getChildren();
|
||||
contentRootChildren.refreshContentKeys();
|
||||
|
||||
//final TreeView tree = getTree();
|
||||
//tree.expandNode(imagesNode);
|
||||
setSelectedNode(selectedPath, DataSourcesNode.NAME);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -963,6 +935,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
||||
});
|
||||
}
|
||||
|
||||
// RJCTODO: What is this all about?
|
||||
@Override
|
||||
public void viewArtifactContent(BlackboardArtifact art) {
|
||||
new ViewContextAction(
|
||||
|
@ -24,49 +24,46 @@ import java.beans.PropertyVetoException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
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.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
|
||||
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo;
|
||||
import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
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.Directory;
|
||||
import org.sleuthkit.datamodel.FileSystem;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.VolumeSystem;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* 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 {
|
||||
|
||||
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 Content content;
|
||||
private final Content content;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* parent of the content, selecting the parent in the tree view, then
|
||||
* selecting the content in the results view. This is commonly called "view
|
||||
* file in directory."
|
||||
* selecting the content in the results view.
|
||||
*
|
||||
* @param displayName The display name for the action.
|
||||
* @param artifactNode The artifact node for the artifact.
|
||||
@ -74,14 +71,20 @@ public final class ViewContextAction extends AbstractAction {
|
||||
public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) {
|
||||
super(displayName);
|
||||
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
|
||||
* 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. This is commonly called "view
|
||||
* file in directory."
|
||||
* selecting the content in the results view.
|
||||
*
|
||||
* @param displayName The display name for the action.
|
||||
* @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
|
||||
* 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. This is commonly called "view file in
|
||||
* directory."
|
||||
* content in the results view.
|
||||
*
|
||||
* @param displayName The display name for the action.
|
||||
* @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
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
/*
|
||||
* Ensure that the action manipulates the tree view in the GUI event
|
||||
* thread.
|
||||
*/
|
||||
EventQueue.invokeLater(() -> {
|
||||
/*
|
||||
* Create a flattened copy of the branch of the tree view that leads
|
||||
* 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.
|
||||
* Get the "Data Sources" node from the tree view.
|
||||
*/
|
||||
DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance();
|
||||
TreeView treeView = treeViewTopComponent.getTree();
|
||||
ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager();
|
||||
Node treeViewRootNode = treeViewExplorerMgr.getRootContext();
|
||||
Children treeViewRootNodeChildren = treeViewRootNode.getChildren();
|
||||
Node dataSourcesNode = treeViewRootNodeChildren.findChild(DataSourcesNode.NAME);
|
||||
Children currentTreeViewNodeChildren = dataSourcesNode.getChildren();
|
||||
Node contentParentNode = null;
|
||||
for (int i = 0; i < branchChildren.getNodesCount(); i++) {
|
||||
Node currentBranchNode = branchChildren.getNodeAt(i);
|
||||
for (int j = 0; j < currentTreeViewNodeChildren.getNodesCount(); j++) {
|
||||
Node currentTreeViewNode = currentTreeViewNodeChildren.getNodeAt(j);
|
||||
if (currentBranchNode.getDisplayName().equals(currentTreeViewNode.getDisplayName())) {
|
||||
contentParentNode = currentTreeViewNode;
|
||||
treeView.expandNode(contentParentNode);
|
||||
currentTreeViewNodeChildren = currentTreeViewNode.getChildren();
|
||||
break;
|
||||
Node parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME);
|
||||
|
||||
/*
|
||||
* Get the parent content for the content to be selected in the
|
||||
* results view. If the parent content is null, then the specified
|
||||
* content is a data source, and the parent tree view node is the
|
||||
* "Data Sources" node. Otherwise, the tree view needs to be
|
||||
* searched to find the parent treeview node.
|
||||
*/
|
||||
Content parentContent = null;
|
||||
try {
|
||||
parentContent = content.getParent();
|
||||
} catch (TskCoreException ex) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
* results view in the tree view.
|
||||
* Set the child selection info of the parent tree node, then select
|
||||
* the parent node in the tree view. The results view will retrieve
|
||||
* this selection info and use it to complete this action when the
|
||||
* tree view top component responds to the selection of the parent
|
||||
* node by pushing it into the results view top component.
|
||||
*/
|
||||
DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal();
|
||||
undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content));
|
||||
TreeView treeView = treeViewTopComponent.getTree();
|
||||
treeView.expandNode(parentTreeViewNode);
|
||||
try {
|
||||
treeView.expandNode(contentParentNode);
|
||||
treeViewExplorerMgr.setExploredContextAndSelection(contentParentNode, new Node[]{contentParentNode});
|
||||
// RJCTODO: Pop up
|
||||
treeViewExplorerMgr.setExploredContextAndSelection(parentTreeViewNode, new Node[]{parentTreeViewNode});
|
||||
} 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
|
||||
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();
|
||||
// });
|
||||
logger.log(Level.SEVERE, "Failed to select the parent node in the tree view", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a clone of this action.
|
||||
*
|
||||
* @return The cloned action.
|
||||
*
|
||||
* @throws CloneNotSupportedException Exception thrown if there is a problem
|
||||
* creating the clone.
|
||||
* 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 lineage.
|
||||
*/
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
ViewContextAction clone = (ViewContextAction) super.clone();
|
||||
clone.setContent(this.content);
|
||||
return clone;
|
||||
}
|
||||
private static class AncestorVisitor extends ContentVisitor.Default<List<Content>> {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
List<Content> lineage = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected Node[] doInBackground() throws Exception {
|
||||
/*
|
||||
* Return all of the child nodes of the current root node of the
|
||||
* 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;
|
||||
protected List<Content> defaultVisit(Content content) {
|
||||
lineage.add(content);
|
||||
Content parent = null;
|
||||
try {
|
||||
nodesDisplayedInResultsView = get();
|
||||
} catch (InterruptedException | CancellationException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "ResultViewNodeSelectionTask failed to get nodes displayed in results view.", ex); //NON-NLS
|
||||
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 (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;
|
||||
}
|
||||
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 ? lineage : parent.accept(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
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
|
||||
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);
|
||||
private List<Content> skipToParent(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);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user