merge from 8183

This commit is contained in:
Greg DiCristofaro 2021-12-09 13:54:58 -05:00
commit c556d26f55
25 changed files with 553 additions and 175 deletions

View File

@ -41,7 +41,7 @@
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="7" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="0" gridY="0" gridWidth="8" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@ -53,7 +53,7 @@
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="8" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="13" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="9" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="13" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@ -69,7 +69,7 @@
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="9" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="10" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@ -252,7 +252,7 @@
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="10" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
<GridBagConstraints gridX="0" gridY="2" gridWidth="11" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>

View File

@ -878,7 +878,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 7;
gridBagConstraints.gridwidth = 8;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
@ -886,7 +886,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 8;
gridBagConstraints.gridx = 9;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5);
@ -894,7 +894,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 9;
gridBagConstraints.gridx = 10;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
@ -1007,7 +1007,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.gridwidth = 10;
gridBagConstraints.gridwidth = 11;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
@ -1356,7 +1356,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
try {
this.searchResultManager = new SearchManager(new FileSystemFetcher(fileSystemKey), getPageSize());
SearchResultsDTO results = searchResultManager.getResults();
displaySearchResults(results, true);
displaySearchResults(results, true, fileSystemKey.getChildIdToSelect());
} catch (ExecutionException | IllegalArgumentException ex) {
logger.log(Level.WARNING, MessageFormat.format(
"There was an error fetching data for file system filter: {0}.",
@ -1417,16 +1417,23 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
"# {1} - pageCount",
"DataResultPanel_pageIdxOfCount={0} of {1}"
})
private void displaySearchResults(SearchResultsDTO searchResults, boolean resetPaging) {
private void displaySearchResults(SearchResultsDTO searchResults, boolean resetPaging) {
displaySearchResults(searchResults, resetPaging, null);
}
private void displaySearchResults(SearchResultsDTO searchResults, boolean resetPaging, Long contentIdToSelect) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> displaySearchResults(searchResults, resetPaging));
SwingUtilities.invokeLater(() -> displaySearchResults(searchResults, resetPaging, contentIdToSelect));
return;
}
if (searchResults == null) {
setNode(null, resetPaging);
} else {
setNode(new SearchResultRootNode(searchResults), resetPaging);
SearchResultRootNode node = new SearchResultRootNode(searchResults);
node.setChildIdToSelect(contentIdToSelect);
setNode(node, resetPaging);
setNumberOfChildNodes(
searchResults.getTotalResultsCount() > Integer.MAX_VALUE
? Integer.MAX_VALUE

View File

@ -71,9 +71,8 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
import org.sleuthkit.autopsy.mainui.nodes.SearchResultRootNode;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.autopsy.mainui.nodes.NodeSelectionInfo.ContentNodeSelectionInfo;
import org.sleuthkit.datamodel.Score.Significance;
/**
@ -388,8 +387,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* If one of the child nodes of the root node is to be selected, select
* it.
*/
if (rootNode instanceof TableFilterNode) {
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
if (rootNode instanceof ContentNodeSelectionInfo) {
ContentNodeSelectionInfo selectedChildInfo = ((ContentNodeSelectionInfo) rootNode);
if (null != selectedChildInfo) {
Node[] childNodes = rootNode.getChildren().getNodes(true);
for (int i = 0; i < childNodes.length; ++i) {
@ -406,7 +405,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
break;
}
}
((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
// Once it is selected clear the id.
((ContentNodeSelectionInfo) rootNode).setChildIdToSelect(null);
}
}

View File

@ -26,6 +26,7 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.mainui.nodes.ContentNodeUtil;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.LocalFilesDataSource;
@ -52,13 +53,13 @@ class DataSourceGroupingNode extends DisplayableItemNode {
if (dataSource instanceof Image) {
Image image = (Image) dataSource;
super.setName(image.getName());
super.setName(ContentNodeUtil.getContentName(image.getId()));
super.setDisplayName(image.getName());
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png");
} else if (dataSource instanceof LocalFilesDataSource) {
LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource;
super.setName(localFilesDataSource.getName());
super.setName(ContentNodeUtil.getContentName(localFilesDataSource.getId()));
super.setDisplayName(localFilesDataSource.getName());
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png");
}

View File

@ -33,6 +33,7 @@ import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.corecomponents.SelectionResponder;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
@ -127,6 +128,13 @@ public class DataResultFilterNode extends FilterNode {
@Override
public Action getPreferredAction() {
final Node original = this.getOriginal();
if (original instanceof SelectionResponder
&& original instanceof AbstractNode) {
AbstractNode abstractNode = (AbstractNode)original;
return DirectoryTreeTopComponent.getOpenChildAction(abstractNode.getName(), sourceEm);
}
// Once had a org.openide.nodes.ChildFactory$WaitFilterNode passed in
if ((original instanceof DisplayableItemNode) == false) {
return null;
@ -356,43 +364,7 @@ public class DataResultFilterNode extends FilterNode {
* @return
*/
private AbstractAction openChild(final AbstractNode dataModelNode) {
// get the current selection from the directory tree explorer manager,
// which is a DirectoryTreeFilterNode. One of that node's children
// is a DirectoryTreeFilterNode that wraps the dataModelNode. We need
// to set that wrapped node as the selection and root context of the
// directory tree explorer manager (sourceEm)
if (sourceEm == null || sourceEm.getSelectedNodes().length == 0) {
return null;
}
final Node currentSelectionInDirectoryTree = sourceEm.getSelectedNodes()[0];
return new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (currentSelectionInDirectoryTree != null) {
// Find the filter version of the passed in dataModelNode.
final org.openide.nodes.Children children = currentSelectionInDirectoryTree.getChildren();
// This call could break if the DirectoryTree is re-implemented with lazy ChildFactory objects.
Node newSelection = children.findChild(dataModelNode.getName());
/*
* We got null here when we were viewing a ZIP file in
* the Views -> Archives area and double clicking on it
* got to this code. It tried to find the child in the
* tree and didn't find it. An exception was then thrown
* from setting the selected node to be null.
*/
if (newSelection != null) {
try {
sourceEm.setExploredContextAndSelection(newSelection, new Node[]{newSelection});
} catch (PropertyVetoException ex) {
Logger logger = Logger.getLogger(DataResultFilterNode.class.getName());
logger.log(Level.WARNING, "Error: can't open the selected directory.", ex); //NON-NLS
}
}
}
}
};
return DirectoryTreeTopComponent.getOpenChildAction(dataModelNode.getName(), sourceEm);
}
/**
@ -404,25 +376,7 @@ public class DataResultFilterNode extends FilterNode {
* @return
*/
private AbstractAction openParent(AbstractNode node) {
if (sourceEm == null) {
return null;
}
// @@@ Why do we ignore node?
Node[] selectedFilterNodes = sourceEm.getSelectedNodes();
Node selectedFilterNode = selectedFilterNodes[0];
final Node parentNode = selectedFilterNode.getParentNode();
return new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
sourceEm.setSelectedNodes(new Node[]{parentNode});
} catch (PropertyVetoException ex) {
Logger logger = Logger.getLogger(DataResultFilterNode.class.getName());
logger.log(Level.WARNING, "Error: can't open the parent directory.", ex); //NON-NLS
}
}
};
return DirectoryTreeTopComponent.getOpenParentAction();
}
}
}

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.directorytree;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
@ -41,6 +42,7 @@ import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
@ -899,6 +901,13 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
}
} else if (originNode.getLookup().lookup(String.class) != null) {
displayName = originNode.getLookup().lookup(String.class);
} else {
if (originNode.getDisplayName() != null) {
// Remove node count from name if present
displayName = originNode.getDisplayName().replaceAll("\\(([0-9]+|\\.\\.\\.)\\)$", "");
} else {
displayName = originNode.getName();
}
}
dataResult.setPath(displayName);
}
@ -1677,5 +1686,114 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
public void addOnFinishedListener(PropertyChangeListener l) {
DirectoryTreeTopComponent.this.addPropertyChangeListener(l);
}
/**
* Gets the open child action for the given node name. Will return
* null if the nodeName matches the currently selected tree node.
*
* @param nodeName The child node to open.
*
* @return The openChild action or null if not valid.
*/
public static AbstractAction getOpenChildAction(String nodeName) {
DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance();
ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager();
return getOpenChildAction(nodeName, treeViewExplorerMgr);
}
/**
* Gets the open child action for the given node name. Will return
* null if the nodeName matches the currently selected tree node.
*
* @param nodeName The child node to open.
* @param explorerManager The explorer manager for the tree.
*
* @return The openChild action or null if not valid.
*/
static AbstractAction getOpenChildAction(String nodeName, ExplorerManager explorerManager) {
// get the current selection from the directory tree explorer manager,
// which is a DirectoryTreeFilterNode. One of that node's children
// is a DirectoryTreeFilterNode that wraps the dataModelNode. We need
// to set that wrapped node as the selection and root context of the
// directory tree explorer manager (sourceEm)
if (explorerManager == null || explorerManager.getSelectedNodes().length == 0 || nodeName == null) {
return null;
}
final Node currentSelectionInDirectoryTree = explorerManager.getSelectedNodes()[0];
// We have several node types that are used in both the tree and the result viewer.
// For tree nodes, we don't want to do the open child action.
// When double-clicking on a tree node, the nodeName to open will be the same
// as the currently seleted node, so don't return an action if this is the case.
if (nodeName.equals(currentSelectionInDirectoryTree.getName())) {
return null;
}
return new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (currentSelectionInDirectoryTree != null) {
// Find the filter version of the passed in dataModelNode.
final org.openide.nodes.Children children = currentSelectionInDirectoryTree.getChildren();
// This call could break if the DirectoryTree is re-implemented with lazy ChildFactory objects.
Node newSelection = children.findChild(nodeName);
/*
* We got null here when we were viewing a ZIP file in
* the Views -> Archives area and double clicking on it
* got to this code. It tried to find the child in the
* tree and didn't find it. An exception was then thrown
* from setting the selected node to be null.
*/
if (newSelection != null) {
try {
explorerManager.setExploredContextAndSelection(newSelection, new Node[]{newSelection});
} catch (PropertyVetoException ex) {
Logger logger = Logger.getLogger(DataResultFilterNode.class.getName());
logger.log(Level.WARNING, "Error: can't open the selected directory.", ex); //NON-NLS
}
}
}
}
};
}
/**
* Gets the open parent action for the currently selected node.
*
* @return The openChild action.
*/
public static AbstractAction getOpenParentAction() {
DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance();
ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager();
return getOpenParentAction(treeViewExplorerMgr);
}
/**
* Gets the open parent action for the currently selected node.
*
* @param explorerManager The explorer manager for the tree.
*
* @return The open parent action or null if given an invalid ExplorerManager.
*/
static AbstractAction getOpenParentAction(ExplorerManager explorerManager) {
if (explorerManager == null) {
return null;
}
Node[] selectedFilterNodes = explorerManager.getSelectedNodes();
Node selectedFilterNode = selectedFilterNodes[0];
final Node parentNode = selectedFilterNode.getParentNode();
return new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
explorerManager.setSelectedNodes(new Node[]{parentNode});
} catch (PropertyVetoException ex) {
Logger logger = Logger.getLogger(DataResultFilterNode.class.getName());
logger.log(Level.WARNING, "Error: can't open the parent directory.", ex); //NON-NLS
}
}
};
}
}

View File

@ -46,12 +46,11 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
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.DataSourceFilesNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.PersonNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
import org.sleuthkit.autopsy.mainui.nodes.NodeSelectionInfo.ContentNodeSelectionInfo;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
@ -332,18 +331,23 @@ public class ViewContextAction extends AbstractAction {
* 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));
Long childIdToSelect = content.getId();
if (content instanceof BlackboardArtifact) {
BlackboardArtifact artifact = ((BlackboardArtifact) content);
long associatedId = artifact.getObjectID();
try {
Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId);
undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent));
childIdToSelect = associatedFileContent.getId();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId());
}
}
if(parentTreeViewNode instanceof ContentNodeSelectionInfo) {
((ContentNodeSelectionInfo) parentTreeViewNode).setChildIdToSelect(childIdToSelect);
}
TreeView treeView = treeViewTopComponent.getTree();
treeView.expandNode(parentTreeViewNode);

View File

@ -19,11 +19,12 @@
package org.sleuthkit.autopsy.mainui.datamodel;
import java.util.Objects;
import org.sleuthkit.autopsy.mainui.nodes.NodeSelectionInfo.ContentNodeSelectionInfo;
/**
* Key for content object in order to retrieve data from DAO.
*/
public class FileSystemContentSearchParam {
public class FileSystemContentSearchParam implements ContentNodeSelectionInfo {
private static final String TYPE_ID = "FILE_SYSTEM_CONTENT";
@ -35,6 +36,10 @@ public class FileSystemContentSearchParam {
}
private final Long contentObjectId;
// This param is can change, is not used as part of the search query and
// therefore is not included in the equals and hashcode methods.
private Long childContentToSelect;
public FileSystemContentSearchParam(Long contentObjectId) {
this.contentObjectId = contentObjectId;
@ -43,6 +48,16 @@ public class FileSystemContentSearchParam {
public Long getContentObjectId() {
return contentObjectId;
}
@Override
public void setChildIdToSelect(Long content) {
childContentToSelect = content;
}
@Override
public Long getChildIdToSelect() {
return childContentToSelect;
}
@Override
public int hashCode() {

View File

@ -68,7 +68,8 @@ public class MainDAO extends AbstractDAO {
Case.Events.OS_ACCOUNTS_ADDED.toString(),
Case.Events.OS_ACCOUNTS_UPDATED.toString(),
Case.Events.OS_ACCOUNTS_DELETED.toString(),
Case.Events.OS_ACCT_INSTANCES_ADDED.toString()
Case.Events.OS_ACCT_INSTANCES_ADDED.toString(),
Case.Events.DATA_SOURCE_ADDED.toString()
);
private static final long WATCH_RESOLUTION_MILLIS = 30 * 1000;

View File

@ -80,12 +80,23 @@ public class TreeResultsDTO<T> {
return count;
}
/**
* Returns the suffix to be added to a display string when displaying
* this count.
*
* NOTE: If this code changes, regex code in
* DirectoryTreeTopComponent.respondSelection will need to be updated as
* well.
*
* @return The suffix to be added to a display string when displaying
* this count.
*/
public String getDisplaySuffix() {
switch (this.type) {
case DETERMINATE:
return " (" + count + ")";
case INDETERMINATE:
return "...";
return " (...)";
case NOT_SHOWN:
case UNSPECIFIED:
default:

View File

@ -25,6 +25,7 @@ import java.beans.PropertyChangeEvent;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -169,8 +170,8 @@ public class ViewsDAO extends AbstractDAO {
}
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) eventData;
return key.getFilter().equals(extEvt.getExtensionFilter())
&& (key.getDataSourceId() == null || key.getDataSourceId().equals(extEvt.getDataSourceId()));
return (extEvt.getExtensionFilter() == null || key.getFilter().equals(extEvt.getExtensionFilter()))
&& (key.getDataSourceId() == null || extEvt.getDataSourceId() == null || key.getDataSourceId().equals(extEvt.getDataSourceId()));
}
private boolean isFilesByMimeInvalidating(FileTypeMimeSearchParams key, DAOEvent eventData) {
@ -189,8 +190,8 @@ public class ViewsDAO extends AbstractDAO {
}
FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) eventData;
return sizeEvt.getSizeFilter().equals(key.getSizeFilter())
&& (key.getDataSourceId() == null || Objects.equals(key.getDataSourceId(), sizeEvt.getDataSourceId()));
return (sizeEvt.getSizeFilter() == null || sizeEvt.getSizeFilter().equals(key.getSizeFilter()))
&& (key.getDataSourceId() == null || sizeEvt.getDataSourceId() == null || Objects.equals(key.getDataSourceId(), sizeEvt.getDataSourceId()));
}
private boolean isDeletedContentInvalidating(DeletedContentSearchParams params, DAOEvent eventData) {
@ -433,11 +434,13 @@ public class ViewsDAO extends AbstractDAO {
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof FileTypeExtensionsEvent) {
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) evt;
if (dataSourceId == null || Objects.equals(extEvt.getDataSourceId(), dataSourceId)) {
for (FileExtSearchFilter filter : filters) {
if (filter.getFilter().contains(evt)) {
indeterminateFilters.add(filter);
}
if (dataSourceId == null || extEvt.getDataSourceId() == null || Objects.equals(extEvt.getDataSourceId(), dataSourceId)) {
if (extEvt.getExtensionFilter() == null) {
// add all filters if extension filter is null and keep going
indeterminateFilters.addAll(filters);
break;
} else if (filters.contains(extEvt.getExtensionFilter())) {
indeterminateFilters.add(extEvt.getExtensionFilter());
}
}
}
@ -478,7 +481,7 @@ public class ViewsDAO extends AbstractDAO {
FileTypeExtensionsSearchParams.getTypeId(),
new FileTypeExtensionsSearchParams(filter, dataSourceId),
filter,
filter.getDisplayName(),
filter == null ? "" : filter.getDisplayName(),
displayCount);
}
@ -498,8 +501,14 @@ public class ViewsDAO extends AbstractDAO {
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof FileTypeSizeEvent) {
FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) evt;
if (dataSourceId == null || Objects.equals(sizeEvt.getDataSourceId(), dataSourceId)) {
indeterminateFilters.add(sizeEvt.getSizeFilter());
if (dataSourceId == null || sizeEvt.getDataSourceId() == null || Objects.equals(sizeEvt.getDataSourceId(), dataSourceId)) {
if (sizeEvt.getSizeFilter() == null) {
// if null size filter, indicates full refresh and all file sizes need refresh.
indeterminateFilters.addAll(Arrays.asList(FileSizeFilter.values()));
break;
} else {
indeterminateFilters.add(sizeEvt.getSizeFilter());
}
}
}
}
@ -591,7 +600,7 @@ public class ViewsDAO extends AbstractDAO {
FileTypeSizeSearchParams.getTypeId(),
new FileTypeSizeSearchParams(filter, dataSourceId),
filter,
filter.getDisplayName(),
filter == null ? "" : filter.getDisplayName(),
displayCount);
}
@ -920,8 +929,21 @@ public class ViewsDAO extends AbstractDAO {
@Override
Set<? extends DAOEvent> handleIngestComplete() {
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> searchParamsMatchEvent(null, null, null, null, true, searchParams));
Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
(daoEvt, count) -> createTreeItem(daoEvt, count));
Set<? extends DAOEvent> fileViewRefreshEvents = getFileViewRefreshEvents(null);
List<? extends DAOEvent> fileViewRefreshTreeEvents = fileViewRefreshEvents.stream()
.map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toList());
return Stream.of(treeEvts, fileViewRefreshEvents, fileViewRefreshTreeEvents)
.flatMap(c -> c.stream())
.collect(Collectors.toSet());
}
@Override
@ -932,56 +954,87 @@ public class ViewsDAO extends AbstractDAO {
@Override
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt);
if (af == null) {
return Collections.emptySet();
} else if (hideKnownFilesInViewsTree() && FileKnown.KNOWN.equals(af.getKnown())) {
return Collections.emptySet();
}
Long dsId = null;
boolean dataSourceAdded = false;
Set<FileExtSearchFilter> evtExtFilters = null;
String evtMimeType = null;
FileSizeFilter evtFileSize = null;
long dsId = af.getDataSourceObjectId();
if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) {
dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null;
dataSourceAdded = true;
} else {
AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt);
if (af == null) {
return Collections.emptySet();
} else if (hideKnownFilesInViewsTree() && TskData.FileKnown.KNOWN.equals(af.getKnown())) {
return Collections.emptySet();
}
// create an extension mapping if extension present
Set<FileExtSearchFilter> evtExtFilters = (StringUtils.isBlank(af.getNameExtension()) || !TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType()))
? Collections.emptySet()
: EXTENSION_FILTER_MAP.getOrDefault("." + af.getNameExtension(), Collections.emptySet());
dsId = af.getDataSourceObjectId();
Set<DeletedContentFilter> deletedContentFilters = getMatchingDeletedContentFilters(af);
// create an extension mapping if extension present
if (StringUtils.isBlank(af.getNameExtension()) || !TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType())) {
evtExtFilters = EXTENSION_FILTER_MAP.getOrDefault("." + af.getNameExtension(), Collections.emptySet());
}
// create a mime type mapping if mime type present
String evtMimeType = (StringUtils.isBlank(af.getMIMEType()) || !TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType())) || !getMimeDbFilesTypes().contains(af.getType())
? null
: af.getMIMEType();
Set<DeletedContentFilter> deletedContentFilters = getMatchingDeletedContentFilters(af);
// create a size mapping if size present in filters
FileSizeFilter evtFileSize = TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.equals(af.getType())
? null
: Stream.of(FileSizeFilter.values())
// create a mime type mapping if mime type present
if (StringUtils.isBlank(af.getMIMEType()) || !TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType()) || !getMimeDbFilesTypes().contains(af.getType())) {
evtMimeType = af.getMIMEType();
}
// create a size mapping if size present in filters
if (TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.equals(af.getType())) {
evtFileSize = Stream.of(FileSizeFilter.values())
.filter(filter -> af.getSize() >= filter.getMinBound() && (filter.getMaxBound() == null || af.getSize() < filter.getMaxBound()))
.findFirst()
.orElse(null);
}
if (evtExtFilters.isEmpty() && deletedContentFilters.isEmpty() && evtMimeType == null && evtFileSize == null) {
if (evtExtFilters == null || evtExtFilters.isEmpty() && deletedContentFilters.isEmpty() && evtMimeType == null && evtFileSize == null) {
return Collections.emptySet();
}
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(Predicate<Object>) (searchParams) -> searchParamsMatchEvent(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, dsId, searchParams));
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, dsId, dataSourceAdded);
}
return getDAOEvents(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, dsId);
/**
* Handles invalidating caches and returning events based on digest.
*
* @param evtExtFilters The file extension filters or empty set.
* @param evtMimeType The mime type or null.
* @param evtFileSize The file size filter or null.
* @param dsId The data source id or null.
* @param dataSourceAdded Whether or not this is a data source added event.
*
* @return The set of dao events to be fired.
*/
private Set<DAOEvent> invalidateAndReturnEvents(Set<FileExtSearchFilter> evtExtFilters, String evtMimeType,
FileSizeFilter evtFileSize, Long dsId, boolean dataSourceAdded) {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> searchParamsMatchEvent(evtExtFilters, deletedContentFilters,
evtMimeType, evtFileSize, dsId, dataSourceAdded, searchParams));
return getDAOEvents(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, dsId, dataSourceAdded);
}
private boolean searchParamsMatchEvent(Set<FileExtSearchFilter> evtExtFilters,
Set<DeletedContentFilter> deletedContentFilters,
String evtMimeType,
FileSizeFilter evtFileSize,
long dsId,
Long dsId,
boolean dataSourceAdded,
Object searchParams) {
if (searchParams instanceof FileTypeExtensionsSearchParams) {
FileTypeExtensionsSearchParams extParams = (FileTypeExtensionsSearchParams) searchParams;
return evtExtFilters.contains(extParams.getFilter())
&& (extParams.getDataSourceId() == null || Objects.equals(extParams.getDataSourceId(), dsId));
// if data source added or evtExtFilters contain param filter
return (dataSourceAdded || (evtExtFilters != null && evtExtFilters.contains(extParams.getFilter())))
// and data source is either null or they are equal data source ids
&& (extParams.getDataSourceId() == null || dsId == null || Objects.equals(extParams.getDataSourceId(), dsId));
} else if (searchParams instanceof FileTypeMimeSearchParams) {
FileTypeMimeSearchParams mimeParams = (FileTypeMimeSearchParams) searchParams;
@ -990,8 +1043,10 @@ public class ViewsDAO extends AbstractDAO {
} else if (searchParams instanceof FileTypeSizeSearchParams) {
FileTypeSizeSearchParams sizeParams = (FileTypeSizeSearchParams) searchParams;
return Objects.equals(sizeParams.getSizeFilter(), evtFileSize)
&& (sizeParams.getDataSourceId() == null || Objects.equals(sizeParams.getDataSourceId(), dsId));
// if data source added or size filter is equal to param filter
return (dataSourceAdded || Objects.equals(sizeParams.getSizeFilter(), evtFileSize))
// and data source is either null or they are equal data source ids
&& (sizeParams.getDataSourceId() == null || dsId == null || Objects.equals(sizeParams.getDataSourceId(), dsId));
} else if (searchParams instanceof DeletedContentSearchParams) {
DeletedContentSearchParams deletedParams = (DeletedContentSearchParams) searchParams;
return deletedContentFilters.contains(deletedParams.getFilter())
@ -1010,6 +1065,7 @@ public class ViewsDAO extends AbstractDAO {
* @param mimeType The affected mime type or null.
* @param sizeFilter The affected size filter or null.
* @param dsId The file object id.
* @param dataSourceAdded A data source was added.
*
* @return The list of affected dao events.
*/
@ -1017,10 +1073,14 @@ public class ViewsDAO extends AbstractDAO {
Set<DeletedContentFilter> deletedContentFilters,
String mimeType,
FileSizeFilter sizeFilter,
long dsId) {
long dsId,
boolean dataSourceAdded) {
Stream<DAOEvent> extEvents = extFilters.stream()
.map(extFilter -> new FileTypeExtensionsEvent(extFilter, dsId));
List<DAOEvent> daoEvents = extFilters == null
? new ArrayList<>()
: extFilters.stream()
.map(extFilter -> new FileTypeExtensionsEvent(extFilter, dsId))
.collect(Collectors.toList());
Stream<DAOEvent> deletedEvents = deletedContentFilters.stream()
.map(deletedFilter -> new DeletedContentEvent(deletedFilter, dsId));
@ -1040,7 +1100,16 @@ public class ViewsDAO extends AbstractDAO {
.map(daoEvt -> new TreeEvent(createTreeItem(daoEvt, TreeDisplayCount.INDETERMINATE), false))
.collect(Collectors.toList());
return Stream.of(daoEvents, treeEvents)
// data source added events are not necessarily fired before ingest completed/cancelled, so don't handle dataSourceAdded events with delay.
Set<DAOEvent> forceRefreshEvents = (dataSourceAdded)
? getFileViewRefreshEvents(dsId)
: Collections.emptySet();
List<TreeEvent> forceRefreshTreeEvents = forceRefreshEvents.stream()
.map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toList());
return Stream.of(daoEvents, treeEvents, forceRefreshEvents, forceRefreshTreeEvents)
.flatMap(lst -> lst.stream())
.collect(Collectors.toSet());
}
@ -1067,6 +1136,22 @@ public class ViewsDAO extends AbstractDAO {
return toRet;
}
/**
* Returns events for when a full refresh is required because module content
* events will not necessarily provide events for files (i.e. data source
* added, ingest cancelled/completed).
*
* @param dataSourceId The data source id or null if not applicable.
*
* @return The set of events that apply in this situation.
*/
private Set<DAOEvent> getFileViewRefreshEvents(Long dataSourceId) {
return ImmutableSet.of(
new FileTypeSizeEvent(null, dataSourceId),
new FileTypeExtensionsEvent(null, dataSourceId)
);
}
/**
* Handles fetching and paging of data for file types by extension.
*/

View File

@ -22,15 +22,22 @@ import java.util.Objects;
import org.sleuthkit.autopsy.mainui.datamodel.FileExtSearchFilter;
/**
* An event to signal that files have been added or removed
* with the given extension on the given data source.
* An event to signal that files have been added or removed with the given
* extension on the given data source.
*/
public class FileTypeExtensionsEvent implements DAOEvent {
private final FileExtSearchFilter extensionFilter;
private final long dataSourceId;
private final Long dataSourceId;
public FileTypeExtensionsEvent(FileExtSearchFilter extensionFilter, long dataSourceId) {
/**
* Main constructor.
*
* @param extensionFilter The extension filter. If null, indicates full
* refresh necessary.
* @param dataSourceId The data source id.
*/
public FileTypeExtensionsEvent(FileExtSearchFilter extensionFilter, Long dataSourceId) {
this.extensionFilter = extensionFilter;
this.dataSourceId = dataSourceId;
}
@ -39,15 +46,15 @@ public class FileTypeExtensionsEvent implements DAOEvent {
return extensionFilter;
}
public long getDataSourceId() {
public Long getDataSourceId() {
return dataSourceId;
}
@Override
public int hashCode() {
int hash = 7;
hash = 83 * hash + Objects.hashCode(this.extensionFilter);
hash = 83 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32));
int hash = 3;
hash = 89 * hash + Objects.hashCode(this.extensionFilter);
hash = 89 * hash + Objects.hashCode(this.dataSourceId);
return hash;
}
@ -63,17 +70,15 @@ public class FileTypeExtensionsEvent implements DAOEvent {
return false;
}
final FileTypeExtensionsEvent other = (FileTypeExtensionsEvent) obj;
if (this.dataSourceId != other.dataSourceId) {
if (!Objects.equals(this.extensionFilter, other.extensionFilter)) {
return false;
}
if (!Objects.equals(this.extensionFilter, other.extensionFilter)) {
if (!Objects.equals(this.dataSourceId, other.dataSourceId)) {
return false;
}
return true;
}
@Override
public Type getType() {
return Type.RESULT;

View File

@ -22,14 +22,21 @@ import java.util.Objects;
import org.sleuthkit.autopsy.mainui.datamodel.FileSizeFilter;
/**
* An event to signal that files have been added or removed
* within the given size range on the given data source.
* An event to signal that files have been added or removed within the given
* size range on the given data source.
*/
public class FileTypeSizeEvent implements DAOEvent {
private final FileSizeFilter sizeFilter;
private final Long dataSourceId;
/**
* Main constructor.
*
* @param sizeFilter The size filter. If null, indicates full refresh is
* necessary.
* @param dataSourceId The data source id or null.
*/
public FileTypeSizeEvent(FileSizeFilter sizeFilter, Long dataSourceId) {
this.sizeFilter = sizeFilter;
this.dataSourceId = dataSourceId;
@ -45,9 +52,9 @@ public class FileTypeSizeEvent implements DAOEvent {
@Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + Objects.hashCode(this.sizeFilter);
hash = 53 * hash + Objects.hashCode(this.dataSourceId);
int hash = 5;
hash = 73 * hash + Objects.hashCode(this.sizeFilter);
hash = 73 * hash + Objects.hashCode(this.dataSourceId);
return hash;
}
@ -72,6 +79,8 @@ public class FileTypeSizeEvent implements DAOEvent {
return true;
}
@Override
public Type getType() {
return Type.RESULT;

View File

@ -235,7 +235,7 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
* @param itemData The data to display.
*/
public TreeSetTypeNode(TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSetSearchParam> itemData) {
super(itemData.getSearchParams().getArtifactType().getTypeName(),
super(itemData.getSearchParams().getArtifactType().getTypeName() + "_SET_" + itemData.getSearchParams().getSetName(),
getIconPath(itemData.getSearchParams().getArtifactType()),
itemData,
Children.LEAF,
@ -274,7 +274,7 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
* @param itemData The data to display.
*/
public KeywordSetNode(TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSetSearchParam> itemData) {
super(itemData.getSearchParams().getArtifactType().getTypeName(),
super("TSK_KEYWORD_HIT_SET_" + itemData.getSearchParams().getSetName(),
getIconPath(itemData.getSearchParams().getArtifactType()),
itemData,
Children.create(new KeywordSearchTermFactory(itemData.getSearchParams()), true),

View File

@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.BaseRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory;
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
/**
* A a simple starting point for nodes.
@ -69,4 +70,9 @@ abstract class BaseNode<S extends SearchResultsDTO, R extends BaseRowDTO> extend
public Action[] getActions(boolean context) {
return ActionsFactory.getActions(this);
}
@Override
public Action getPreferredAction() {
return DirectoryTreeTopComponent.getOpenChildAction(getName());
}
}

View File

@ -1,5 +1,5 @@
AnalysisResultTypeFactory_adHocName=Adhoc Results
DataArtifactTypeFactory_AccountTypeParentNode_displayName=Communcation Accounts
DataArtifactTypeFactory_AccountTypeParentNode_displayName=Communication Accounts
FileSystemFactory.FileSystemTreeNode.ExtractUnallocAction.text=Extract Unallocated Space to Single Files
FileSystemFactory.UnsupportedTreeNode.displayName=Unsupported Content
ImageNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files

View File

@ -118,7 +118,7 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
* The account node that has nested children of account types.
*/
@Messages({
"DataArtifactTypeFactory_AccountTypeParentNode_displayName=Communcation Accounts"
"DataArtifactTypeFactory_AccountTypeParentNode_displayName=Communication Accounts"
})
static class AccountTypeParentNode extends TreeNode<DataArtifactSearchParam> {

View File

@ -19,7 +19,9 @@
package org.sleuthkit.autopsy.mainui.nodes;
import java.util.Optional;
import javax.swing.Action;
import org.openide.nodes.Children;
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.DirectoryRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.datamodel.AbstractFile;
@ -83,4 +85,13 @@ public class DirectoryNode extends BaseNode<SearchResultsDTO, DirectoryRowDTO> {
public boolean supportsContentTagAction() {
return true;
}
@Override
public Action getPreferredAction() {
if (getDisplayName().equals(org.sleuthkit.autopsy.datamodel.DirectoryNode.DOTDOTDIR)
|| getDisplayName().equals("..")) {
return DirectoryTreeTopComponent.getOpenParentAction();
}
return DirectoryTreeTopComponent.getOpenChildAction(getName());
}
}

View File

@ -26,6 +26,7 @@ import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.datamodel.FileTypeExtensions;
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey;
@ -159,6 +160,11 @@ public class FileNode extends AbstractNode implements ActionContext {
protected Sheet createSheet() {
return ContentNodeUtil.setSheet(super.createSheet(), this.columns, this.fileData.getCellValues());
}
@Override
public Action getPreferredAction() {
return DirectoryTreeTopComponent.getOpenChildAction(getName());
}
/**
* A node for representing a LayoutFile.

View File

@ -47,6 +47,7 @@ import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.CARVED_FILE;
import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.DELETED_FILE;
import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.DELETED_FOLDER;
import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.FOLDER;
import org.sleuthkit.autopsy.mainui.nodes.NodeSelectionInfo.ContentNodeSelectionInfo;
import static org.sleuthkit.autopsy.mainui.nodes.TreeNode.getDefaultLookup;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory;
@ -241,7 +242,9 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
*/
@NbBundle.Messages({
"FileSystemFactory.FileSystemTreeNode.ExtractUnallocAction.text=Extract Unallocated Space to Single Files"})
public abstract static class FileSystemTreeNode extends TreeNode<FileSystemContentSearchParam> implements ActionContext {
public abstract static class FileSystemTreeNode extends TreeNode<FileSystemContentSearchParam> implements ActionContext, ContentNodeSelectionInfo {
private Long childContentToSelect;
protected FileSystemTreeNode(String icon, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData, Children children, Lookup lookup) {
super(ContentNodeUtil.getContentName(itemData.getSearchParams().getContentObjectId()), icon, itemData, children, lookup);
@ -264,6 +267,7 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
@Override
public void respondSelection(DataResultTopComponent dataResultPanel) {
getItemData().getSearchParams().setChildIdToSelect(childContentToSelect);
dataResultPanel.displayFileSystemContent(this.getItemData().getSearchParams());
}
@ -273,6 +277,16 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
public Action[] getActions(boolean context) {
return ActionsFactory.getActions(this);
}
@Override
public void setChildIdToSelect(Long content) {
childContentToSelect = content;
}
@Override
public Long getChildIdToSelect() {
return childContentToSelect;
}
}
static class ImageTreeNode extends FileSystemTreeNode {
@ -346,7 +360,7 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
Pool pool;
PoolTreeNode(Pool pool, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) {
super(NodeIconUtil.VOLUME.getPath(),
super(NodeIconUtil.POOL.getPath(),
itemData,
createChildrenForContent(itemData.getSearchParams().getContentObjectId()),
ContentNodeUtil.getLookup(pool));

View File

@ -0,0 +1,52 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.mainui.nodes;
import org.openide.nodes.Node;
import org.sleuthkit.datamodel.Content;
/**
* An interface for nodes that support the view selected file\directory.
*/
public interface NodeSelectionInfo {
/**
* Determine of the given node represents the child content to
* be selected.
*
* @param node
*
* @return True if there is a match.
*/
boolean matches(Node node);
public interface ContentNodeSelectionInfo extends NodeSelectionInfo{
void setChildIdToSelect(Long contentId);
Long getChildIdToSelect();
default boolean matches(Node node) {
Content content = node.getLookup().lookup(Content.class);
if (content != null && getChildIdToSelect() != null) {
return getChildIdToSelect().equals(content.getId());
}
return false;
}
}
}

View File

@ -21,18 +21,22 @@ package org.sleuthkit.autopsy.mainui.nodes;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.autopsy.mainui.nodes.NodeSelectionInfo.ContentNodeSelectionInfo;
/**
* A node whose children will be displayed in the results view and determines
* children based on a SearchResultDTO.
*/
public class SearchResultRootNode extends AbstractNode {
public class SearchResultRootNode extends AbstractNode implements ContentNodeSelectionInfo{
private final SearchResultChildFactory factory;
// This param is can change, is not used as part of the search query and
// therefore is not included in the equals and hashcode methods.
private Long childContentToSelect;
public SearchResultRootNode(SearchResultsDTO initialResults) {
this(initialResults, new SearchResultChildFactory(initialResults));
@ -45,7 +49,17 @@ public class SearchResultRootNode extends AbstractNode {
setName(initialResults.getTypeId());
setDisplayName(initialResults.getDisplayName());
}
@Override
public void setChildIdToSelect(Long contentId) {
childContentToSelect = contentId;
}
@Override
public Long getChildIdToSelect() {
return childContentToSelect;
}
@Messages({
"SearchResultRootNode_noDesc=No Description",
"SearchResultRootNode_createSheet_type_name=Name",

View File

@ -48,21 +48,7 @@ public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
if (evt.getNewValue() instanceof DAOAggregateEvent) {
DAOAggregateEvent aggEvt = (DAOAggregateEvent) evt.getNewValue();
for (DAOEvent daoEvt : aggEvt.getEvents()) {
if (daoEvt instanceof TreeEvent) {
TreeEvent treeEvt = (TreeEvent) daoEvt;
TreeItemDTO<? extends T> item = getOrCreateRelevantChild(treeEvt);
if (item != null) {
if (treeEvt.isRefreshRequired()) {
update();
break;
} else {
updateNodeData(item);
}
}
}
}
handleDAOAggregateEvent((DAOAggregateEvent) evt.getNewValue());
}
};
@ -83,6 +69,30 @@ public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object
// maps the Node key (ID) to its DTO
private Map<Object, TreeItemDTO<? extends T>> idMapping = new HashMap<>();
/**
* Handles processing and updating due to an aggregate event. This method
* can be overridden for custom behavior while handling DAO aggregate
* events.
*
* @param aggEvt The aggregate event.
*/
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent daoEvt : aggEvt.getEvents()) {
if (daoEvt instanceof TreeEvent) {
TreeEvent treeEvt = (TreeEvent) daoEvt;
TreeItemDTO<? extends T> item = getOrCreateRelevantChild(treeEvt);
if (item != null) {
if (treeEvt.isRefreshRequired()) {
update();
break;
} else {
updateNodeData(item);
}
}
}
}
}
@Override
protected boolean createKeys(List<Object> toPopulate) {
List<TreeItemDTO<? extends T>> itemsList;
@ -98,9 +108,9 @@ public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object
}
// make copy to avoid concurrent modification
synchronized (resultsUpdateLock) {
itemsList = new ArrayList<>(curItemsList);
itemsList = new ArrayList<>(curItemsList);
}
// update existing cached nodes
List<Object> curResultIds = new ArrayList<>();
for (TreeItemDTO<? extends T> dto : itemsList) {

View File

@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.corecomponents.SelectionResponder;
import java.text.MessageFormat;
import java.util.Objects;
import java.util.logging.Level;
import javax.swing.Action;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
@ -30,6 +31,7 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
/**
@ -140,6 +142,19 @@ public abstract class TreeNode<T> extends AbstractNode implements SelectionRespo
dataResultPanel.setNode(this);
}
@Override
public Action getPreferredAction() {
// TreeNodes are used for both the result viewer and the tree viewer. For the result viewer,
// we want to open the child of the double-clicked node. For the tree viewer, we want the default
// action (explanding/closing the node). If getOpenChildAction() returns null, we likely
// have a tree node and want to call the default preferred action.
Action openChildAction = DirectoryTreeTopComponent.getOpenChildAction(getName());
if (openChildAction == null) {
return super.getPreferredAction();
}
return openChildAction;
}
/**
* Tree node for displaying static content in the tree.
@ -159,6 +174,6 @@ public abstract class TreeNode<T> extends AbstractNode implements SelectionRespo
public StaticTreeNode(String nodeName, String displayName, String icon, Children children, Lookup lookup) {
super(nodeName, icon, new TreeItemDTO<String>(nodeName, nodeName, nodeName, displayName, TreeDisplayCount.NOT_SHOWN), children, lookup);
}
}
}
}
}

View File

@ -40,6 +40,8 @@ import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode;
@ -261,12 +263,32 @@ public class ViewsTypeFactory {
return MainDAO.getInstance().getViewsDAO().getFileSizeCounts(this.dataSourceId);
}
@Override
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent evt : aggEvt.getEvents()) {
if (evt instanceof TreeEvent) {
TreeResultsDTO.TreeItemDTO<FileTypeSizeSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, FileTypeSizeSearchParams.class);
// if file type size search params has null filter, trigger full refresh
if (treeItem != null && treeItem.getSearchParams().getSizeFilter() == null) {
super.update();
return;
}
}
}
super.handleDAOAggregateEvent(aggEvt);
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<FileTypeSizeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeSizeSearchParams.class);
if (originalTreeItem != null
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// only create child if size filter is present (if null, update should be triggered separately)
&& originalTreeItem.getSearchParams().getSizeFilter() != null
&& (this.dataSourceId == null
|| originalTreeItem.getSearchParams().getDataSourceId() == null
|| Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
FileTypeSizeSearchParams searchParam = originalTreeItem.getSearchParams();
@ -514,11 +536,29 @@ public class ViewsTypeFactory {
return MainDAO.getInstance().getViewsDAO().getFileExtCounts(this.childFilters, this.dataSourceId);
}
@Override
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent evt : aggEvt.getEvents()) {
if (evt instanceof TreeEvent) {
TreeResultsDTO.TreeItemDTO<FileTypeExtensionsSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, FileTypeExtensionsSearchParams.class);
// if search params has null filter, trigger full refresh
if (treeItem != null && treeItem.getSearchParams().getFilter() == null) {
super.update();
return;
}
}
}
super.handleDAOAggregateEvent(aggEvt);
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<FileTypeExtensionsSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeExtensionsSearchParams.class);
if (originalTreeItem != null
// if filter is null, this should trigger a full refresh which should be handled in handleDAOAggregateEvent
&& originalTreeItem.getSearchParams().getFilter() != null
&& this.childFilters.contains(originalTreeItem.getSearchParams().getFilter())
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {