Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 3668-intra-case-correlation-content-viewer

# Conflicts:
#	Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java
#	Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
#	Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
This commit is contained in:
Andrew Ziehl 2018-05-01 08:52:35 -07:00
commit 43545a0805
92 changed files with 4804 additions and 2294 deletions

View File

@ -13,7 +13,7 @@ before_install:
install:
- sudo apt-get install testdisk
- cd sleuthkit/sleuthkit
- sh install-sleuthkit.sh
- sh travis_build.sh
script:
- set -e
- echo "building autopsy..." && echo -en 'travis_fold:start:script.build\\r'

View File

@ -86,7 +86,10 @@
<mkdir dir="${basedir}/test/qa-functional/data"/>
<get src="https://drive.google.com/uc?id=0BxdBkzm5VKGNT0dGY0dqcHVsU3M" dest="${test-input}/filter_test1.img" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1bghoSm7z7nhmGIxlllyY1MMlbLntxm7n" dest="${test-input}/local_files_test.zip" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1dLYGctuvRQMmnzfXPppTM_9gB49eLc_g" dest="${test-input}/embedded.vhd" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1BrSiUQ1fzxFS9vIaK4mYKX6qIVp9kRWT" dest="${test-input}/password_detection_test.img" skipexisting="true"/>
</target>
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist, getTestDataFiles">
<mkdir dir="${ext.dir}"/>
<copy file="${thirdparty.dir}/LICENSE-2.0.txt" todir="${ext.dir}" />

View File

@ -18,7 +18,17 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi
file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
file.reference.sleuthkit-postgresql-4.6.0.jar=release/modules/ext/sleuthkit-postgresql-4.6.0.jar
file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar
file.reference.jempbox-1.8.13.jar=release/modules/ext/jempbox-1.8.13.jar
file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0.1.jar
file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar
file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar
file.reference.cxf-rt-rs-client-3.0.16.jar=release/modules/ext/cxf-rt-rs-client-3.0.16.jar
file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar
file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar
file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar
file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar
file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar
file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar
file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar
file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar

View File

@ -327,6 +327,7 @@
<package>org.sleuthkit.autopsy.events</package>
<package>org.sleuthkit.autopsy.filesearch</package>
<package>org.sleuthkit.autopsy.guiutils</package>
<package>org.sleuthkit.autopsy.healthmonitor</package>
<package>org.sleuthkit.autopsy.ingest</package>
<package>org.sleuthkit.autopsy.keywordsearchservice</package>
<package>org.sleuthkit.autopsy.menuactions</package>
@ -347,8 +348,8 @@
<binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-postgresql-4.6.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-postgresql-4.6.0.jar</binary-origin>
<runtime-relative-path>ext/sleuthkit-postgresql-4.6.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-postgresql-4.6.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/opencv-248.jar</runtime-relative-path>
@ -410,10 +411,46 @@
<runtime-relative-path>ext/curator-client-2.8.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/curator-client-2.8.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jempbox-1.8.13.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jempbox-1.8.13.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/javax.ws.rs-api-2.0.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/javax.ws.rs-api-2.0.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/cxf-rt-rs-client-3.0.16.jar</runtime-relative-path>
<binary-origin>release/modules/ext/cxf-rt-rs-client-3.0.16.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/cxf-rt-transports-http-3.0.16.jar</runtime-relative-path>
<binary-origin>release/modules/ext/cxf-rt-transports-http-3.0.16.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/cxf-core-3.0.16.jar</runtime-relative-path>
<binary-origin>release/modules/ext/cxf-core-3.0.16.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/cxf-rt-frontend-jaxrs-3.0.16.jar</runtime-relative-path>
<binary-origin>release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/tika-parsers-1.17.jar</runtime-relative-path>
<binary-origin>release/modules/ext/tika-parsers-1.17.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/fontbox-2.0.8.jar</runtime-relative-path>
<binary-origin>release/modules/ext/fontbox-2.0.8.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/pdfbox-2.0.8.jar</runtime-relative-path>
<binary-origin>release/modules/ext/pdfbox-2.0.8.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/pdfbox-tools-2.0.8.jar</runtime-relative-path>
<binary-origin>release/modules/ext/pdfbox-tools-2.0.8.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sqlite-jdbc-3.8.11.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sqlite-jdbc-3.8.11.jar</binary-origin>

View File

@ -1543,8 +1543,8 @@ public class Case {
* @throws TskCoreException if there is a problem adding the report to the
* case database.
*/
public Report addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
return addReport(localPath, srcModuleName, reportName, null);
public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
addReport(localPath, srcModuleName, reportName, null);
}
/**

View File

@ -68,8 +68,18 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter {
@Override
public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) {
if (settings instanceof IngestSettings) {
return new IngestModule((IngestSettings) settings);
}
/*
* Compatibility check for older versions.
*/
if (settings instanceof NoIngestModuleIngestJobSettings) {
return new IngestModule(new IngestSettings());
}
throw new IllegalArgumentException("Expected settings argument to be an instance of IngestSettings");
}
@Override
public boolean hasGlobalSettingsPanel() {

View File

@ -128,7 +128,7 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov
public void propertyChange(PropertyChangeEvent pce) {
if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes();
messagesResultPanel.setNumMatches(0);
messagesResultPanel.setNumberOfChildNodes(0);
messagesResultPanel.setNode(null);
messagesResultPanel.setPath("");
if (selectedNodes.length > 0) {

View File

@ -208,11 +208,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
applyLayout(fastOrganicLayout);
}
/**
*
* @param layoutButton the value of layoutButton
* @param layout the value of layout
*/
@Override
public Lookup getLookup() {
return proxyLookup;

View File

@ -98,7 +98,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
@NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments")
public MessageContentViewer() {
initComponents();
drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", Node.EMPTY, 0, null);
drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null);
attachmentsScrollPane.setViewportView(drp);
msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true);

View File

@ -455,7 +455,8 @@ public final class CoordinationService {
CASES("cases"),
MANIFESTS("manifests"),
CONFIG("config"),
CENTRAL_REPO("centralRepository");
CENTRAL_REPO("centralRepository"),
HEALTH_MONITOR("healthMonitor");
private final String displayName;

View File

@ -216,6 +216,7 @@ public class Installer extends ModuleInstall {
packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault());
}
/**

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 Basis Technology Corp.
* Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,41 +22,58 @@ import java.util.List;
import org.openide.nodes.Node;
/**
* The interface for the "top right component" window.
* An interface for result view components. A result view component provides
* multiple views of the application data represented by a given NetBeans Node.
* The differing views of the node are supplied by a collection of result
* viewers (implementations of the DataResultViewer interface).
*
* A typical implementation of this interface are the NetBeans TopComponents
* (DataResultTopComponents) that use a child result view component
* (DataResultPanel) for displaying their result viewers, and are docked into
* the upper right hand side (editor mode) of the main application window.
*/
public interface DataResult {
/**
* Sets the "selected" node in this class.
* Sets the node for which this result view component should provide
* multiple views of the underlying application data.
*
* @param node The node, may be null. If null, the call to this method is
* equivalent to a call to resetComponent on this result view
* component's result viewers.
*/
public void setNode(Node selectedNode);
public void setNode(Node node);
/**
* Gets the unique TopComponent ID of this class.
* Gets the preferred identifier for this result view panel in the window
* system.
*
* @return preferredID the unique ID
* @return The preferred identifier.
*/
public String getPreferredID();
/**
* Sets the title of this TopComponent
* Sets the title of this result view component.
*
* @param title the given title (String)
* @param title The title.
*/
public void setTitle(String title);
/**
* Sets the descriptive context text at the top of the pane.
* Sets the descriptive text about the source of the nodes displayed in this
* result view component.
*
* @param pathText Descriptive text giving context for the current results
* @param description The text to display.
*/
public void setPath(String pathText);
/**
* Checks if this is the main (uncloseable) instance of DataResult
* Gets whether or not this result view panel is the "main" result view
* panel used to view the child nodes of a node selected in the application
* tree view (DirectoryTreeTopComponent) that is normally docked into the
* left hand side of the main window.
*
* @return true if it is the main instance, otherwise false
* @return True or false.
*/
public boolean isMain();

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-17 Basis Technology Corp.
* Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,76 +22,120 @@ import java.awt.Component;
import org.openide.nodes.Node;
/**
* Interface for the different viewers that show a set of nodes in the
* DataResult area. AbstractDataResultViewer has default implementations for the
* action handlers.
* An interface for result viewers. A result viewer uses a Swing Component to
* provide a view of the application data represented by a NetBeans Node passed
* to it via its setNode method.
*
* Result viewers are most commonly presented as a tab in a result view panel
* (DataResultPanel) inside a result view top component (DataResultTopComponent)
* that is docked into the upper right hand side (editor mode) of the main
* application window.
*
* A result viewer is typically a JPanel that displays the child nodes of the
* given node using a NetBeans explorer view child component. Such a result
* viewer should use the explorer manager of the ancestor top component to
* connect the lookups of the nodes displayed in the NetBeans explorer view to
* the actions global context. It is strongly recommended that this type of
* result viewer is implemented by extending the abstract base class
* AbstractDataResultViewer, which will handle some key aspects of working with
* the ancestor top component's explorer manager.
*
* This interface is an extension point, so classes that implement it should
* provide an appropriate ServiceProvider annotation.
*/
public interface DataResultViewer {
/**
* Set the root node to display in this viewer. When called with null, must
* clear all references to previous nodes.
*/
public void setNode(Node selectedNode);
/**
* Gets the title of this viewer
*/
public String getTitle();
/**
* Get a new instance of DataResultViewer
* Creates a new instance of this result viewer, which allows the
* application to use the capability provided by this result viewer in more
* than one result view. This is done by using the default instance of this
* result viewer as a "factory" for creating other instances.
*/
public DataResultViewer createInstance();
/**
* Get the Swing component (i.e. JPanel) for this viewer
* Indicates whether this result viewer is able to provide a meaningful view
* of the application data represented by a given node. Typically, indicates
* whether or not this result viewer can use the given node as the root node
* of its child explorer view component.
*
* @param node The node.
*
* @return True or false.
*/
public boolean isSupported(Node node);
/**
* Sets the node for which this result viewer should provide a view of the
* underlying application data. Typically, this means using the given node
* as the root node of this result viewer's child explorer view component.
*
* @param node The node, may be null. If null, the call to this method is
* equivalent to a call to resetComponent.
*/
public void setNode(Node node);
/**
* Requests selection of the given child nodes of the node passed to
* setNode. This method should be implemented as a no-op for result viewers
* that do not display the child nodes of a given root node using a NetBeans
* explorer view set up to use a given explorer manager.
*
* @param selectedNodes The child nodes to select.
*/
default public void setSelectedNodes(Node[] selectedNodes) {
}
/**
* Gets the title of this result viewer.
*
* @return The title.
*/
public String getTitle();
/**
* Gets the Swing component for this viewer.
*
* @return The component.
*/
public Component getComponent();
/**
* Resets the viewer.
* Resets the state of the Swing component for this viewer to its default
* state.
*/
public void resetComponent();
default public void resetComponent() {
}
/**
* Frees the objects that have been allocated by this viewer, in preparation
* for permanently disposing of it.
* Frees any resources tha have been allocated by this result viewer, in
* preparation for permanently disposing of it.
*/
public void clearComponent();
default public void clearComponent() {
}
/**
* Expand node, if supported by the viewed
* Sets the node for which this result viewer should provide a view of the
* underlying application data model object, and expands the node.
*
* @param n Node to expand
*/
public void expandNode(Node n);
/**
* Select the given node array
*/
public void setSelectedNodes(Node[] selected);
/**
* Checks whether the currently selected root node is supported by this
* viewer
* @param node The node.
*
* @param selectedNode the selected node
*
* @return True if supported, else false
*/
public boolean isSupported(Node selectedNode);
/**
* Set a custom content viewer to respond to selection events from this
* result viewer. If not set, the default content viewer is used
*
* @param contentViewer content viewer to respond to selection events from
* this viewer
*
* @deprecated All implementations of this in the standard DataResultViewers are now no-ops.
* @deprecated This API is not used by the application.
*/
@Deprecated
public void setContentViewer(DataContent contentViewer);
default public void expandNode(Node node) {
}
/**
* Sets a custom content viewer to which nodes selected in this result
* viewer should be pushed via DataContent.setNode.
*
* @param contentViewer The content viewer.
*
* @deprecated This API is not used by the application.
*/
@Deprecated
default public void setContentViewer(DataContent contentViewer) {
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-17 Basis Technology Corp.
* Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -23,65 +23,64 @@ import java.beans.PropertyVetoException;
import java.util.logging.Level;
import javax.swing.JPanel;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerManager.Provider;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* This class provides a default implementation of selected methods of the
* DataResultViewer interface. Derived classes will be Swing JPanel objects.
* Additionally, the ExplorerManager.Provider interface is implemented to supply
* an ExplorerManager to derived classes and their child components.
* An abstract base class for an implementation of the result viewer interface
* that is a JPanel that displays the child nodes of a given node using a
* NetBeans explorer view as a child component. Such a result viewer should use
* the explorer manager of an ancestor top component to connect the lookups of
* the nodes displayed in the NetBeans explorer view to the actions global
* context. This class handles some key aspects of working with the ancestor top
* component's explorer manager.
*
* Instances of this class can be supplied with the top component's explorer
* manager during construction, but the typical use case is for the result
* viewer to find the ancestor top component's explorer manager at runtime.
*
* IMPORTANT: If the result viewer is going to find the ancestor top component's
* explorer manager at runtime, the first call to the getExplorerManager method
* of this class must be made AFTER the component hierarchy is fully
* constructed.
*
*/
abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider {
public abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, ExplorerManager.Provider {
private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName());
protected transient ExplorerManager em;
private transient ExplorerManager explorerManager;
/**
* This constructor is intended to allow an AbstractDataResultViewer to use
* an ExplorerManager provided by a TopComponent, allowing Node selections
* to be available to Actions via the action global context lookup when the
* TopComponent has focus. The ExplorerManager must be present when the
* object is constructed so that its child components can discover it using
* the ExplorerManager.find() method.
* Constructs an abstract base class for an implementation of the result
* viewer interface that is a JPanel that displays the child nodes of the
* given node using a NetBeans explorer view as a child component.
*
* @param explorerManager
* @param explorerManager The explorer manager to use in the NetBeans
* explorer view child component of this result
* viewer, may be null. If null, the explorer manager
* will be discovered the first time
* getExplorerManager is called.
*/
AbstractDataResultViewer(ExplorerManager explorerManager) {
this.em = explorerManager;
}
/**
* This constructor can be used by AbstractDataResultViewers that do not
* need to make Node selections available to Actions via the action global
* context lookup.
*/
public AbstractDataResultViewer() {
this(new ExplorerManager());
public AbstractDataResultViewer(ExplorerManager explorerManager) {
this.explorerManager = explorerManager;
}
@Override
public void clearComponent() {
public ExplorerManager getExplorerManager() {
if (this.explorerManager == null) {
this.explorerManager = ExplorerManager.find(this);
}
public Node getSelectedNode() {
Node result = null;
Node[] selectedNodes = this.getExplorerManager().getSelectedNodes();
if (selectedNodes.length > 0) {
result = selectedNodes[0];
}
return result;
return this.explorerManager;
}
@Override
public void expandNode(Node n) {
public void setSelectedNodes(Node[] selected) {
try {
this.getExplorerManager().setSelectedNodes(selected);
} catch (PropertyVetoException ex) {
logger.log(Level.SEVERE, "Couldn't set selected nodes", ex); //NON-NLS
}
@Override
public void resetComponent() {
}
@Override
@ -89,22 +88,4 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView
return this;
}
@Override
public ExplorerManager getExplorerManager() {
return this.em;
}
@Override
public void setSelectedNodes(Node[] selected) {
try {
this.em.setSelectedNodes(selected);
} catch (PropertyVetoException ex) {
logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS
}
}
@Deprecated
@Override
public void setContentViewer(DataContent contentViewer) {
}
}

View File

@ -62,9 +62,6 @@ DataResultViewerThumbnail.filePathLabel.text=\ \ \
DataResultViewerThumbnail.goToPageLabel.text=Go to Page:
DataResultViewerThumbnail.goToPageField.text=
AdvancedConfigurationDialog.cancelButton.text=Cancel
DataResultPanel.directoryTablePath.text=directoryPath
DataResultPanel.numberMatchLabel.text=0
DataResultPanel.matchLabel.text=Results
DataContentViewerArtifact.waitText=Retrieving and preparing data, please wait...
DataContentViewerArtifact.errorText=Error retrieving result
DataContentViewerArtifact.title=Results
@ -181,3 +178,6 @@ AutopsyOptionsPanel.agencyLogoPreview.text=<html><div style='text-align: center;
AutopsyOptionsPanel.logoPanel.border.title=Logo
AutopsyOptionsPanel.runtimePanel.border.title=Runtime
AutopsyOptionsPanel.viewPanel.border.title=View
DataResultPanel.matchLabel.text=Results
DataResultPanel.numberOfChildNodesLabel.text=0
DataResultPanel.descriptionLabel.text=directoryPath

View File

@ -37,9 +37,6 @@ DataResultViewerThumbnail.imagesRangeLabel.text=-
DataResultViewerThumbnail.pageNumLabel.text=-
DataResultViewerThumbnail.goToPageLabel.text=\u6b21\u306e\u30da\u30fc\u30b8\u306b\u79fb\u52d5\uff1a
AdvancedConfigurationDialog.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb
DataResultPanel.directoryTablePath.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30d1\u30b9
DataResultPanel.numberMatchLabel.text=0
DataResultPanel.matchLabel.text=\u7d50\u679c
DataContentViewerArtifact.waitText=\u30c7\u30fc\u30bf\u3092\u53d6\u8fbc\u307f\u304a\u3088\u3073\u6e96\u5099\u4e2d\u3002\u3057\u3070\u3089\u304f\u304a\u5f85\u3061\u4e0b\u3055\u3044...
DataContentViewerArtifact.errorText=\u7d50\u679c\u3092\u53d6\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
DataContentViewerArtifact.title=\u7d50\u679c
@ -131,3 +128,6 @@ DataResultViewerThumbnail.thumbnailSizeComboBox.small=\u30b5\u30e0\u30cd\u30a4\u
MediaViewImagePanel.errorLabel.OOMText=\u30d5\u30a1\u30a4\u30eb\u3092\u30e1\u30c7\u30a3\u30a2\u30d3\u30e5\u30fc\u306b\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\uff1a\u30e1\u30e2\u30ea\u4e0d\u8db3\u3002
MediaViewImagePanel.errorLabel.text=\u30d5\u30a1\u30a4\u30eb\u3092\u30e1\u30c7\u30a3\u30a2\u30d3\u30e5\u30fc\u306b\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\u3002
MediaViewImagePanel.externalViewerButton.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u30fc\u3067\u958b\u304f
DataResultPanel.matchLabel.text=\u7d50\u679c
DataResultPanel.numberOfChildNodesLabel.text=0
DataResultPanel.descriptionLabel.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30d1\u30b9

View File

@ -483,7 +483,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
// file and not more details about the artifact
if ((artifact == null)
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID())) {
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())) {
return 3;
} else {
return 6;

View File

@ -25,13 +25,13 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="directoryTablePath" min="-2" max="-2" attributes="0"/>
<Component id="descriptionLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="numberMatchLabel" min="-2" max="-2" attributes="0"/>
<Component id="numberOfChildNodesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="matchLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="dataResultTabbedPanel" max="32767" attributes="0"/>
<Component id="resultViewerTabs" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
@ -39,32 +39,32 @@
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" alignment="0" groupAlignment="3" attributes="0">
<Component id="numberMatchLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="numberOfChildNodesLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="matchLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="directoryTablePath" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="dataResultTabbedPanel" max="32767" attributes="0"/>
<Component id="resultViewerTabs" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="directoryTablePath">
<Component class="javax.swing.JLabel" name="descriptionLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.directoryTablePath.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.descriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[5, 14]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="numberMatchLabel">
<Component class="javax.swing.JLabel" name="numberOfChildNodesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.numberMatchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.numberOfChildNodesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
@ -75,7 +75,7 @@
</Property>
</Properties>
</Component>
<Container class="javax.swing.JTabbedPane" name="dataResultTabbedPanel">
<Container class="javax.swing.JTabbedPane" name="resultViewerTabs">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 5]"/>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,6 +22,7 @@ import java.awt.Cursor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.swing.JTabbedPane;
@ -44,153 +45,204 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
/**
* A Swing JPanel with a JTabbedPane child component. The tabbed pane contains
* result viewers.
* A result view panel is a JPanel with a JTabbedPane child component that
* contains a collection of result viewers and implements the DataResult
* interface. The result viewers in a result view panel are either supplied
* during construction of the panel or are obtained from the result viewer
* extension point (DataResultViewer service providers).
*
* The "main" DataResultPanel for the desktop application has a table viewer
* (DataResultViewerTable) and a thumbnail viewer (DataResultViewerThumbnail),
* plus zero to many additional DataResultViewers, since the DataResultViewer
* interface is an extension point.
* A result view panel provides an implementation of the setNode API of the the
* DataResult interface that pushes a given NetBeans Node into its child result
* viewers via the DataResultViewer.setNode API. The result viewers are
* responsible for providing a view of the application data represented by the
* node. A typical result viewer is a JPanel that displays the child nodes of
* the given node using a NetBeans explorer view child component.
*
* The "main" DataResultPanel resides in the "main" results view
* (DataResultTopComponent) that is normally docked into the upper right hand
* side of the main window of the desktop application.
* A result panel should be child components of top components that are explorer
* manager providers. The parent top component is expected to expose a lookup
* maintained by its explorer manager to the actions global context. The child
* result view panel will then find the parent top component's explorer manager
* at runtime, so that it can act as an explorer manager provider for its child
* result viewers. This connects the nodes displayed in the result viewers to
* the actions global context.
*
* The result viewers in the "main panel" are used to view the child nodes of a
* node selected in the tree view (DirectoryTreeTopComponent) that is normally
* docked into the left hand side of the main window of the desktop application.
*
* Nodes selected in the child results viewers of a DataResultPanel are
* displayed in a content view (implementation of the DataContent interface)
* supplied the panel. The default content view is (DataContentTopComponent) is
* normally docked into the lower right hand side of the main window, underneath
* the results view. A custom content view may be specified instead.
* All result view panels push single node selections in the child result
* viewers to either the "main" content view (DataContentTopComponent) that is
* normally docked into the lower right hand side of the main application
* window, or to a supplied custom content view (implements DataContent).
*/
public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider {
private static final long serialVersionUID = 1L;
private static final int NO_TAB_SELECTED = -1;
private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName");
private final List<DataResultViewer> resultViewers = new ArrayList<>();
private boolean isMain;
private ExplorerManager explorerManager;
private ExplorerManagerNodeSelectionListener emNodeSelectionListener;
private Node rootNode;
private final RootNodeListener rootNodeListener = new RootNodeListener();
private boolean listeningToTabbedPane;
private final boolean isMain;
private final List<DataResultViewer> resultViewers;
private final ExplorerManagerListener explorerManagerListener;
private final RootNodeListener rootNodeListener;
private DataContent contentView;
private ExplorerManager explorerManager;
private Node currentRootNode;
private boolean listeningToTabbedPane;
/**
* Constructs and opens a DataResultPanel with the given initial data, and
* the default DataContent.
* Creates and opens a Swing JPanel with a JTabbedPane child component that
* contains instances of the result viewers (DataResultViewer) provided by
* the result viewer extension point (service providers that implement
* DataResultViewer). The result view panel will push single node selections
* from its child result viewers to the "main" content view that is normally
* docked into the lower right hand side of the main application window.
*
* @param title The title for the panel.
* @param pathText Descriptive text about the source of the nodes
* @param title The title for the result view panel.
* @param description Descriptive text about the source of the nodes
* displayed.
* @param rootNode The new root node.
* @param totalMatches Cardinality of root node's children
* @param currentRootNode The current root (parent) node for the nodes
* displayed. May be changed by calling setNode.
* @param childNodeCount The cardinality of the root node's children.
*
* @return A DataResultPanel instance.
* @return A result view panel.
*/
public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches) {
DataResultPanel resultPanel = new DataResultPanel(title, false);
createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel);
public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount) {
DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), null);
createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
resultPanel.open();
return resultPanel;
}
/**
* Constructs and opens a DataResultPanel with the given initial data, and a
* custom DataContent.
* Creates and opens a Swing JPanel with a JTabbedPane child component that
* contains a given collection of result viewers (DataResultViewer) instead
* of the result viewers provided by the results viewer extension point. The
* result view panel will push single node selections from its child result
* viewers to the "main" content view that is normally docked into the lower
* right hand side of the main application window..
*
* @param title The title for the panel.
* @param pathText Descriptive text about the source of the nodes
* @param title The title for the result view panel.
* @param description Descriptive text about the source of the nodes
* displayed.
* @param rootNode The new root node.
* @param totalMatches Cardinality of root node's children
* @param customContentView A content view to use in place of the default
* content view.
* @param currentRootNode The current root (parent) node for the nodes
* displayed. May be changed by calling setNode.
* @param childNodeCount The cardinality of the root node's children.
* @param viewers A collection of result viewers to use instead of
* the result viewers provided by the results viewer
* extension point.
*
* @return A DataResultPanel instance.
* @return A result view panel.
*/
public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) {
DataResultPanel resultPanel = new DataResultPanel(title, customContentView);
createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel);
public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, Collection<DataResultViewer> viewers) {
DataResultPanel resultPanel = new DataResultPanel(title, false, viewers, null);
createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
resultPanel.open();
return resultPanel;
}
/**
* Constructs a DataResultPanel with the given initial data, and a custom
* DataContent. The panel is NOT opened; the client of this method must call
* open on the panel that is returned.
* Creates and opens a Swing JPanel with a JTabbedPane child component that
* contains instances of the result viewers (DataResultViewer) provided by
* the result viewer extension point (service providers that implement
* DataResultViewer). The result view panel will push single node selections
* from its child result viewers to the supplied custom content view.
*
* @param title The title for the panel.
* @param pathText Descriptive text about the source of the nodes
* @param title The title for the result view panel.
* @param description Descriptive text about the source of the nodes
* displayed.
* @param rootNode The new root node.
* @param totalMatches Cardinality of root node's children
* @param customContentView A content view to use in place of the default
* content view.
* @param currentRootNode The current root (parent) node for the nodes
* displayed. May be changed by calling setNode.
* @param childNodeCount The cardinality of the root node's children.
* @param customContentView A custom content view to use instead of the
* "main" content view that is normally docked into
* the lower right hand side of the main
* application window.
*
* @return A DataResultPanel instance.
* @return A result view panel.
*/
public static DataResultPanel createInstanceUninitialized(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) {
DataResultPanel resultPanel = new DataResultPanel(title, customContentView);
createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel);
public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) {
DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView);
createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
resultPanel.open();
return resultPanel;
}
/**
* Executes code common to all of the DataSreultPanel factory methods.
* Creates, but does not open, a Swing JPanel with a JTabbedPane child
* component that contains instances of the result viewers
* (DataResultViewer) provided by the result viewer extension point (service
* providers that implement DataResultViewer). The result view panel will
* push single node selections from its child result viewers to the supplied
* custom content view.
*
* @param title The title for the panel.
* @param pathText Descriptive text about the source of the nodes
* @param title The title for the result view panel.
* @param description Descriptive text about the source of the nodes
* displayed.
* @param rootNode The new root node.
* @param totalMatches Cardinality of root node's children
* @param resultViewPanel A content view to use in place of the default
* @param currentRootNode The current root (parent) node for the nodes
* displayed. May be changed by calling setNode.
* @param childNodeCount The cardinality of the root node's children.
* @param customContentView A content view to use in place of the default
* content view.
*
* @return A result view panel.
*/
private static void createInstanceCommon(String title, String pathText, Node rootNode, int totalMatches, DataResultPanel resultViewPanel) {
public static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) {
DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView);
createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
return resultPanel;
}
/**
* Executes code common to all of the result view panel factory methods.
*
* @param title The title for the result view panel.
* @param description Descriptive text about the source of the nodes
* displayed.
* @param currentRootNode The current root (parent) node for the nodes
* displayed. May be changed by calling setNode.
* @param childNodeCount The cardinality of the root node's children.
* @param resultViewPanel A new results view panel.
*/
private static void createInstanceCommon(String title, String description, Node currentRootNode, int childNodeCount, DataResultPanel resultViewPanel) {
resultViewPanel.setTitle(title);
resultViewPanel.setName(title);
resultViewPanel.setNumMatches(totalMatches);
resultViewPanel.setNode(rootNode);
resultViewPanel.setPath(pathText);
resultViewPanel.setNumberOfChildNodes(childNodeCount);
resultViewPanel.setNode(currentRootNode);
resultViewPanel.setPath(description);
}
/**
* Constructs a DataResultPanel with the default DataContent
* Constructs a Swing JPanel with a JTabbedPane child component that
* contains a collection of result viewers that is either supplied or
* provided by the result viewer extension point.
*
* @param title The title for the panel.
* @param isMain True if the DataResultPanel being constructed is the "main"
* DataResultPanel.
* @param title The title of the result view panel.
* @param isMain Whether or not the result view panel is the
* "main" instance of the panel that resides in the
* "main" results view (DataResultTopComponent)
* that is normally docked into the upper right
* hand side of the main application window.
* @param viewers A collection of result viewers to use instead of
* the result viewers provided by the results
* viewer extension point, may be empty.
* @param customContentView A custom content view to use instead of the
* "main" content view that is normally docked into
* the lower right hand side of the main
* application window, may be null.
*/
DataResultPanel(String title, boolean isMain) {
this(isMain, Lookup.getDefault().lookup(DataContent.class));
setTitle(title);
}
private DataResultPanel(boolean isMain, DataContent contentView) {
DataResultPanel(String title, boolean isMain, Collection<DataResultViewer> viewers, DataContent customContentView) {
this.setTitle(title);
this.isMain = isMain;
this.contentView = contentView;
if (customContentView == null) {
this.contentView = Lookup.getDefault().lookup(DataContent.class);
} else {
this.contentView = customContentView;
}
this.resultViewers = new ArrayList<>(viewers);
this.explorerManagerListener = new ExplorerManagerListener();
this.rootNodeListener = new RootNodeListener();
initComponents();
}
/**
* Constructs a DataResultPanel with the a custom DataContent.
*
* @param title The title for the panel.
* @param customContentView A content view to use in place of the default
* content view.
*/
DataResultPanel(String title, DataContent customContentView) {
this(false, customContentView);
}
/**
* Gets the preferred identifier for this panel in the window system.
* Gets the preferred identifier for this result view panel in the window
* system.
*
* @return The preferred identifier.
*/
@ -200,19 +252,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/**
* Gets whether or not this panel is the "main" panel used to view the child
* nodes of a node selected in the tree view (DirectoryTreeTopComponent)
* that is normally docked into the left hand side of the main window.
*
* @return True or false.
*/
@Override
public boolean isMain() {
return this.isMain;
}
/**
* Sets the title of this panel.
* Sets the title of this result view panel.
*
* @param title The title.
*/
@ -223,27 +263,27 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
/**
* Sets the descriptive text about the source of the nodes displayed in this
* panel.
* result view panel.
*
* @param pathText The text to display.
* @param description The text to display.
*/
@Override
public void setPath(String pathText) {
this.directoryTablePath.setText(pathText);
public void setPath(String description) {
this.descriptionLabel.setText(description);
}
/**
* Adds a result viewer to this panel.
* Adds a results viewer to this result view panel.
*
* @param resultViewer The result viewer.
* @param resultViewer The results viewer.
*/
public void addResultViewer(DataResultViewer resultViewer) {
resultViewers.add(resultViewer);
dataResultTabbedPanel.addTab(resultViewer.getTitle(), resultViewer.getComponent());
resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent());
}
/**
* Gets the result viewers for this panel.
* Gets the result viewers for this result view panel.
*
* @return A list of result viewers.
*/
@ -253,8 +293,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/**
* Sets the content view for this panel. Needs to be called before the first
* call to open.
* Sets the content view for this result view panel. Needs to be called
* before the first call to open.
*
* @param customContentView A content view to use in place of the default
* content view.
@ -264,67 +304,60 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/**
* Initializes this panel. Intended to be called by a parent top component
* Opens this result view panel. Should be called by a parent top component
* when the top component is opened.
*/
public void open() {
if (null == explorerManager) {
/*
* Get an explorer manager to pass to the child result viewers. If
* the application components are put together as expected, this
* will be an explorer manager owned by a parent top component, and
* placed by the top component in the look up that is proxied as the
* action global context when the top component has focus. The
* sharing of this explorer manager enables the same child node
* selections to be made in all of the result viewers.
* The parent top component is expected to be an explorer manager
* provider that exposes a lookup maintained by its explorer manager to
* the actions global context. The child result view panel will then
* find the parent top component's explorer manager at runtime, so that
* it can act as an explorer manager provider for its child result
* viewers. This connects the nodes displayed in the result viewers to
* the actions global context.
*/
explorerManager = ExplorerManager.find(this);
emNodeSelectionListener = new ExplorerManagerNodeSelectionListener();
explorerManager.addPropertyChangeListener(emNodeSelectionListener);
if (this.explorerManager == null) {
this.explorerManager = ExplorerManager.find(this);
this.explorerManager.addPropertyChangeListener(this.explorerManagerListener);
}
/*
* Load the child result viewers into the tabbed pane.
* Load either the supplied result viewers or the result viewers
* provided by the result viewer extension point into the tabbed pane.
* If loading from the extension point and distinct result viewer
* instances MUST be created if this is not the "main" result view.
*/
if (0 == dataResultTabbedPanel.getTabCount()) {
/*
* TODO (JIRA-2658): Fix the DataResultViewer extension point. When
* this is done, restore the implementation of DataResultViewerTable
* and DataREsultViewerThumbnail as DataResultViewer service
* providers.
*/
addResultViewer(new DataResultViewerTable(this.explorerManager));
//addResultViewer(new DataResultViewerThumbnail(this.explorerManager));
for (DataResultViewer factory : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
DataResultViewer resultViewer;
if (isMain) {
resultViewer = factory;
if (this.resultViewerTabs.getTabCount() == 0) {
if (this.resultViewers.isEmpty()) {
for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
if (this.isMain) {
this.resultViewers.add(resultViewer);
} else {
resultViewer = factory.createInstance();
}
addResultViewer(resultViewer);
this.resultViewers.add(resultViewer.createInstance());
}
}
if (isMain && null == rootNode) {
setNode(rootNode);
}
this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()));
}
this.setVisible(true);
}
/**
* Sets the root node for this panel. The child nodes of the root node will
* be displayed in the result viewers. For the "main" panel, the root node
* is the currently selected node in the tree view docked into the left side
* of the main application window.
* Sets the current root node for this result view panel. The child nodes of
* the current root node will be displayed in the child result viewers. For
* the "main" panel, the root node is the currently selected node in the
* application tree view docked into the left side of the main application
* window.
*
* @param rootNode The root node for this panel.
* @param rootNode The root node for this panel, may be null if the panel is
* to be reset.
*/
@Override
public void setNode(Node rootNode) {
if (this.rootNode != null) {
this.rootNode.removeNodeListener(rootNodeListener);
if (this.currentRootNode != null) {
this.currentRootNode.removeNodeListener(rootNodeListener);
}
/*
@ -333,53 +366,54 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* construction.
*/
if (listeningToTabbedPane == false) {
dataResultTabbedPanel.addChangeListener(this);
resultViewerTabs.addChangeListener(this);
listeningToTabbedPane = true;
}
this.rootNode = rootNode;
if (this.rootNode != null) {
this.currentRootNode = rootNode;
if (this.currentRootNode != null) {
rootNodeListener.reset();
this.rootNode.addNodeListener(rootNodeListener);
this.currentRootNode.addNodeListener(rootNodeListener);
}
resetTabs(this.rootNode);
setupTabs(this.rootNode);
this.resultViewers.forEach((viewer) -> {
viewer.resetComponent();
});
setupTabs(this.currentRootNode);
if (null != this.rootNode) {
int childrenCount = this.rootNode.getChildren().getNodesCount();
this.numberMatchLabel.setText(Integer.toString(childrenCount));
if (this.currentRootNode != null) {
int childrenCount = this.currentRootNode.getChildren().getNodesCount();
this.numberOfChildNodesLabel.setText(Integer.toString(childrenCount));
}
this.numberMatchLabel.setVisible(true);
this.numberOfChildNodesLabel.setVisible(true);
}
/**
* Gets the root node of this panel. For the "main" panel, the root node is
* the currently selected node in the tree view docked into the left side of
* the main application window.
* Gets the root node of this result view panel. For the "main" panel, the
* root node is the currently selected node in the application tree view
* docked into the left side of the main application window.
*
* @return The root node.
*/
public Node getRootNode() {
return rootNode;
return currentRootNode;
}
/**
* Set number of child nodes displayed for the current root node.
* Sets the label text that displays the number of the child nodes displayed
* by this result view panel's result viewers.
*
* @param numberOfChildNodes
* @param numberOfChildNodes The number of child nodes.
*/
public void setNumMatches(Integer numberOfChildNodes) {
if (this.numberMatchLabel != null) {
this.numberMatchLabel.setText(Integer.toString(numberOfChildNodes));
}
public void setNumberOfChildNodes(Integer numberOfChildNodes) {
this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes));
}
/**
* Sets the children of the root node that should be currently selected in
* this panel's result viewers.
* Selects the given child nodes of the root node in this panel's result
* viewers.
*
* @param selectedNodes The nodes to be selected.
* @param selectedNodes The child nodes to be selected.
*/
public void setSelectedNodes(Node[] selectedNodes) {
this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes));
@ -396,11 +430,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* Enable or disable the result viewer tabs based on whether or not the
* corresponding results viewer supports display of the selected node.
*/
for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) {
for (int i = 0; i < resultViewerTabs.getTabCount(); i++) {
if (resultViewers.get(i).isSupported(selectedNode)) {
dataResultTabbedPanel.setEnabledAt(i, true);
resultViewerTabs.setEnabledAt(i, true);
} else {
dataResultTabbedPanel.setEnabledAt(i, false);
resultViewerTabs.setEnabledAt(i, false);
}
}
@ -415,17 +449,17 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) {
for (int i = 0; i < resultViewers.size(); ++i) {
if (resultViewers.get(i) instanceof DataResultViewerTable && dataResultTabbedPanel.isEnabledAt(i)) {
if (resultViewers.get(i) instanceof DataResultViewerTable && resultViewerTabs.isEnabledAt(i)) {
tabToSelect = i;
}
}
}
};
if (NO_TAB_SELECTED == tabToSelect) {
tabToSelect = dataResultTabbedPanel.getSelectedIndex();
if ((NO_TAB_SELECTED == tabToSelect) || (!dataResultTabbedPanel.isEnabledAt(tabToSelect))) {
for (int i = 0; i < dataResultTabbedPanel.getTabCount(); ++i) {
if (dataResultTabbedPanel.isEnabledAt(i)) {
if (tabToSelect == NO_TAB_SELECTED) {
tabToSelect = resultViewerTabs.getSelectedIndex();
if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) {
for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) {
if (resultViewerTabs.isEnabledAt(i)) {
tabToSelect = i;
break;
}
@ -434,27 +468,15 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/*
* If there is a tab to sele3ct, do so, and push the selected node to
* the corresponding result viewer.
* If there is a tab to select, do so, and push the selected node to the
* corresponding result viewer.
*/
if (NO_TAB_SELECTED != tabToSelect) {
dataResultTabbedPanel.setSelectedIndex(tabToSelect);
if (tabToSelect != NO_TAB_SELECTED) {
resultViewerTabs.setSelectedIndex(tabToSelect);
resultViewers.get(tabToSelect).setNode(selectedNode);
}
}
/**
* Resets the state of the child result viewers, based on a selected root
* node.
*
* @param unusedSelectedNode The selected node.
*/
public void resetTabs(Node unusedSelectedNode) {
this.resultViewers.forEach((viewer) -> {
viewer.resetComponent();
});
}
/**
* Responds to a tab selection changed event by setting the root node of the
* corresponding result viewer.
@ -465,57 +487,34 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
public void stateChanged(ChangeEvent event) {
JTabbedPane pane = (JTabbedPane) event.getSource();
int currentTab = pane.getSelectedIndex();
if (-1 != currentTab) {
if (currentTab != DataResultPanel.NO_TAB_SELECTED) {
DataResultViewer currentViewer = this.resultViewers.get(currentTab);
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
currentViewer.setNode(rootNode);
currentViewer.setNode(currentRootNode);
} finally {
this.setCursor(null);
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
}
/**
* Indicates whether or not this panel can be closed at the time of the
* call.
*
* @return True or false.
*/
public boolean canClose() {
/*
* If this is the "main" panel, only allow it to be closed when no case
* is open or no there are no data sources in the current case.
*/
Case openCase;
try {
openCase = Case.getOpenCase();
} catch (NoCurrentCaseException ex) {
return true;
}
return (!this.isMain) || openCase.hasData() == false;
}
/**
* Closes down the component. Intended to be called by the parent top
* Closes this reult view panel. Intended to be called by the parent top
* component when it is closed.
*/
void close() {
if (null != explorerManager && null != emNodeSelectionListener) {
explorerManager.removePropertyChangeListener(emNodeSelectionListener);
if (explorerManager != null && explorerManagerListener != null) {
explorerManager.removePropertyChangeListener(explorerManagerListener);
explorerManager = null;
}
this.resultViewers.forEach((viewer) -> viewer.setNode(null));
if (!this.isMain) {
if (!this.isMain) { // RJCTODO: What?
this.resultViewers.forEach(DataResultViewer::clearComponent);
this.directoryTablePath.removeAll();
this.directoryTablePath = null;
this.numberMatchLabel.removeAll();
this.numberMatchLabel = null;
this.descriptionLabel.removeAll();
this.numberOfChildNodesLabel.removeAll();
this.matchLabel.removeAll();
this.matchLabel = null;
this.setLayout(null);
this.removeAll();
this.setVisible(false);
@ -525,52 +524,39 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
@Override
public ExplorerManager getExplorerManager() {
return explorerManager;
}
/**
* Responds to node selection change events from the explorer manager.
* Responds to node selection change events from the explorer manager of
* this panel's parent top component. The selected nodes are passed to the
* content view. This is how the results view and the content view are kept
* in sync. It is therefore required that all of the result viewers in this
* panel use the explorer manager of the parent top component. This supports
* this way of passing the selection to the content view, plus the exposure
* of the selection to through the actions global context, which is needed
* for multiple selection.
*/
private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener {
private class ExplorerManagerListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
try {
Case.getOpenCase();
} catch (NoCurrentCaseException ex) {
return;
}
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) {
/*
* Only interested in node selection events.
* Pass a single node selection in a result viewer to the
* content view. Note that passing null to the content view
* signals that either multiple nodes are selected, or a
* previous selection has been cleared. This is important to the
* content view, since its child content viewers only work for a
* single node.
*/
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
if (contentView != null) {
Node[] selectedNodes = explorerManager.getSelectedNodes();
/*
* Pass the selected nodes to all of the result viewers
* sharing this explorer manager.
*/
resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes));
/*
* Passing null signals that either multiple nodes are
* selected, or no nodes are selected. This is important
* to the content view, since content views only work
* for a single node..
*/
if (1 == selectedNodes.length) {
contentView.setNode(selectedNodes[0]);
} else {
contentView.setNode(null);
}
}
} finally {
setCursor(null);
}
}
}
}
@ -598,6 +584,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* Responds to changes in the root node due to asynchronous child node
* creation.
*/
// RJCTODO: Why do we need this?
private class RootNodeListener implements NodeListener {
private volatile boolean waitingForData = true;
@ -638,8 +625,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
*
*/
private void updateMatches() {
if (rootNode != null && rootNode.getChildren() != null) {
setNumMatches(rootNode.getChildren().getNodesCount());
if (currentRootNode != null && currentRootNode.getChildren() != null) {
setNumMatches(currentRootNode.getChildren().getNodesCount());
}
}
@ -670,52 +657,93 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
directoryTablePath = new javax.swing.JLabel();
numberMatchLabel = new javax.swing.JLabel();
descriptionLabel = new javax.swing.JLabel();
numberOfChildNodesLabel = new javax.swing.JLabel();
matchLabel = new javax.swing.JLabel();
dataResultTabbedPanel = new javax.swing.JTabbedPane();
resultViewerTabs = new javax.swing.JTabbedPane();
setMinimumSize(new java.awt.Dimension(0, 5));
setPreferredSize(new java.awt.Dimension(5, 5));
org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N
directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14));
org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N
descriptionLabel.setMinimumSize(new java.awt.Dimension(5, 14));
org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N
dataResultTabbedPanel.setMinimumSize(new java.awt.Dimension(0, 5));
resultViewerTabs.setMinimumSize(new java.awt.Dimension(0, 5));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(numberMatchLabel)
.addComponent(numberOfChildNodesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(matchLabel))
.addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(numberMatchLabel)
.addComponent(numberOfChildNodesLabel)
.addComponent(matchLabel))
.addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(0, 0, 0)
.addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTabbedPane dataResultTabbedPanel;
private javax.swing.JLabel directoryTablePath;
private javax.swing.JLabel descriptionLabel;
private javax.swing.JLabel matchLabel;
private javax.swing.JLabel numberMatchLabel;
private javax.swing.JLabel numberOfChildNodesLabel;
private javax.swing.JTabbedPane resultViewerTabs;
// End of variables declaration//GEN-END:variables
/**
* Gets whether or not this result view panel is the "main" result view
* panel used to view the child nodes of a node selected in the application
* tree view (DirectoryTreeTopComponent) that is normally docked into the
* left hand side of the main window.
*
* @return True or false.
*
* @Deprecated This method has no valid use case.
*/
@Deprecated
@Override
public boolean isMain() {
return this.isMain;
}
/**
* Sets the label text that displays the number of the child nodes displayed
* by this result view panel's result viewers.
*
* @param numberOfChildNodes The number of child nodes.
*
* @deprecated Use setNumberOfChildNodes instead.
*/
@Deprecated
public void setNumMatches(Integer numberOfChildNodes) {
this.setNumberOfChildNodes(numberOfChildNodes);
}
/**
* Resets the state of this results panel.
*
* @param unusedSelectedNode Unused.
*
* @deprecated Use setNode(null) instead.
*/
@Deprecated
public void resetTabs(Node unusedSelectedNode) {
this.setNode(null);
}
}

View File

@ -28,7 +28,7 @@
<SubComponents>
<Component class="org.sleuthkit.autopsy.corecomponents.DataResultPanel" name="dataResultPanelLocal">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="dataResultPanel;"/>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="resultViewersPanel;"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.corecomponents;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
@ -39,201 +40,237 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Top component which displays results (top-right editor mode by default).
* A DataResultTopComponent object is a NetBeans top component that provides
* multiple views of the application data represented by a NetBeans Node. It is
* a result view component (implements DataResult) that contains a result view
* panel (DataResultPanel), which is also a result view component. The result
* view panel is a JPanel with a JTabbedPane child component that contains a
* collection of result viewers. Each result viewer (implements
* DataResultViewer) presents a different view of the current node. Result
* viewers are usually JPanels that display the child nodes of the current node
* using a NetBeans explorer view child component. The result viewers are either
* supplied during construction of the result view top component or provided by
* the result viewer extension point (service providers that implement
* DataResultViewer).
*
* There is a main tc instance that responds to directory tree selections.
* Others can also create an additional result viewer tc using one of the
* factory methods, that can be:
* Result view top components are typically docked into the upper right hand
* side of the main application window (editor mode), and are linked to the
* content view in the lower right hand side of the main application window
* (output mode) by the result view panel. The panel passes single node
* selections in the active result viewer to the content view.
*
* - added to top-right corner as an additional, closeable viewer - added to a
* different, custom mode, - linked to a custom content viewer that responds to
* selections from this top component.
* The "main" result view top component receives its current node as a selection
* from the case tree view in the top component (DirectoryTreeYopComponent)
* docked into the left hand side of the main application window.
*
* For embedding custom data result in other top components window, use
* DataResultPanel component instead, since we cannot nest top components.
*
* Encapsulates the internal DataResultPanel and delegates to it.
*
* Implements DataResult interface by delegating to the encapsulated
* DataResultPanel.
* Result view top components are explorer manager providers to connect the
* lookups of the nodes displayed in the NetBeans explorer views of the result
* viewers to the actions global context.
*/
@RetainLocation("editor")
public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider {
private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName());
private final ExplorerManager explorerManager = new ExplorerManager();
private final DataResultPanel dataResultPanel; //embedded component with all the logic
private boolean isMain;
private String customModeName;
//keep track of tcs opened for menu presenters
private static final List<String> activeComponentIds = Collections.synchronizedList(new ArrayList<String>());
private final boolean isMain;
private final String customModeName;
private final ExplorerManager explorerManager;
private final DataResultPanel dataResultPanel;
/**
* Create a new data result top component
* Creates a result view top component that provides multiple views of the
* application data represented by a NetBeans Node. The result view will be
* docked into the upper right hand side of the main application window
* (editor mode) and will be linked to the content view in the lower right
* hand side of the main application window (output mode). Its result
* viewers are provided by the result viewer extension point (service
* providers that implement DataResultViewer).
*
* @param isMain whether it is the main, application default result viewer,
* there can be only 1 main result viewer
* @param title title of the data result window
* @param title The title for the top component, appears on the top
* component's tab.
* @param description Descriptive text about the node displayed, appears
* on the top component's tab
* @param node The node to display.
* @param childNodeCount The cardinality of the node's children.
*
* @return The result view top component.
*/
public DataResultTopComponent(boolean isMain, String title) {
associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
this.dataResultPanel = new DataResultPanel(title, isMain);
initComponents();
customizeComponent(isMain, title);
public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount) {
DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null);
initInstance(description, node, childNodeCount, resultViewTopComponent);
return resultViewTopComponent;
}
/**
* Create a new, custom data result top component, in addition to the
* application main one
* Creates a result view top component that provides multiple views of the
* application data represented by a NetBeans Node. The result view will be
* docked into the upper right hand side of the main application window
* (editor mode) and will be linked to the content view in the lower right
* hand side of the main application window (output mode).
*
* @param name unique name of the data result window, also
* used as title
* @param mode custom mode to dock into
* @param customContentViewer custom content viewer to send selection events
* to
* @param title The title for the top component, appears on the top
* component's tab.
* @param description Descriptive text about the node displayed, appears
* on the top component's tab
* @param node The node to display.
* @param childNodeCount The cardinality of the node's children.
* @param viewers A collection of result viewers to use instead of
* the result viewers provided by the results viewer
* extension point.
*
* @return The result view top component.
*/
DataResultTopComponent(String name, String mode, DataContentTopComponent customContentViewer) {
public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount, Collection<DataResultViewer> viewers) {
DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, viewers, null);
initInstance(description, node, childNodeCount, resultViewTopComponent);
return resultViewTopComponent;
}
/**
* Creates a partially initialized result view top component that provides
* multiple views of the application data represented by a NetBeans Node.
* The result view will be docked into the upper right hand side of the main
* application window (editor mode) and will be linked to the content view
* in the lower right hand side of the main application window (output
* mode). Its result viewers are provided by the result viewer extension
* point (service providers that implement DataResultViewer).
*
* IMPORTANT: Initialization MUST be completed by calling initInstance.
*
* @param title The title for the result view top component, appears on the
* top component's tab.
*
* @return The partially initialized result view top component.
*/
public static DataResultTopComponent createInstance(String title) {
DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null);
return resultViewTopComponent;
}
/**
* Initializes a partially initialized result view top component.
*
* @param description Descriptive text about the node displayed,
* appears on the top component's tab
* @param node The node to display.
* @param childNodeCount The cardinality of the node's children.
* @param resultViewTopComponent The partially initialized result view top
* component.
*/
public static void initInstance(String description, Node node, int childNodeCount, DataResultTopComponent resultViewTopComponent) {
resultViewTopComponent.setNumberOfChildNodes(childNodeCount);
resultViewTopComponent.open();
resultViewTopComponent.setNode(node);
resultViewTopComponent.setPath(description);
resultViewTopComponent.requestActive();
}
/**
* Creates a result view top component that provides multiple views of the
* application data represented by a NetBeans Node. The result view will be
* docked into a custom mode and linked to the supplied content view. Its
* result viewers are provided by the result viewer extension point (service
* providers that implement DataResultViewer).
*
* @param title The title for the top component, appears
* on the top component's tab.
* @param mode The NetBeans Window system mode into which
* this top component should be docked.
* @param description Descriptive text about the node displayed,
* appears on the top component's tab
* @param node The node to display.
* @param childNodeCount The cardinality of the node's children.
* @param contentViewTopComponent A content view to which this result view
* will be linked.
*
* @return The result view top component.
*/
public static DataResultTopComponent createInstance(String title, String mode, String description, Node node, int childNodeCount, DataContentTopComponent contentViewTopComponent) {
DataResultTopComponent newDataResult = new DataResultTopComponent(false, title, mode, Collections.emptyList(), contentViewTopComponent);
initInstance(description, node, childNodeCount, newDataResult);
return newDataResult;
}
/**
* Creates a result view top component that provides multiple views of the
* application data represented by a NetBeans Node. The result view will be
* the "main" result view and will docked into the upper right hand side of
* the main application window (editor mode) and will be linked to the
* content view in the lower right hand side of the main application window
* (output mode). Its result viewers are provided by the result viewer
* extension point (service providers that implement DataResultViewer).
*
* IMPORTANT: The "main" result view top component receives its current node
* as a selection from the case tree view in the top component
* (DirectoryTreeTopComponent) docked into the left hand side of the main
* application window. This constructor is RESERVED for the use of the
* DirectoryTreeTopComponent singleton only. DO NOT USE OTHERWISE.
*
* @param title The title for the top component, appears on the top
* component's tab.
*/
public DataResultTopComponent(String title) {
this(true, title, null, Collections.emptyList(), null);
}
/**
* Constructs a result view top component that provides multiple views of
* the application data represented by a NetBeans Node.
*
* @param isMain Whether or not this is the "main" result
* view top component.
* @param title The title for the top component, appears
* on the top component's tab.
* @param mode The NetBeans Window system mode into which
* this top component should be docked. If
* null, the editor mode will be used by
* default.
* @param viewers A collection of result viewers. If empty,
* the result viewers provided by the results
* viewer extension point will be used.
* @param contentViewTopComponent A content view to which this result view
* will be linked. If null, this result view
* will be linked to the content view docked
* into the lower right hand side of the main
* application window,
*/
private DataResultTopComponent(boolean isMain, String title, String mode, Collection<DataResultViewer> viewers, DataContentTopComponent contentViewTopComponent) {
this.isMain = isMain;
this.explorerManager = new ExplorerManager();
associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
this.customModeName = mode;
dataResultPanel = new DataResultPanel(name, customContentViewer);
this.dataResultPanel = new DataResultPanel(title, isMain, viewers, contentViewTopComponent);
initComponents();
customizeComponent(isMain, name);
customizeComponent(title);
}
private void customizeComponent(boolean isMain, String title) {
this.isMain = isMain;
this.customModeName = null;
setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent"));
setTitle(title); // set the title
private void customizeComponent(String title) {
setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); //NON-NLS
setTitle(title);
setName(title);
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS
getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain);
putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true);
putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true);
activeComponentIds.add(title);
}
/**
* Initialize previously created tc instance with additional data
*
* @param pathText
* @param givenNode
* @param totalMatches
* @param newDataResult previously created with createInstance()
* uninitialized instance
*/
public static void initInstance(String pathText, Node givenNode, int totalMatches, DataResultTopComponent newDataResult) {
newDataResult.setNumMatches(totalMatches);
newDataResult.open(); // open it first so the component can be initialized
// set the tree table view
newDataResult.setNode(givenNode);
newDataResult.setPath(pathText);
newDataResult.requestActive();
}
/**
* Creates a new non-default DataResult component and initializes it
*
* @param title Title of the component window
* @param pathText Descriptive text about the source of the nodes
* displayed
* @param givenNode The new root node
* @param totalMatches Cardinality of root node's children
*
* @return a new, not default, initialized DataResultTopComponent instance
*/
public static DataResultTopComponent createInstance(String title, String pathText, Node givenNode, int totalMatches) {
DataResultTopComponent newDataResult = new DataResultTopComponent(false, title);
initInstance(pathText, givenNode, totalMatches, newDataResult);
return newDataResult;
}
/**
* Creates a new non-default DataResult component linked with a custom data
* content, and initializes it.
*
*
* @param title Title of the component window
* @param mode custom mode to dock this custom TopComponent to
* @param pathText Descriptive text about the source of the nodes
* displayed
* @param givenNode The new root node
* @param totalMatches Cardinality of root node's children
* @param dataContentWindow a handle to data content top component window to
*
* @return a new, not default, initialized DataResultTopComponent instance
*/
public static DataResultTopComponent createInstance(String title, final String mode, String pathText, Node givenNode, int totalMatches, DataContentTopComponent dataContentWindow) {
DataResultTopComponent newDataResult = new DataResultTopComponent(title, mode, dataContentWindow);
initInstance(pathText, givenNode, totalMatches, newDataResult);
return newDataResult;
}
/**
* Creates a new non-default DataResult component. You probably want to use
* initInstance after it
*
* @param title
*
* @return a new, not default, not fully initialized DataResultTopComponent
* instance
*/
public static DataResultTopComponent createInstance(String title) {
final DataResultTopComponent newDataResult = new DataResultTopComponent(false, title);
return newDataResult;
}
@Override
public ExplorerManager getExplorerManager() {
return explorerManager;
}
/**
* Get a list with names of active windows ids, e.g. for the menus
* Get a listing of the preferred identifiers of all the result view top
* components that have been created.
*
* @return
* @return The listing.
*/
public static List<String> getActiveComponentIds() {
return new ArrayList<>(activeComponentIds);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel;
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
@Override
public int getPersistenceType() {
if (customModeName == null) {
@ -245,16 +282,6 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override
public void open() {
setCustomMode();
super.open(); //To change body of generated methods, choose Tools | Templates.
}
@Override
public List<DataResultViewer> getViewers() {
return dataResultPanel.getViewers();
}
private void setCustomMode() {
if (customModeName != null) {
Mode mode = WindowManager.getDefault().findMode(customModeName);
if (mode != null) {
@ -264,6 +291,12 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
logger.log(Level.WARNING, "Could not find mode: {0}, will dock into the default one", customModeName);//NON-NLS
}
}
super.open();
}
@Override
public List<DataResultViewer> getViewers() {
return dataResultPanel.getViewers();
}
@Override
@ -343,31 +376,15 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override
public boolean canClose() {
/*
* If this is the results top component in the upper right of the main
* window, only allow it to be closed when there's no case opened or no
* data sources in the open case.
*/
Case openCase;
try {
openCase = Case.getOpenCase();
} catch (NoCurrentCaseException ex) {
} catch (NoCurrentCaseException unused) {
return true;
}
return (!this.isMain) || openCase.hasData() == false;
}
/**
* Resets the tabs based on the selected Node. If the selected node is null
* or not supported, disable that tab as well.
*
* @param selectedNode the selected content Node
*/
public void resetTabs(Node selectedNode) {
dataResultPanel.resetTabs(selectedNode);
}
public void setSelectedNodes(Node[] selected) {
dataResultPanel.setSelectedNodes(selected);
}
@ -376,7 +393,74 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
return dataResultPanel.getRootNode();
}
void setNumMatches(int matches) {
this.dataResultPanel.setNumMatches(matches);
/**
* Sets the cardinality of the current node's children
*
* @param childNodeCount The cardinality of the node's children.
*/
private void setNumberOfChildNodes(int childNodeCount) {
this.dataResultPanel.setNumberOfChildNodes(childNodeCount);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel;
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
/**
* Creates a partially initialized result view top component that provides
* multiple views of the application data represented by a NetBeans Node.
* The result view will be docked into the upper right hand side of the main
* application window (editor mode) and will be linked to the content view
* in the lower right hand side of the main application window (output
* mode). Its result viewers are provided by the result viewer extension
* point (service providers that implement DataResultViewer).
*
* IMPORTANT: Initialization MUST be completed by calling initInstance.
*
* @param isMain Ignored.
* @param title The title for the top component, appears on the top
* component's tab.
*
* @deprecated Use an appropriate overload of createIntance instead.
*/
@Deprecated
public DataResultTopComponent(boolean isMain, String title) {
this(false, title, null, Collections.emptyList(), null);
}
/**
* Sets the node for which this result view component should provide
* multiple views of the underlying application data.
*
* @param node The node, may be null. If null, the call to this method is
* equivalent to a call to resetComponent on this result view
* component's result viewers.
*
* @deprecated Use setNode instead.
*/
@Deprecated
public void resetTabs(Node node) {
dataResultPanel.setNode(node);
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -60,155 +60,153 @@ import org.openide.nodes.Node;
import org.openide.nodes.Node.Property;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
/**
* A tabular viewer for the results view.
* A tabular result viewer that displays the children of the given root node
* using an OutlineView.
*
* TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
* restore implementation of DataResultViewerTable as a DataResultViewer service
* provider.
* Instances of this class should use the explorer manager of an ancestor top
* component to connect the lookups of the nodes displayed in the OutlineView to
* the actions global context. The explorer manager can be supplied during
* construction, but the typical use case is for the result viewer to find the
* ancestor top component's explorer manager at runtime.
*/
//@ServiceProvider(service = DataResultViewer.class)
public class DataResultViewerTable extends AbstractDataResultViewer {
@ServiceProvider(service = DataResultViewer.class)
public final 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();
private static final Color TAGGED_COLOR = new Color(255, 255, 195);
static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195);
private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
private final String title;
private final Map<String, ETableColumn> columnMap;
private final Map<Integer, Property<?>> propertiesMap;
private final Outline outline;
private final TableListener outlineViewListener;
private Node rootNode;
/**
* The properties map:
* Constructs a tabular result viewer that displays the children of the
* given root node using an OutlineView. The viewer should have an ancestor
* top component to connect the lookups of the nodes displayed in the
* OutlineView to the actions global context. The explorer manager will be
* discovered at runtime.
*/
public DataResultViewerTable() {
this(null, Bundle.DataResultViewerTable_title());
}
/**
* Constructs a tabular result viewer that displays the children of a given
* root node using an OutlineView. The viewer should have an ancestor top
* component to connect the lookups of the nodes displayed in the
* OutlineView to the actions global context.
*
* stored value of column index -> property at that index
*
* We move around stored values instead of directly using the column indices
* in order to not override settings for a column that may not appear in the
* current table view due to its collection of its children's properties.
*/
private final Map<Integer, Property<?>> propertiesMap = new TreeMap<>();
/**
* Stores references to the actual table column objects, keyed by column
* name, so that we can check there visibility later in
* storeColumnVisibility().
*/
private final Map<String, ETableColumn> columnMap = new HashMap<>();
private Node currentRoot;
/*
* Convience reference to internal Outline.
*/
private Outline outline;
/**
* Listener for table model event and mouse clicks.
*/
private final TableListener tableListener;
/**
* Creates a DataResultViewerTable object that is compatible with node
* multiple selection actions, and the default title.
*
* @param explorerManager allow for explorer manager sharing
* @param explorerManager The explorer manager of the ancestor top
* component.
*/
public DataResultViewerTable(ExplorerManager explorerManager) {
this(explorerManager, Bundle.DataResultViewerTable_title());
}
/**
* Creates a DataResultViewerTable object that is compatible with node
* multiple selection actions, and a custom title.
* Constructs a tabular result viewer that displays the children of a given
* root node using an OutlineView with a given title. The viewer should have
* an ancestor top component to connect the lookups of the nodes displayed
* in the OutlineView to the actions global context.
*
* @param explorerManager allow for explorer manager sharing
* @param title The custom title.
* @param explorerManager The explorer manager of the ancestor top
* component.
* @param title The title.
*/
public DataResultViewerTable(ExplorerManager explorerManager, String title) {
super(explorerManager);
this.title = title;
this.columnMap = new HashMap<>();
this.propertiesMap = new TreeMap<>();
/*
* Execute the code generated by the GUI builder.
*/
initComponents();
/*
* Configure the child OutlineView (explorer view) component.
*/
outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
outline = outlineView.getOutline();
outline.setRowSelectionAllowed(true);
outline.setColumnSelectionAllowed(true);
outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
outline.setRootVisible(false); // don't show the root node
outline.setRootVisible(false);
outline.setDragEnabled(false);
outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer());
// add a listener so that when columns are moved, the new order is stored
tableListener = new TableListener();
outline.getColumnModel().addColumnModelListener(tableListener);
// the listener also moves columns back if user tries to move the first column out of place
outline.getTableHeader().addMouseListener(tableListener);
}
/**
* Creates a DataResultViewerTable object that is NOT compatible with node
* multiple selection actions.
/*
* Add a table listener to the child OutlineView (explorer view) to
* persist the order of the table columns when a column is moved.
*/
public DataResultViewerTable() {
this(new ExplorerManager(),Bundle.DataResultViewerTable_title());
outlineViewListener = new TableListener();
outline.getColumnModel().addColumnModelListener(outlineViewListener);
/*
* Add a mouse listener to the child OutlineView (explorer view) to make
* sure the first column of the table is kept in place.
*/
outline.getTableHeader().addMouseListener(outlineViewListener);
}
/**
* Expand node
* Creates a new instance of a tabular result viewer that displays the
* children of a given root node using an OutlineView. This method exists to
* make it possible to use the default service provider instance of this
* class in the "main" results view of the application, while using distinct
* instances in other places in the UI.
*
* @param n Node to expand
* @return A new instance of a tabular result viewer,
*/
@Override
public void expandNode(Node n) {
super.expandNode(n);
outlineView.expandNode(n);
public DataResultViewer createInstance() {
return new DataResultViewerTable();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
* Gets the title of this tabular result viewer.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.openide.explorer.view.OutlineView outlineView;
// End of variables declaration//GEN-END:variables
@Override
public boolean isSupported(Node selectedNode) {
@NbBundle.Messages("DataResultViewerTable.title=Table")
public String getTitle() {
return title;
}
/**
* Indicates whether a given node is supported as a root node for this
* tabular viewer.
*
* @param candidateRootNode The candidate root node.
*
* @return
*/
@Override
public boolean isSupported(Node candidateRootNode) {
return true;
}
/**
* Sets the current root node of this tabular result viewer.
*
* @param rootNode The node to set as the current root node, possibly null.
*/
@Override
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void setNode(Node selectedNode) {
public void setNode(Node rootNode) {
/*
* The quick filter must be reset because when determining column width,
* ETable.getRowCount is called, and the documentation states that quick
@ -217,30 +215,30 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* model."
*/
outline.unsetQuickFilter();
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
boolean hasChildren = false;
if (selectedNode != null) {
// @@@ This just did a DB round trip to get the count and the results were not saved...
hasChildren = selectedNode.getChildren().getNodesCount() > 0;
}
if (hasChildren) {
currentRoot = selectedNode;
em.setRootContext(currentRoot);
/*
* If the given node is not null and has children, set it as the
* root context of the child OutlineView, otherwise make an
* "empty"node the root context.
*
* IMPORTANT NOTE: This is the first of many times where a
* getChildren call on the current root node causes all of the
* children of the root node to be created and defeats lazy child
* node creation, if it is enabled. It also likely leads to many
* case database round trips.
*/
if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
this.rootNode = rootNode;
this.getExplorerManager().setRootContext(this.rootNode);
setupTable();
} else {
Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node
this.getExplorerManager().setRootContext(emptyNode);
outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
/*
* Since we are modifying the columns, we don't want to listen
* to added/removed events as un-hide/hide.
*/
tableListener.listenToVisibilityChanges(false);
outlineView.setPropertyColumns(); // set the empty property header
outlineViewListener.listenToVisibilityChanges(false);
outlineView.setPropertyColumns();
}
} finally {
this.setCursor(null);
@ -248,16 +246,17 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
/**
* Create Column Headers based on the Content represented by the Nodes in
* the table. Load persisted column order, sorting and visibility.
* Sets up the Outline view of this tabular result viewer by creating
* column headers based on the children of the current root node. The
* persisted column order, sorting and visibility is used.
*/
private void setupTable() {
/*
* Since we are modifying the columns, we don't want to listen to
* added/removed events as un-hide/hide, until the table setup is done.
*/
tableListener.listenToVisibilityChanges(false);
/**
outlineViewListener.listenToVisibilityChanges(false);
/*
* OutlineView makes the first column be the result of
* node.getDisplayName with the icon. This duplicates our first column,
* which is the file name, etc. So, pop that property off the list, but
@ -289,7 +288,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
setColumnWidths();
//Load column sorting information from preferences file and apply it to columns.
/*
* Load column sorting information from preferences file and apply it to
* columns.
*/
loadColumnSorting();
/*
@ -301,7 +303,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
populateColumnMap();
//Load column visibility information from preferences file and apply it to columns.
/*
* Load column visibility information from preferences file and apply it
* to columns.
*/
loadColumnVisibility();
/*
@ -309,32 +314,36 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* it.
*/
SwingUtilities.invokeLater(() -> {
if (currentRoot instanceof TableFilterNode) {
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
if (rootNode instanceof TableFilterNode) {
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) {
Node[] childNodes = currentRoot.getChildren().getNodes(true);
Node[] childNodes = rootNode.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});
this.getExplorerManager().setSelectedNodes(new Node[]{childNode});
} catch (PropertyVetoException ex) {
LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
}
break;
}
}
((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null);
((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
}
}
});
//the table setup is done, so any added/removed events can now be treated as un-hide/hide.
tableListener.listenToVisibilityChanges(true);
/*
* The table setup is done, so any added/removed events can now be
* treated as un-hide/hide.
*/
outlineViewListener.listenToVisibilityChanges(true);
}
/*
* Populate the map with references to the column objects for use when
* Populates the column map for the child OutlineView of this tabular
* result viewer with references to the column objects for use when
* loading/storing the visibility info.
*/
private void populateColumnMap() {
@ -351,8 +360,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
/*
* Sets the column widths for the child OutlineView of this tabular results
* viewer.
*/
private void setColumnWidths() {
if (currentRoot.getChildren().getNodesCount() != 0) {
if (rootNode.getChildren().getNodesCount() != 0) {
final Graphics graphics = outlineView.getGraphics();
if (graphics != null) {
final FontMetrics metrics = graphics.getFontMetrics();
@ -388,8 +401,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
/*
* Sets up the columns for the child OutlineView of this tabular results
* viewer with respect to column names and visisbility.
*/
synchronized private void assignColumns(List<Property<?>> props) {
// Get the columns setup with respect to names and sortability
String[] propStrings = new String[props.size() * 2];
for (int i = 0; i < props.size(); i++) {
final Property<?> prop = props.get(i);
@ -402,29 +418,25 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
propStrings[2 * i] = prop.getName();
propStrings[2 * i + 1] = prop.getDisplayName();
}
outlineView.setPropertyColumns(propStrings);
}
/**
* Store the current column visibility information into a preference file.
* Persists the current column visibility information for the child
* OutlineView of this tabular result viewer using a preferences file.
*/
private synchronized void storeColumnVisibility() {
if (currentRoot == null || propertiesMap.isEmpty()) {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (currentRoot instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) currentRoot;
if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
//store hidden state
for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
String columnName = entry.getKey();
final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
final TableColumn column = entry.getValue();
boolean columnHidden = columnModel.isColumnHidden(column);
if (columnHidden) {
preferences.putBoolean(columnHiddenKey, true);
@ -436,16 +448,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
/**
* Store the current column order information into a preference file.
* Persists the current column ordering for the child OutlineView of this
* tabular result viewer using a preferences file.
*/
private synchronized void storeColumnOrder() {
if (currentRoot == null || propertiesMap.isEmpty()) {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (currentRoot instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) currentRoot;
if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
// Store the current order of the columns into settings
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
@ -454,20 +466,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
/**
* Store the current column sorting information into a preference file.
* Persists the current column sorting information using a preferences file.
*/
private synchronized void storeColumnSorting() {
if (currentRoot == null || propertiesMap.isEmpty()) {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (currentRoot instanceof TableFilterNode) {
final TableFilterNode tfn = ((TableFilterNode) currentRoot);
if (rootNode instanceof TableFilterNode) {
final TableFilterNode tfn = ((TableFilterNode) rootNode);
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
ETableColumn etc = entry.getValue();
String columnName = entry.getKey();
//store sort rank and order
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
@ -485,47 +496,43 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
/**
* Reads and applies the column sorting information persisted to the
* preferences file. Must be called after loadColumnOrder() since it depends
* on propertiesMap being initialized, and after assignColumns since it
* cannot set the sort on columns that have not been added to the table.
* preferences file. Must be called after loadColumnOrder, since it depends
* on the properties map being initialized, and after assignColumns, since
* it cannot set the sort on columns that have not been added to the table.
*/
private synchronized void loadColumnSorting() {
if (currentRoot == null || propertiesMap.isEmpty()) {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (currentRoot instanceof TableFilterNode) {
final TableFilterNode tfn = (TableFilterNode) currentRoot;
if (rootNode instanceof TableFilterNode) {
final TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
//organize property sorting information, sorted by rank
TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
propertiesMap.entrySet().stream().forEach(entry -> {
final String propName = entry.getValue().getName();
//if the sort rank is undefined, it will be defaulted to 0 => unsorted.
Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
//default to true => ascending
Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
});
//apply sort information in rank order.
sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
}
}
/**
* Reads and applies the column visibility information persisted to the
* preferences file.
*/
private synchronized void loadColumnVisibility() {
if (currentRoot == null || propertiesMap.isEmpty()) {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (currentRoot instanceof TableFilterNode) {
if (rootNode instanceof TableFilterNode) {
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final TableFilterNode tfn = ((TableFilterNode) currentRoot);
final TableFilterNode tfn = ((TableFilterNode) rootNode);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
final String propName = entry.getValue().getName();
@ -538,7 +545,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
/**
* Gets a list of child properties (columns) in the order persisted in the
* preference file. Also initialized the propertiesMap with the column
* preference file. Also initialized the properties map with the column
* order.
*
* @return a List<Node.Property<?>> of the properties in the persisted
@ -546,14 +553,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
private synchronized List<Node.Property<?>> loadColumnOrder() {
List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100);
List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
// If node is not table filter node, use default order for columns
if (!(currentRoot instanceof TableFilterNode)) {
if (!(rootNode instanceof TableFilterNode)) {
return props;
}
final TableFilterNode tfn = ((TableFilterNode) currentRoot);
final TableFilterNode tfn = ((TableFilterNode) rootNode);
propertiesMap.clear();
/*
@ -590,24 +597,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
return new ArrayList<>(propertiesMap.values());
}
@Override
@NbBundle.Messages("DataResultViewerTable.title=Table")
public String getTitle() {
return title;
}
@Override
public DataResultViewer createInstance() {
return new DataResultViewerTable();
}
/**
* Frees the resources that have been allocated by this tabular results
* viewer, in preparation for permanently disposing of it.
*/
@Override
public void clearComponent() {
this.outlineView.removeAll();
this.outlineView = null;
super.clearComponent();
}
/**
@ -778,8 +776,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
// only override the color if a node is not selected
if (currentRoot != null && !isSelected) {
Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row));
if (rootNode != null && !isSelected) {
Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row));
boolean tagFound = false;
if (node != null) {
Node.PropertySet[] propSets = node.getPropertySets();
@ -799,10 +797,37 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
//if the node does have associated tags, set its background color
if (tagFound) {
component.setBackground(TAGGED_COLOR);
component.setBackground(TAGGED_ROW_COLOR);
}
}
return component;
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.openide.explorer.view.OutlineView outlineView;
// End of variables declaration//GEN-END:variables
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -50,6 +50,7 @@ import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import static org.sleuthkit.autopsy.corecomponents.Bundle.*;
import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion;
@ -59,64 +60,67 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A thumbnail viewer for the results view, with paging support.
* A thumbnail result viewer, with paging support, that displays the children of
* the given root node using an IconView. The paging is intended to reduce
* memory footprint by loading no more than two humdred images at a time.
*
* The paging is intended to reduce memory footprint by load only up to
* (currently) 200 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.
* Instances of this class should use the explorer manager of an ancestor top
* component to connect the lookups of the nodes displayed in the IconView to
* the actions global context. The explorer manager can be supplied during
* construction, but the typical use case is for the result viewer to find the
* ancestor top component's explorer manager at runtime.
*/
//@ServiceProvider(service = DataResultViewer.class)
final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@ServiceProvider(service = DataResultViewer.class)
public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
private int curPage;
private int totalPages;
private int curPageImages;
private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
private final PageUpdater pageUpdater = new PageUpdater();
private TableFilterNode tfn;
private ThumbnailViewChildren tvc;
private TableFilterNode rootNode;
private ThumbnailViewChildren rootNodeChildren;
private NodeSelectionListener selectionListener;
private int currentPage;
private int totalPages;
private int currentPageImages;
private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
/**
* Constructs a thumbnail viewer for the results view, with paging support,
* that is compatible with node multiple selection actions.
* Constructs a thumbnail result viewer, with paging support, that displays
* the children of the given root node using an IconView. The viewer should
* have an ancestor top component to connect the lookups of the nodes
* displayed in the IconView to the actions global context. The explorer
* manager will be discovered at runtime.
*/
public DataResultViewerThumbnail() {
this(null);
}
/**
* Constructs a thumbnail result viewer, with paging support, that displays
* the children of the given root node using an IconView. The viewer should
* have an ancestor top component to connect the lookups of the nodes
* displayed in the IconView to the actions global context.
*
* @param explorerManager The shared ExplorerManager for the result viewers.
* @param explorerManager The explorer manager of the ancestor top
* component.
*/
DataResultViewerThumbnail(ExplorerManager explorerManager) {
super(explorerManager);
initialize();
}
/**
* Constructs a thumbnail viewer for the results view, with paging support,
* that is NOT compatible with node multiple selection actions.
*/
DataResultViewerThumbnail() {
initialize();
}
@NbBundle.Messages({"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
@NbBundle.Messages({
"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
"DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails",
"DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails"
})
private void initialize() {
public DataResultViewerThumbnail(ExplorerManager explorerManager) {
super(explorerManager);
initComponents();
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(
new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{
Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(),
Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()}));
thumbnailSizeComboBox.setSelectedIndex(1);
curPage = -1;
currentPage = -1;
totalPages = 0;
curPageImages = 0;
currentPageImages = 0;
}
/**
@ -297,24 +301,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
if (thumbSize != newIconSize) {
thumbSize = newIconSize;
Node root = em.getRootContext();
Node root = this.getExplorerManager().getRootContext();
((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize);
// Temporarily set the explored context to the root, instead of a child node.
// This is a workaround hack to convince org.openide.explorer.ExplorerManager to
// update even though the new and old Node values are identical. This in turn
// will cause the entire view to update completely. After this we
// immediately set the node back to the current child by calling switchPage().
em.setExploredContext(root);
this.getExplorerManager().setExploredContext(root);
switchPage();
}
}//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed
private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed
List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100);
SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn));
List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100);
SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(rootNode));
DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle());
Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
createDialog.setVisible(true);
@ -335,8 +337,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
Node.Property<?> prop = childProperties.get(i);
String propName = prop.getName();
SortCriterion criterion = criteriaMap.get(prop);
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName);
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(rootNode, propName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(rootNode, propName);
if (criterion != null) {
preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING);
@ -346,7 +348,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
preferences.remove(columnSortRankKey);
}
}
setNode(tfn); //this is just to force a refresh
setNode(rootNode); //this is just to force a refresh
}
}//GEN-LAST:event_sortButtonActionPerformed
@ -379,28 +381,31 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override
public void setNode(Node givenNode) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (tvc != null) {
tvc.cancelLoadingThumbnails();
if (selectionListener == null) {
this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup
}
if (rootNodeChildren != null) {
rootNodeChildren.cancelLoadingThumbnails();
}
try {
if (givenNode != null) {
tfn = (TableFilterNode) givenNode;
rootNode = (TableFilterNode) givenNode;
/*
* Wrap the given node in a ThumbnailViewChildren that will
* produce ThumbnailPageNodes with ThumbnailViewNode children
* from the child nodes of the given node.
*/
tvc = new ThumbnailViewChildren(givenNode,thumbSize);
final Node root = new AbstractNode(tvc);
rootNodeChildren = new ThumbnailViewChildren(givenNode, thumbSize);
final Node root = new AbstractNode(rootNodeChildren);
pageUpdater.setRoot(root);
root.addNodeListener(pageUpdater);
em.setRootContext(root);
this.getExplorerManager().setRootContext(root);
} else {
tfn = null;
tvc = null;
rootNode = null;
rootNodeChildren = null;
Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode);
this.getExplorerManager().setRootContext(emptyNode);
iconView.setBackground(Color.BLACK);
}
} finally {
@ -422,8 +427,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
public void resetComponent() {
super.resetComponent();
this.totalPages = 0;
this.curPage = -1;
curPageImages = 0;
this.currentPage = -1;
currentPageImages = 0;
updateControls();
}
@ -435,15 +440,15 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
private void nextPage() {
if (curPage < totalPages) {
curPage++;
if (currentPage < totalPages) {
currentPage++;
switchPage();
}
}
private void previousPage() {
if (curPage > 1) {
curPage--;
if (currentPage > 1) {
currentPage--;
switchPage();
}
}
@ -465,7 +470,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
return;
}
curPage = newPage;
currentPage = newPage;
switchPage();
}
@ -488,10 +493,11 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs"));
progress.start();
progress.switchToIndeterminate();
Node root = em.getRootContext();
Node pageNode = root.getChildren().getNodeAt(curPage - 1);
em.setExploredContext(pageNode);
curPageImages = pageNode.getChildren().getNodesCount();
ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager();
Node root = explorerManager.getRootContext();
Node pageNode = root.getChildren().getNodeAt(currentPage - 1);
explorerManager.setExploredContext(pageNode);
currentPageImages = pageNode.getChildren().getNodesCount();
return null;
}
@ -504,8 +510,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
NotifyDescriptor d =
new NotifyDescriptor.Message(
NotifyDescriptor d
= new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg",
ex.getMessage()),
NotifyDescriptor.ERROR_MESSAGE);
@ -535,20 +541,19 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
} else {
pageNumLabel.setText(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal",
Integer.toString(curPage), Integer.toString(totalPages)));
final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
pageNumLabel.setText(NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal",
Integer.toString(currentPage), Integer.toString(totalPages)));
final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
imagesRangeLabel.setText(imagesFrom + "-" + imagesTo);
pageNextButton.setEnabled(!(curPage == totalPages));
pagePrevButton.setEnabled(!(curPage == 1));
pageNextButton.setEnabled(!(currentPage == totalPages));
pagePrevButton.setEnabled(!(currentPage == 1));
goToPageField.setEnabled(totalPages > 1);
sortButton.setEnabled(true);
thumbnailSizeComboBox.setEnabled(true);
if (tfn != null) {
String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream()
if (rootNode != null) {
String sortString = ResultViewerPersistence.loadSortCriteria(rootNode).stream()
.map(SortCriterion::toString)
.collect(Collectors.joining(" "));
sortString = StringUtils.defaultIfBlank(sortString, "---");
@ -579,30 +584,30 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
totalPages = root.getChildren().getNodesCount();
if (totalPages == 0) {
curPage = -1;
currentPage = -1;
updateControls();
return;
}
if (curPage == -1 || curPage > totalPages) {
curPage = 1;
if (currentPage == -1 || currentPage > totalPages) {
currentPage = 1;
}
//force load the curPage node
final Node pageNode = root.getChildren().getNodeAt(curPage - 1);
final Node pageNode = root.getChildren().getNodeAt(currentPage - 1);
//em.setSelectedNodes(new Node[]{pageNode});
if (pageNode != null) {
pageNode.addNodeListener(new NodeListener() {
@Override
public void childrenAdded(NodeMemberEvent nme) {
curPageImages = pageNode.getChildren().getNodesCount();
currentPageImages = pageNode.getChildren().getNodesCount();
updateControls();
}
@Override
public void childrenRemoved(NodeMemberEvent nme) {
curPageImages = 0;
currentPageImages = 0;
updateControls();
}
@ -619,7 +624,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
});
em.setExploredContext(pageNode);
DataResultViewerThumbnail.this.getExplorerManager().setExploredContext(pageNode);
}
updateControls();
@ -628,7 +633,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override
public void childrenRemoved(NodeMemberEvent nme) {
totalPages = 0;
curPage = -1;
currentPage = -1;
updateControls();
}
@ -641,14 +646,14 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
}
private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener {
private class NodeSelectionListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
Node[] selectedNodes = em.getSelectedNodes();
Node[] selectedNodes = DataResultViewerThumbnail.this.getExplorerManager().getSelectedNodes();
if (selectedNodes.length == 1) {
AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
if (af == null) {

View File

@ -76,7 +76,8 @@ public class Installer extends ModuleInstall {
private void setLookAndFeel() {
if (System.getProperty("os.name").toLowerCase().contains("mac")) { //NON-NLS
setOSXLookAndFeel();
setUnixLookAndFeel();
setModuleSettings("false");
}else if (System.getProperty("os.name").toLowerCase().contains("nux")){
setUnixLookAndFeel();
}

View File

@ -358,7 +358,7 @@ public class PlatformUtil {
File[] files = dev.listFiles();
for (File f : files) {
String name = f.getName();
if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 3) { //NON-NLS
if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 5) { //NON-NLS
String path = "/dev/" + name; //NON-NLS
if (canReadDrive(path)) {
try {
@ -401,7 +401,7 @@ public class PlatformUtil {
File[] files = dev.listFiles();
for (File f : files) {
String name = f.getName();
if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 4) { //NON-NLS
if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 7) { //NON-NLS
String path = "/dev/" + name; //NON-NLS
if (canReadDrive(path)) {
drives.add(new LocalDisk(path, path, f.getTotalSpace()));

View File

@ -386,9 +386,16 @@ public class DataResultFilterNode extends FilterNode {
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c));
}
// action to go to the source file of the artifact
// action to go to the source file of the artifact
Content fileContent = ban.getLookup().lookup(AbstractFile.class);
if (fileContent == null) {
Content content = ban.getLookup().lookup(Content.class);
actionsList.add(new ViewContextAction("View Source Content in Directory", content));
} else {
actionsList.add(new ViewContextAction(
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban));
}
}
Content c = ban.getLookup().lookup(File.class);
Node n = null;
boolean md5Action = false;

View File

@ -101,7 +101,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private final transient ExplorerManager em = new ExplorerManager();
private static DirectoryTreeTopComponent instance;
private final DataResultTopComponent dataResult = new DataResultTopComponent(true, Bundle.DirectoryTreeTopComponent_resultsView_title());
private final DataResultTopComponent dataResult = new DataResultTopComponent(Bundle.DirectoryTreeTopComponent_resultsView_title());
private final LinkedList<String[]> backList;
private final LinkedList<String[]> forwardList;
private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.guiutils;
import java.awt.Color;
import java.awt.Component;
import java.time.Duration;
import javax.swing.JTable;
@ -41,9 +40,25 @@ public class DurationCellRenderer extends GrayableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Long) {
{
Duration d = Duration.ofMillis((long) value);
setText(DurationCellRenderer.longToDurationString((long) value));
}
}
grayCellIfTableNotEnabled(table, isSelected);
return this;
}
/**
* Convert a duration represented by a long to a human readable string with
* with days, hours, minutes, and seconds components.
*
* @param duration - the representation of the duration in long form
*
* @return - the representation of the duration in String form.
*/
public static String longToDurationString(long duration) {
Duration d = Duration.ofMillis(duration);
if (d.isNegative()) {
d = Duration.ofMillis(-(long) value);
d = Duration.ofMillis(-duration);
}
String result;
@ -65,12 +80,7 @@ public class DurationCellRenderer extends GrayableCellRenderer {
} else {
result = seconds + " s";
}
setText(result);
}
}
grayCellIfTableNotEnabled(table, isSelected);
return this;
return result;
}
}

View File

@ -0,0 +1,859 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.healthmonitor;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import org.apache.commons.dbcp2.BasicDataSource;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.ThreadUtils;
import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Class for recording data on the health of the system.
*
* For timing data:
* Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object
* Modules will call submitTimingMetric() with the obtained TimingMetric object to log it
*/
public final class EnterpriseHealthMonitor implements PropertyChangeListener {
private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName());
private final static String DATABASE_NAME = "EnterpriseHealthMonitor";
private final static String MODULE_NAME = "EnterpriseHealthMonitor";
private final static String IS_ENABLED_KEY = "is_enabled";
private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes
public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
= new CaseDbSchemaVersionNumber(1, 0);
private static final AtomicBoolean isEnabled = new AtomicBoolean(false);
private static EnterpriseHealthMonitor instance;
private final ExecutorService healthMonitorExecutor;
private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d";
private ScheduledThreadPoolExecutor healthMonitorOutputTimer;
private final Map<String, TimingInfo> timingInfoMap;
private static final int CONN_POOL_SIZE = 10;
private BasicDataSource connectionPool = null;
private String hostName;
private EnterpriseHealthMonitor() throws HealthMonitorException {
// Create the map to collect timing metrics. The map will exist regardless
// of whether the monitor is enabled.
timingInfoMap = new HashMap<>();
// Set up the executor to handle case events
healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build());
// Get the host name
try {
hostName = java.net.InetAddress.getLocalHost().getHostName();
} catch (java.net.UnknownHostException ex) {
// Continue on, but log the error and generate a UUID to use for this session
hostName = UUID.randomUUID().toString();
logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex);
}
// Read from module settings to determine if the module is enabled
if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) {
if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){
isEnabled.set(true);
try {
activateMonitor();
} catch (HealthMonitorException ex) {
// If we failed to activate it, then disable the monitor
logger.log(Level.SEVERE, "Health monitor activation failed - disabling health monitor");
setEnabled(false);
throw ex;
}
return;
}
}
isEnabled.set(false);
}
/**
* Get the instance of the EnterpriseHealthMonitor
* @return the instance
* @throws HealthMonitorException
*/
synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException {
if (instance == null) {
instance = new EnterpriseHealthMonitor();
Case.addPropertyChangeListener(instance);
}
return instance;
}
/**
* Activate the health monitor.
* Creates/initialized the database (if needed), clears any existing metrics
* out of the maps, and sets up the timer for writing to the database.
* @throws HealthMonitorException
*/
private synchronized void activateMonitor() throws HealthMonitorException {
logger.log(Level.INFO, "Activating Servies Health Monitor");
if (!UserPreferences.getIsMultiUserModeEnabled()) {
throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor");
}
// Set up database (if needed)
try (CoordinationService.Lock lock = getExclusiveDbLock()) {
if(lock == null) {
throw new HealthMonitorException("Error getting database lock");
}
// Check if the database exists
if (! databaseExists()) {
// If not, create a new one
createDatabase();
}
if( ! databaseIsInitialized()) {
initializeDatabaseSchema();
}
} catch (CoordinationService.CoordinationServiceException ex) {
throw new HealthMonitorException("Error releasing database lock", ex);
}
// Clear out any old data
timingInfoMap.clear();
// Start the timer for database writes
startTimer();
}
/**
* Deactivate the health monitor.
* This should only be used when disabling the monitor, not when Autopsy is closing.
* Clears out any metrics that haven't been written, stops the database write timer,
* and shuts down the connection pool.
* @throws HealthMonitorException
*/
private synchronized void deactivateMonitor() throws HealthMonitorException {
logger.log(Level.INFO, "Deactivating Servies Health Monitor");
// Clear out the collected data
timingInfoMap.clear();
// Stop the timer
stopTimer();
// Shut down the connection pool
shutdownConnections();
}
/**
* Start the ScheduledThreadPoolExecutor that will handle the database writes.
*/
private synchronized void startTimer() {
// Make sure the previous executor (if it exists) has been stopped
stopTimer();
healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build());
healthMonitorOutputTimer.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES);
}
/**
* Stop the ScheduledThreadPoolExecutor to prevent further database writes.
*/
private synchronized void stopTimer() {
if(healthMonitorOutputTimer != null) {
ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer);
}
}
/**
* Called from the installer to set up the Health Monitor instance at startup.
* @throws HealthMonitorException
*/
static synchronized void startUpIfEnabled() throws HealthMonitorException {
getInstance();
}
/**
* Enabled/disable the health monitor.
* @param enabled true to enable the monitor, false to disable it
* @throws HealthMonitorException
*/
static synchronized void setEnabled(boolean enabled) throws HealthMonitorException {
if(enabled == isEnabled.get()) {
// The setting has not changed, so do nothing
return;
}
if(enabled) {
getInstance().activateMonitor();
// If activateMonitor fails, we won't update either of these
ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "true");
isEnabled.set(true);
} else {
ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false");
isEnabled.set(false);
getInstance().deactivateMonitor();
}
}
/**
* Get a metric that will measure the time to execute a section of code.
* Call this before the section of code to be timed and then
* submit it afterward using submitTimingMetric().
* This method is safe to call regardless of whether the Enterprise Health
* Monitor is enabled.
* @param name A short but descriptive name describing the code being timed.
* This name will appear in the UI.
* @return The TimingMetric object
*/
public static TimingMetric getTimingMetric(String name) {
if(isEnabled.get()) {
return new TimingMetric(name);
}
return null;
}
/**
* Submit the metric that was previously obtained through getTimingMetric().
* Call this immediately after the section of code being timed.
* This method is safe to call regardless of whether the Enterprise Health
* Monitor is enabled.
* @param metric The TimingMetric object obtained from getTimingMetric()
*/
public static void submitTimingMetric(TimingMetric metric) {
if(isEnabled.get() && (metric != null)) {
metric.stopTiming();
try {
getInstance().addTimingMetric(metric);
} catch (HealthMonitorException ex) {
// We don't want calling methods to have to check for exceptions, so just log it
logger.log(Level.SEVERE, "Error adding timing metric", ex);
}
}
}
/**
* Submit the metric that was previously obtained through getTimingMetric(),
* incorporating a count that the time should be divided by.
* Call this immediately after the section of code being timed.
* This method is safe to call regardless of whether the Enterprise Health
* Monitor is enabled.
* @param metric The TimingMetric object obtained from getTimingMetric()
* @param normalization The number to divide the time by (a zero here will be treated as a one)
*/
public static void submitNormalizedTimingMetric(TimingMetric metric, long normalization) {
if(isEnabled.get() && (metric != null)) {
metric.stopTiming();
try {
metric.normalize(normalization);
getInstance().addTimingMetric(metric);
} catch (HealthMonitorException ex) {
// We don't want calling methods to have to check for exceptions, so just log it
logger.log(Level.SEVERE, "Error adding timing metric", ex);
}
}
}
/**
* Add the timing metric data to the map.
* @param metric The metric to add. stopTiming() should already have been called.
*/
private void addTimingMetric(TimingMetric metric) throws HealthMonitorException {
// Do as little as possible within the synchronized block to minimize
// blocking with multiple threads.
synchronized(this) {
// There's a small check-then-act situation here where isEnabled
// may have changed before reaching this code. This is fine -
// the map still exists and any extra data added after the monitor
// is disabled will be deleted if the monitor is re-enabled. This
// seems preferable to doing another check on isEnabled within
// the synchronized block.
if(timingInfoMap.containsKey(metric.getName())) {
timingInfoMap.get(metric.getName()).addMetric(metric);
} else {
timingInfoMap.put(metric.getName(), new TimingInfo(metric));
}
}
}
/**
* Time a database query.
* Database queries are hard to test in normal processing because the time
* is so dependent on the size of the tables being queried. We use getImages here
* because every table it queries is the same size (one entry for each image) so
* we a) know the size of the tables and b) can use that table size to do
* normalization.
* @throws HealthMonitorException
*/
private void performDatabaseQuery() throws HealthMonitorException {
try {
SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase();
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Database: getImages query");
List<Image> images = skCase.getImages();
// Through testing we found that this normalization gives us fairly
// consistent results for different numbers of data sources.
long normalization = images.size();
if (images.isEmpty()) {
normalization += 2;
} else if (images.size() == 1){
normalization += 3;
} else if (images.size() < 10) {
normalization += 5;
} else {
normalization += 7;
}
EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, normalization);
} catch (NoCurrentCaseException ex) {
// If there's no case open, we just can't do the metrics.
} catch (TskCoreException ex) {
throw new HealthMonitorException("Error running getImages()", ex);
}
}
/**
* Collect metrics at a scheduled time.
* @throws HealthMonitorException
*/
private void gatherTimerBasedMetrics() throws HealthMonitorException {
// Time a database query
performDatabaseQuery();
}
/**
* Write the collected metrics to the database.
* @throws HealthMonitorException
*/
private void writeCurrentStateToDatabase() throws HealthMonitorException {
Map<String, TimingInfo> timingMapCopy;
// Do as little as possible within the synchronized block since it will
// block threads attempting to record metrics.
synchronized(this) {
if(! isEnabled.get()) {
return;
}
// Make a shallow copy of the timing map. The map should be small - one entry
// per metric name.
timingMapCopy = new HashMap<>(timingInfoMap);
timingInfoMap.clear();
}
// Check if there's anything to report (right now we only have the timing map)
if(timingMapCopy.keySet().isEmpty()) {
return;
}
logger.log(Level.INFO, "Writing health monitor metrics to database");
// Write to the database
try (CoordinationService.Lock lock = getSharedDbLock()) {
if(lock == null) {
throw new HealthMonitorException("Error getting database lock");
}
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
// Add timing metrics to the database
String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) {
for(String name:timingMapCopy.keySet()) {
TimingInfo info = timingMapCopy.get(name);
statement.setString(1, name);
statement.setString(2, hostName);
statement.setLong(3, System.currentTimeMillis());
statement.setLong(4, info.getCount());
statement.setDouble(5, info.getAverage());
statement.setDouble(6, info.getMax());
statement.setDouble(7, info.getMin());
statement.execute();
}
} catch (SQLException ex) {
throw new HealthMonitorException("Error saving metric data to database", ex);
} finally {
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing Connection.", ex);
}
}
} catch (CoordinationService.CoordinationServiceException ex) {
throw new HealthMonitorException("Error releasing database lock", ex);
}
}
/**
* Check whether the health monitor database exists.
* Does not check the schema.
* @return true if the database exists, false otherwise
* @throws HealthMonitorException
*/
private boolean databaseExists() throws HealthMonitorException {
try {
// Use the same database settings as the case
CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
Class.forName("org.postgresql.Driver"); //NON-NLS
ResultSet rs = null;
try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
Statement statement = connection.createStatement();) {
String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'";
rs = statement.executeQuery(createCommand);
if(rs.next()) {
logger.log(Level.INFO, "Existing Enterprise Health Monitor database found");
return true;
}
} finally {
if(rs != null) {
rs.close();
}
}
} catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
throw new HealthMonitorException("Failed check for health monitor database", ex);
}
return false;
}
/**
* Create a new health monitor database.
* @throws HealthMonitorException
*/
private void createDatabase() throws HealthMonitorException {
try {
// Use the same database settings as the case
CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
Class.forName("org.postgresql.Driver"); //NON-NLS
try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
Statement statement = connection.createStatement();) {
String createCommand = "CREATE DATABASE \"" + DATABASE_NAME + "\" OWNER \"" + db.getUserName() + "\""; //NON-NLS
statement.execute(createCommand);
}
logger.log(Level.INFO, "Created new health monitor database " + DATABASE_NAME);
} catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
throw new HealthMonitorException("Failed to delete health monitor database", ex);
}
}
/**
* Setup a connection pool for db connections.
* @throws HealthMonitorException
*/
private void setupConnectionPool() throws HealthMonitorException {
try {
CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
connectionPool = new BasicDataSource();
connectionPool.setDriverClassName("org.postgresql.Driver");
StringBuilder connectionURL = new StringBuilder();
connectionURL.append("jdbc:postgresql://");
connectionURL.append(db.getHost());
connectionURL.append(":");
connectionURL.append(db.getPort());
connectionURL.append("/");
connectionURL.append(DATABASE_NAME);
connectionPool.setUrl(connectionURL.toString());
connectionPool.setUsername(db.getUserName());
connectionPool.setPassword(db.getPassword());
// tweak pool configuration
connectionPool.setInitialSize(3); // start with 3 connections
connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections
connectionPool.setValidationQuery("SELECT version()");
} catch (UserPreferencesException ex) {
throw new HealthMonitorException("Error loading database configuration", ex);
}
}
/**
* Shut down the connection pool
* @throws HealthMonitorException
*/
private void shutdownConnections() throws HealthMonitorException {
try {
synchronized(this) {
if(connectionPool != null){
connectionPool.close();
connectionPool = null; // force it to be re-created on next connect()
}
}
} catch (SQLException ex) {
throw new HealthMonitorException("Failed to close existing database connections.", ex); // NON-NLS
}
}
/**
* Get a database connection.
* Sets up the connection pool if needed.
* @return The Connection object
* @throws HealthMonitorException
*/
private Connection connect() throws HealthMonitorException {
synchronized (this) {
if (connectionPool == null) {
setupConnectionPool();
}
}
try {
return connectionPool.getConnection();
} catch (SQLException ex) {
throw new HealthMonitorException("Error getting connection from connection pool.", ex); // NON-NLS
}
}
/**
* Test whether the database schema has been initialized.
* We do this by looking for the version number.
* @return True if it has been initialized, false otherwise.
* @throws HealthMonitorException
*/
private boolean databaseIsInitialized() throws HealthMonitorException {
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
ResultSet resultSet = null;
try (Statement statement = conn.createStatement()) {
resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
return resultSet.next();
} catch (SQLException ex) {
// This likely just means that the db_info table does not exist
return false;
} finally {
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing result set", ex);
}
}
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing Connection.", ex);
}
}
}
/**
* Get the current schema version
* @return the current schema version
* @throws HealthMonitorException
*/
private CaseDbSchemaVersionNumber getVersion() throws HealthMonitorException {
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
ResultSet resultSet = null;
try (Statement statement = conn.createStatement()) {
int minorVersion = 0;
int majorVersion = 0;
resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'");
if (resultSet.next()) {
String minorVersionStr = resultSet.getString("value");
try {
minorVersion = Integer.parseInt(minorVersionStr);
} catch (NumberFormatException ex) {
throw new HealthMonitorException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt");
}
}
resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
if (resultSet.next()) {
String majorVersionStr = resultSet.getString("value");
try {
majorVersion = Integer.parseInt(majorVersionStr);
} catch (NumberFormatException ex) {
throw new HealthMonitorException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt");
}
}
return new CaseDbSchemaVersionNumber(majorVersion, minorVersion);
} catch (SQLException ex) {
throw new HealthMonitorException("Error initializing database", ex);
} finally {
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing result set", ex);
}
}
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing Connection.", ex);
}
}
}
/**
* Initialize the database.
* @throws HealthMonitorException
*/
private void initializeDatabaseSchema() throws HealthMonitorException {
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
try (Statement statement = conn.createStatement()) {
conn.setAutoCommit(false);
String createTimingTable =
"CREATE TABLE IF NOT EXISTS timing_data (" +
"id SERIAL PRIMARY KEY," +
"name text NOT NULL," +
"host text NOT NULL," +
"timestamp bigint NOT NULL," +
"count bigint NOT NULL," +
"average double precision NOT NULL," +
"max double precision NOT NULL," +
"min double precision NOT NULL" +
")";
statement.execute(createTimingTable);
String createDbInfoTable =
"CREATE TABLE IF NOT EXISTS db_info (" +
"id SERIAL PRIMARY KEY NOT NULL," +
"name text NOT NULL," +
"value text NOT NULL" +
")";
statement.execute(createDbInfoTable);
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')");
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')");
conn.commit();
} catch (SQLException ex) {
try {
conn.rollback();
} catch (SQLException ex2) {
logger.log(Level.SEVERE, "Rollback error");
}
throw new HealthMonitorException("Error initializing database", ex);
} finally {
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing connection.", ex);
}
}
}
/**
* The task called by the ScheduledThreadPoolExecutor to handle
* the database writes.
*/
static final class DatabaseWriteTask implements Runnable {
/**
* Write current metric data to the database
*/
@Override
public void run() {
try {
getInstance().gatherTimerBasedMetrics();
getInstance().writeCurrentStateToDatabase();
} catch (HealthMonitorException ex) {
logger.log(Level.SEVERE, "Error writing current metrics to database", ex); //NON-NLS
}
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (Case.Events.valueOf(evt.getPropertyName())) {
case CURRENT_CASE:
if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) {
// When a case is closed, write the current metrics to the database
healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask());
}
break;
}
}
/**
* Get an exclusive lock for the health monitor database.
* Acquire this before creating, initializing, or updating the database schema.
* @return The lock
* @throws HealthMonitorException
*/
private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{
try {
CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES);
if(lock != null){
return lock;
}
throw new HealthMonitorException("Error acquiring database lock");
} catch (InterruptedException | CoordinationService.CoordinationServiceException ex){
throw new HealthMonitorException("Error acquiring database lock", ex);
}
}
/**
* Get an shared lock for the health monitor database.
* Acquire this before database reads or writes.
* @return The lock
* @throws HealthMonitorException
*/
private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException{
try {
String databaseNodeName = DATABASE_NAME;
CoordinationService.Lock lock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES);
if(lock != null){
return lock;
}
throw new HealthMonitorException("Error acquiring database lock");
} catch (InterruptedException | CoordinationService.CoordinationServiceException ex){
throw new HealthMonitorException("Error acquiring database lock");
}
}
/**
* Internal class for collecting timing metrics.
* Instead of storing each TimingMetric, we only store the min and max
* seen and the number of metrics and total duration to compute the average
* later.
* One TimingInfo instance should be created per metric name, and
* additional timing metrics will be added to it.
*/
private class TimingInfo {
private long count; // Number of metrics collected
private double sum; // Sum of the durations collected (nanoseconds)
private double max; // Maximum value found (nanoseconds)
private double min; // Minimum value found (nanoseconds)
TimingInfo(TimingMetric metric) throws HealthMonitorException {
count = 1;
sum = metric.getDuration();
max = metric.getDuration();
min = metric.getDuration();
}
/**
* Add a new TimingMetric to an existing TimingInfo object.
* This is called in a synchronized block for almost all new
* TimingMetric objects, so do as little processing here as possible.
* @param metric The new metric
* @throws HealthMonitorException Will be thrown if the metric hasn't been stopped
*/
void addMetric(TimingMetric metric) throws HealthMonitorException {
// Keep track of needed info to calculate the average
count++;
sum += metric.getDuration();
// Check if this is the longest duration seen
if(max < metric.getDuration()) {
max = metric.getDuration();
}
// Check if this is the lowest duration seen
if(min > metric.getDuration()) {
min = metric.getDuration();
}
}
/**
* Get the average duration
* @return average duration (milliseconds)
*/
double getAverage() {
return sum / count;
}
/**
* Get the maximum duration
* @return maximum duration (milliseconds)
*/
double getMax() {
return max;
}
/**
* Get the minimum duration
* @return minimum duration (milliseconds)
*/
double getMin() {
return min;
}
/**
* Get the total number of metrics collected
* @return number of metrics collected
*/
long getCount() {
return count;
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.healthmonitor;
/**
* Exception used internally by the Services Health Monitor
*/
class HealthMonitorException extends Exception {
private static final long serialVersionUID = 1L;
HealthMonitorException(String message) {
super(message);
}
HealthMonitorException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,53 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.healthmonitor;
import java.util.logging.Level;
import org.openide.modules.ModuleInstall;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
public class Installer extends ModuleInstall {
private static final Logger logger = Logger.getLogger(Installer.class.getName());
private static final long serialVersionUID = 1L;
private static Installer instance;
public synchronized static Installer getDefault() {
if (instance == null) {
instance = new Installer();
}
return instance;
}
private Installer() {
super();
}
@Override
public void restored() {
try {
EnterpriseHealthMonitor.startUpIfEnabled();
} catch (HealthMonitorException ex) {
logger.log(Level.SEVERE, "Error starting health services monitor", ex);
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.healthmonitor;
/**
* Used to calculate and report timing metrics.
*/
public class TimingMetric {
private final String name;
private final long startingTimestamp;
private Double duration;
TimingMetric(String name) {
this.name = name;
this.startingTimestamp = System.nanoTime();
this.duration = null;
}
/**
* Record how long the metric was running.
*/
void stopTiming() {
long endingTimestamp = System.nanoTime();
this.duration = (double)(endingTimestamp - startingTimestamp) / 1000000;
}
/**
* Get the name of metric
* @return name
*/
String getName() {
return name;
}
/**
* Get the duration of the metric. Will throw an exception if the
* metric has not been stopped.
* @return how long the metric was running (milliseconds)
* @throws HealthMonitorException
*/
double getDuration() throws HealthMonitorException {
if (duration != null) {
return duration;
} else {
throw new HealthMonitorException("getDuration() called before stopTiming()");
}
}
/**
* Normalize the metric by dividing the time by the given counter.
* If the counter is zero, it will be treated the same way as if the
* counter were one.
* @param count Value to divide the duration by
* @throws HealthMonitorException
*/
void normalize(long count) throws HealthMonitorException {
if (duration != null) {
if(count < 0) {
throw new HealthMonitorException("normalize() called with negative count (" + count + ")");
} else if(count > 1) {
duration = duration / count;
} // If count is 0 or 1, do nothing
} else {
throw new HealthMonitorException("normalize() called before stopTiming()");
}
}
}

View File

@ -501,13 +501,16 @@ public final class IngestJobSettings {
this.warnings.add(warning);
}
} else {
try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) {
settings = (IngestModuleIngestJobSettings) in.readObject();
} catch (IOException | ClassNotFoundException exception) {
String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS
logger.log(Level.WARNING, warning, exception);
this.warnings.add(warning);
}
// @@@ BC Jython serialization is currently broken and this
// throws an exception. (-2323). Commenting out so that
// Python modules will at least load with default settings.
// try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) {
// settings = (IngestModuleIngestJobSettings) in.readObject();
// } catch (IOException | ClassNotFoundException exception) {
// String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS
// logger.log(Level.WARNING, warning, exception);
// this.warnings.add(warning);
// }
}
}
if (settings == null) {

View File

@ -0,0 +1,179 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.modules.encryptiondetection;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.Volume;
import org.sleuthkit.datamodel.VolumeSystem;
/**
* Data source module to detect encryption.
*/
final class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModule {
private final IngestServices services = IngestServices.getInstance();
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
private Blackboard blackboard;
private double calculatedEntropy;
private final double minimumEntropy;
/**
* Create an EncryptionDetectionDataSourceIngestModule object that will
* detect volumes that are encrypted and create blackboard artifacts as
* appropriate. The supplied EncryptionDetectionIngestJobSettings object is
* used to configure the module.
*/
EncryptionDetectionDataSourceIngestModule(EncryptionDetectionIngestJobSettings settings) {
minimumEntropy = settings.getMinimumEntropy();
}
@Override
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
try {
validateSettings();
blackboard = Case.getOpenCase().getServices().getBlackboard();
} catch (NoCurrentCaseException ex) {
throw new IngestModule.IngestModuleException("Exception while getting open case.", ex);
}
}
@Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
try {
if (dataSource instanceof Image) {
List<VolumeSystem> volumeSystems = ((Image) dataSource).getVolumeSystems();
for (VolumeSystem volumeSystem : volumeSystems) {
for (Volume volume : volumeSystem.getVolumes()) {
if (isVolumeEncrypted(volume)) {
return flagVolume(volume);
}
}
}
}
} catch (ReadContentInputStream.ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Unable to read data source '%s'", dataSource.getName()), ex);
return IngestModule.ProcessResult.ERROR;
} catch (IOException | TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Unable to process data source '%s'", dataSource.getName()), ex);
return IngestModule.ProcessResult.ERROR;
}
return IngestModule.ProcessResult.OK;
}
/**
* Validate the relevant settings for the
* EncryptionDetectionDataSourceIngestModule
*
* @throws IngestModule.IngestModuleException If the input is empty,
* invalid, or out of range.
*
*/
private void validateSettings() throws IngestModule.IngestModuleException {
EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
}
/**
* Create a blackboard artifact.
*
* @param The volume to be processed.
*
* @return 'OK' if the volume was processed successfully, or 'ERROR' if
* there was a problem.
*/
private IngestModule.ProcessResult flagVolume(Volume volume) {
try {
BlackboardArtifact artifact = volume.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED);
try {
/*
* Index the artifact for keyword search.
*/
blackboard.indexArtifact(artifact);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
}
/*
* Send an event to update the view with the new result.
*/
services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, Collections.singletonList(artifact)));
/*
* Make an ingest inbox message.
*/
StringBuilder detailsSb = new StringBuilder("");
detailsSb.append("File: ").append(volume.getParent().getUniquePath()).append(volume.getName()).append("<br/>\n");
detailsSb.append("Entropy: ").append(calculatedEntropy);
services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
"Encryption Detected Match: " + volume.getName(),
detailsSb.toString(),
volume.getName(),
artifact));
return IngestModule.ProcessResult.OK;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", volume.getName()), ex); //NON-NLS
return IngestModule.ProcessResult.ERROR;
}
}
/**
* This method checks if the Volume input is encrypted. Initial
* qualifications require that the Volume not have a file system.
*
* @param volume Volume to be checked.
*
* @return True if the Volume is encrypted.
*/
private boolean isVolumeEncrypted(Volume volume) throws ReadContentInputStream.ReadContentInputStreamException, IOException, TskCoreException {
/*
* Criteria for the checks in this method are partially based on
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection
*/
if (volume.getFileSystems().isEmpty()) {
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume);
if (calculatedEntropy >= minimumEntropy) {
return true;
}
}
return false;
}
}

View File

@ -18,12 +18,18 @@
*/
package org.sleuthkit.autopsy.modules.encryptiondetection;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.datamodel.ReadContentInputStream;
import java.io.BufferedInputStream;
import java.io.InputStream;
import org.apache.tika.exception.EncryptedDocumentException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
@ -37,29 +43,20 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* File ingest module to detect encryption.
* File ingest module to detect encryption and password protection.
*/
final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter {
static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5;
static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB;
static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true;
static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true;
static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0;
static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0;
static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1;
private static final int FILE_SIZE_MODULUS = 512;
private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2))
private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256;
private final IngestServices services = IngestServices.getInstance();
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
private FileTypeDetector fileTypeDetector;
@ -73,9 +70,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/**
* Create a EncryptionDetectionFileIngestModule object that will detect
* files that are encrypted and create blackboard artifacts as appropriate.
* The supplied EncryptionDetectionIngestJobSettings object is used to
* configure the module.
* files that are either encrypted or password protected and create
* blackboard artifacts as appropriate. The supplied
* EncryptionDetectionIngestJobSettings object is used to configure the
* module.
*/
EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
minimumEntropy = settings.getMinimumEntropy();
@ -101,104 +99,6 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
public IngestModule.ProcessResult process(AbstractFile file) {
try {
if (isFileEncrypted(file)) {
return flagFile(file);
}
} catch (ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR;
} catch (IOException | TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR;
}
return IngestModule.ProcessResult.OK;
}
/**
* Validate ingest module settings.
*
* @throws IngestModule.IngestModuleException If the input is empty,
* invalid, or out of range.
*/
@NbBundle.Messages({
"EncryptionDetectionFileIngestModule.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.",
"EncryptionDetectionFileIngestModule.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater."
})
private void validateSettings() throws IngestModule.IngestModuleException {
if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) {
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumEntropyInput());
}
if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) {
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumFileSizeInput());
}
}
/**
* Create a blackboard artifact.
*
* @param The file to be processed.
*
* @return 'OK' if the file was processed successfully, or 'ERROR' if there
* was a problem.
*/
private IngestModule.ProcessResult flagFile(AbstractFile file) {
try {
BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED);
try {
/*
* Index the artifact for keyword search.
*/
blackboard.indexArtifact(artifact);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
}
/*
* Send an event to update the view with the new result.
*/
services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, Collections.singletonList(artifact)));
/*
* Make an ingest inbox message.
*/
StringBuilder detailsSb = new StringBuilder();
detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append("<br/>\n");
detailsSb.append("Entropy: ").append(calculatedEntropy);
services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
"Encryption Detected Match: " + file.getName(),
detailsSb.toString(),
file.getName(),
artifact));
return IngestModule.ProcessResult.OK;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); //NON-NLS
return IngestModule.ProcessResult.ERROR;
}
}
/**
* This method checks if the AbstractFile input is encrypted. Initial
* qualifications require that it be an actual file that is not known, meets
* file size requirements, and has a MIME type of
* 'application/octet-stream'.
*
* @param file AbstractFile to be checked.
*
* @return True if the AbstractFile is encrypted.
*/
private boolean isFileEncrypted(AbstractFile file) throws ReadContentInputStreamException, IOException, TskCoreException {
/*
* Criteria for the checks in this method are partially based on
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection
*/
boolean possiblyEncrypted = false;
/*
* Qualify the file type.
*/
@ -211,81 +111,145 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
* Qualify the file against hash databases.
*/
if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) {
/*
* Qualify the size.
*/
long contentSize = file.getSize();
if (contentSize >= minimumFileSize) {
if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
/*
* Qualify the MIME type.
*/
String mimeType = fileTypeDetector.getMIMEType(file);
if (mimeType.equals("application/octet-stream")) {
possiblyEncrypted = true;
if (isFileEncryptionSuspected(file)) {
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED);
}
} else {
if (isFilePasswordProtected(file)) {
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
}
}
}
}
} catch (ReadContentInputStreamException | SAXException | TikaException ex) {
logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR;
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR;
}
if (possiblyEncrypted) {
calculatedEntropy = calculateEntropy(file);
if (calculatedEntropy >= minimumEntropy) {
return true;
}
}
return false;
return IngestModule.ProcessResult.OK;
}
/**
* Calculate the entropy of the file. The result is used to qualify the file
* as an encrypted file.
* Validate ingest module settings.
*
* @param file The file to be calculated against.
*
* @return The entropy of the file.
*
* @throws IOException If there is a failure closing or reading from the
* InputStream.
*/
private double calculateEntropy(AbstractFile file) throws ReadContentInputStreamException, IOException {
/*
* Logic in this method is based on
* https://github.com/willjasen/entropy/blob/master/entropy.java
* @throws IngestModule.IngestModuleException If the input is empty,
* invalid, or out of range.
*/
private void validateSettings() throws IngestModule.IngestModuleException {
EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
}
/**
* Create a blackboard artifact.
*
* @param file The file to be processed.
* @param artifactType The type of artifact to create.
*
* @return 'OK' if the file was processed successfully, or 'ERROR' if there
* was a problem.
*/
private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) {
try {
BlackboardArtifact artifact = file.newArtifact(artifactType);
try {
/*
* Index the artifact for keyword search.
*/
blackboard.indexArtifact(artifact);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
}
/*
* Send an event to update the view with the new result.
*/
services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact)));
/*
* Make an ingest inbox message.
*/
StringBuilder detailsSb = new StringBuilder();
detailsSb.append("File: ").append(file.getParentPath()).append(file.getName());
if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
detailsSb.append("<br/>\n").append("Entropy: ").append(calculatedEntropy);
}
services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
artifactType.getDisplayName() + " Match: " + file.getName(),
detailsSb.toString(),
file.getName(),
artifact));
return IngestModule.ProcessResult.OK;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); //NON-NLS
return IngestModule.ProcessResult.ERROR;
}
}
/**
* This method checks if the AbstractFile input is password protected.
*
* @param file AbstractFile to be checked.
*
* @return True if the file is password protected.
*
* @throws ReadContentInputStreamException If there is a failure reading
* from the InputStream.
* @throws IOException If there is a failure closing or
* reading from the InputStream.
* @throws SAXException If there was an issue parsing the
* file with Tika.
* @throws TikaException If there was an issue parsing the
* file with Tika.
*/
private boolean isFilePasswordProtected(AbstractFile file) throws ReadContentInputStreamException, IOException, SAXException, TikaException {
boolean passwordProtected = false;
switch (file.getMIMEType()) {
case "application/x-ooxml-protected":
/*
* Office Open XML files that are password protected can be
* determined so simply by checking the MIME type.
*/
passwordProtected = true;
break;
case "application/msword":
case "application/vnd.ms-excel":
case "application/vnd.ms-powerpoint":
case "application/pdf":
/*
* A file of one of these types will be determined to be
* password protected or not by attempting to parse it via Tika.
*/
InputStream in = null;
BufferedInputStream bin = null;
try {
in = new ReadContentInputStream(file);
bin = new BufferedInputStream(in);
ContentHandler handler = new BodyContentHandler(-1);
Metadata metadata = new Metadata();
metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName());
AutoDetectParser parser = new AutoDetectParser();
parser.parse(bin, handler, metadata, new ParseContext());
} catch (EncryptedDocumentException ex) {
/*
* Determine the number of times each byte value appears.
* File is determined to be password protected.
*/
int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE];
int readByte;
while ((readByte = bin.read()) != -1) {
byteOccurences[readByte]++;
}
/*
* Calculate the entropy based on the byte occurence counts.
*/
long dataLength = file.getSize() - 1;
double entropyAccumulator = 0;
for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) {
if (byteOccurences[i] > 0) {
double byteProbability = (double) byteOccurences[i] / (double) dataLength;
entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2);
}
}
return -entropyAccumulator;
passwordProtected = true;
} finally {
if (in != null) {
in.close();
@ -295,4 +259,48 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
}
}
}
return passwordProtected;
}
/**
* This method checks if the AbstractFile input is encrypted. It must meet
* file size requirements before its entropy is calculated. If the entropy
* result meets the minimum entropy value set, the file will be considered
* to be possibly encrypted.
*
* @param file AbstractFile to be checked.
*
* @return True if encryption is suspected.
*
* @throws ReadContentInputStreamException If there is a failure reading
* from the InputStream.
* @throws IOException If there is a failure closing or
* reading from the InputStream.
*/
private boolean isFileEncryptionSuspected(AbstractFile file) throws ReadContentInputStreamException, IOException {
/*
* Criteria for the checks in this method are partially based on
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection
*/
boolean possiblyEncrypted = false;
/*
* Qualify the size.
*/
long contentSize = file.getSize();
if (contentSize >= minimumFileSize) {
if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
/*
* Qualify the entropy.
*/
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
if (calculatedEntropy >= minimumEntropy) {
possiblyEncrypted = true;
}
}
}
return possiblyEncrypted;
}
}

View File

@ -26,7 +26,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJobSettings {
private static final long serialVersionUID = 1L;
private static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5;
private static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB;
private static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true;
private static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true;
private double minimumEntropy;
private int minimumFileSize;
private boolean fileSizeMultipleEnforced;
@ -36,10 +39,10 @@ final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJo
* Instantiate the ingest job settings with default values.
*/
EncryptionDetectionIngestJobSettings() {
this.minimumEntropy = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_ENTROPY;
this.minimumFileSize = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_FILE_SIZE;
this.fileSizeMultipleEnforced = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED;
this.slackFilesAllowed = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_SLACK_FILES_ALLOWED;
this.minimumEntropy = DEFAULT_CONFIG_MINIMUM_ENTROPY;
this.minimumFileSize = DEFAULT_CONFIG_MINIMUM_FILE_SIZE;
this.fileSizeMultipleEnforced = DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED;
this.slackFilesAllowed = DEFAULT_CONFIG_SLACK_FILES_ALLOWED;
}
/**

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017 Basis Technology Corp.
* Copyright 2017-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -30,7 +30,8 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
/**
* A factory that creates file ingest modules that detect encryption.
* A factory that creates file ingest modules that detect encryption and
* password protection.
*/
@ServiceProvider(service = IngestModuleFactory.class)
@Messages({
@ -106,11 +107,14 @@ public class EncryptionDetectionModuleFactory implements IngestModuleFactory {
@Override
public boolean isDataSourceIngestModuleFactory() {
return false;
return true;
}
@Override
public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) {
throw new UnsupportedOperationException();
if (!(settings instanceof EncryptionDetectionIngestJobSettings)) {
throw new IllegalArgumentException("Expected settings argument to be an instance of EncryptionDetectionIngestJobSettings.");
}
return new EncryptionDetectionDataSourceIngestModule((EncryptionDetectionIngestJobSettings) settings);
}
}

View File

@ -0,0 +1,131 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.modules.encryptiondetection;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.Content;
/**
* Class containing common methods concerning the Encryption Detection module.
*/
final class EncryptionDetectionTools {
private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2))
private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256;
static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0;
static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0;
static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1;
@NbBundle.Messages({
"EncryptionDetectionTools.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0."
})
/**
* Check if the minimum entropy setting is in the accepted range for this
* module.
*/
static void validateMinEntropyValue(double minimumEntropy) throws IngestModule.IngestModuleException {
if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) {
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumEntropyInput());
}
}
@NbBundle.Messages({
"EncryptionDetectionTools.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater."
})
/**
* Check if the minimum file size setting is in the accepted range for this
* module.
*/
static void validateMinFileSizeValue(int minimumFileSize) throws IngestModule.IngestModuleException {
if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) {
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumFileSizeInput());
}
}
/**
* Calculate the entropy of the content. The result is used to qualify the
* content as possibly encrypted.
*
* @param content The content to be calculated against.
*
* @return The entropy of the content.
*
* @throws ReadContentInputStreamException If there is a failure reading
* from the InputStream.
* @throws IOException If there is a failure closing or
* reading from the InputStream.
*/
static double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException {
/*
* Logic in this method is based on
* https://github.com/willjasen/entropy/blob/master/entropy.java
*/
InputStream in = null;
BufferedInputStream bin = null;
try {
in = new ReadContentInputStream(content);
bin = new BufferedInputStream(in);
/*
* Determine the number of times each byte value appears.
*/
int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE];
int readByte;
while ((readByte = bin.read()) != -1) {
byteOccurences[readByte]++;
}
/*
* Calculate the entropy based on the byte occurence counts.
*/
long dataLength = content.getSize() - 1;
double entropyAccumulator = 0;
for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) {
if (byteOccurences[i] > 0) {
double byteProbability = (double) byteOccurences[i] / (double) dataLength;
entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2);
}
}
return -entropyAccumulator;
} finally {
if (in != null) {
in.close();
}
if (bin != null) {
bin.close();
}
}
}
/**
* Private constructor for Encryption Detection Tools class.
*/
private EncryptionDetectionTools() {
}
}

View File

@ -33,6 +33,8 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.autopsy.ingest.FileIngestModule;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
@ -182,8 +184,20 @@ public class HashDbIngestModule implements FileIngestModule {
String md5Hash = file.getMd5Hash();
if (md5Hash == null || md5Hash.isEmpty()) {
try {
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
long calcstart = System.currentTimeMillis();
md5Hash = HashUtility.calculateMd5Hash(file);
if (file.getSize() > 0) {
// Surprisingly, the hash calculation does not seem to be correlated that
// strongly with file size until the files get large.
// Only normalize if the file size is greater than ~1MB.
if (file.getSize() < 1000000) {
EnterpriseHealthMonitor.submitTimingMetric(metric);
} else {
// In testing, this normalization gave reasonable resuls
EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000);
}
}
file.setMd5Hash(md5Hash);
long delta = (System.currentTimeMillis() - calcstart);
totals.totalCalctime.addAndGet(delta);

View File

@ -225,7 +225,7 @@ public final class PromptDialogManager {
@NbBundle.Messages({
"PromptDialogManager.showTimeLineDisabledMessage.contentText="
+ "Timeline functionality is not available for Linux yet."
+ "Timeline functionality is not available yet."
+ " Timeline will be disabled. ",
"PromptDialogManager.showTimeLineDisabledMessage.headerText="})
static void showTimeLineDisabledMessage() {

View File

@ -0,0 +1,278 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.ingest;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.Assert.assertEquals;
import junit.framework.Test;
import org.netbeans.junit.NbModuleSuite;
import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions;
import org.python.icu.impl.Assert;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.ImageDSProcessor;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType;
import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory;
import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory;
import org.sleuthkit.autopsy.testutils.CaseUtils;
import org.sleuthkit.autopsy.testutils.IngestUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.TskCoreException;
public class EmbeddedFileTest extends NbTestCase {
private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "EmbeddedFileTest");
private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd");
public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6";
private static final int DEEP_FOLDER_COUNT = 25;
private Case openCase;
public static Test suite() {
NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EmbeddedFileTest.class).
clusters(".*").
enableModules(".*");
return conf.suite();
}
public EmbeddedFileTest(String name) {
super(name);
}
@Override
public void setUp() {
CaseUtils.createCase(CASE_DIRECTORY_PATH);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
try {
openCase = Case.getOpenCase();
} catch (NoCurrentCaseException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
IngestModuleTemplate embeddedTemplate = IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory());
IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory());
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(embeddedTemplate);
templates.add(hashLookupTemplate);
IngestJobSettings ingestJobSettings = new IngestJobSettings(EmbeddedFileTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates);
try {
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
@Override
public void tearDown() {
CaseUtils.closeCase();
CaseUtils.deleteCaseDir(CASE_DIRECTORY_PATH);
}
public void testEncryption() {
try {
List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'");
String protectedName1 = "password_protected.zip";
String protectedName2 = "level1_protected.zip";
String protectedName3 = "42.zip";
assertEquals(2207, results.size());
int passwdProtectedZips = 0;
for (AbstractFile file : results) {
//.zip file has artifact TSK_ENCRYPTION_DETECTED
if (file.getName().equalsIgnoreCase(protectedName1) || file.getName().equalsIgnoreCase(protectedName2) || file.getName().equalsIgnoreCase(protectedName3)){
ArrayList<BlackboardArtifact> artifacts = file.getAllArtifacts();
assertEquals(1, artifacts.size());
for (BlackboardArtifact artifact : artifacts) {
assertEquals(artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID());
passwdProtectedZips++;
}
} else {//No other files have artifact defined
assertEquals(0, file.getAllArtifacts().size());
}
}
//Make sure 3 password protected zip files have been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing.
assertEquals(3, passwdProtectedZips);
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public void testBigFolder() {
final int numOfFilesToTest = 1000;
try {
//Get all files under 'big folder' directory except '.' '..' 'slack' files
List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%big folder/' and name != '.' and name != '..' and extension NOT LIKE '%slack'");
assertEquals(numOfFilesToTest, results.size()); //There are 1000 files
int numOfFilesTested = 0;
for (AbstractFile file : results) {
String fileName = file.getName();
//File name should like file1.txt, file2.txt ... file1000.txt
String errMsg = String.format("File name %s doesn't follow the expected naming convention: fileNaturalNumber.txt, eg. file234.txt.", fileName);
assertTrue(errMsg, file.getName().matches("file[1-9]\\d*.txt"));
String hashValue = file.getMd5Hash();
//All files have the same hash value
assertEquals(HASH_VALUE, hashValue);
numOfFilesTested++;
}
//Make sure 1000 files have been tested
assertEquals(numOfFilesToTest, numOfFilesTested);
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public void testDeepFolder() {
try {
//Get all files under 'deep folder' directory except '.' '..'
List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%deep folder/' and name != '.' and name != '..'");
assertEquals(1, results.size());
StringBuffer dirReached = new StringBuffer();
ArrayList<String> fileReached = new ArrayList<>();
checkEachFileInDeepFolder(results.get(0), dirReached, fileReached, 0);
//Check that all 25 folders/files have been reached
assertEquals(DEEP_FOLDER_COUNT, fileReached.size());
//Make sure the test reached the last directory 'dir25'. The whole directory is dir1/dir2...dir24/dir25/
assertTrue(dirReached.toString().startsWith("dir1/dir2/"));
assertTrue(dirReached.toString().endsWith("dir24/dir25/"));
//Make sure the test reached the last file.txt in dir1/dir2...dir24/dir25/
assertTrue(fileReached.get(0).endsWith(dirReached.toString() + "file.txt"));
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public void testEmbeddedFile() {
try {
//Query level3.txt under '/ZIP/embedded/level3.zip/'
List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level3.txt' and parent_path = '/ZIP/embedded/level3.zip/'");
assertEquals(1, results.size());
//Query level2.txt under '/ZIP/embedded/level3.zip/level2.zip/'
results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level2.txt' and parent_path = '/ZIP/embedded/level3.zip/level2.zip/'");
assertEquals(1, results.size());
//Query level1.txt under '/ZIP/embedded/level3.zip/level2.zip/level1.zip/'
results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level1.txt' and parent_path = '/ZIP/embedded/level3.zip/level2.zip/level1.zip/'");
assertEquals(1, results.size());
//Confirm that we can reach level1.txt from the embedded folder
results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%embedded/' and name != '.' and name != '..' and extension NOT LIKE '%slack%'");
assertEquals(1, results.size());
assertTrue(checkFileInEmbeddedFolder(results.get(0)));
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public void testContent() {
final int numOfFilesToTest = 1029;
try {
//All files with txt extension should have the same hash value,
//except the zip file with txt extension and the .txt files extracted from password protected zip shouldn't have hash value
List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere(
"extension = 'txt' and name != 'zipFileWithTxtExtension.txt' and parent_path NOT LIKE '%_protected%'");
assertEquals(numOfFilesToTest, results.size());
int numOfHashTested = 0;
for (AbstractFile file : results) {
String fileName = file.getName();
String errMsg = String.format("File name %s doesn't have the extected hash value %s.", fileName, HASH_VALUE);
assertEquals(errMsg, HASH_VALUE, file.getMd5Hash());
numOfHashTested++;
}
//Make sure the hash value of 1029 files have been tested
assertEquals(numOfFilesToTest, numOfHashTested);
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public void testExtension() {
try {
//Query zipFileWithTxtExtension.txt at extension folder
List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere("extension = 'txt' and parent_path = '/ZIP/extension/zipFileWithTxtExtension.txt/'");
assertEquals(1, results.size());
assertEquals("file.txt wasn't extracted from the file: zipFileWithTxtExtension.txt", "file.txt", results.get(0).getName());
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
private void checkEachFileInDeepFolder(AbstractFile file, StringBuffer dirReached, ArrayList<String> fileReached, int numOfDir) {
String errMsg = String.format("File/Directory name is not as expected name: %s", file.getName());
if (file.isDir() && !file.getName().equals(".") && !file.getName().equals("..")) {
numOfDir++;
assertEquals(errMsg, String.format("dir%d", numOfDir), file.getName());
dirReached.append(file.getName()).append("/");
try {
List<AbstractFile> children = file.listFiles();
for (AbstractFile child : children) {
checkEachFileInDeepFolder(child, dirReached, fileReached, numOfDir);
}
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
} else if (file.isFile() && !file.getName().endsWith("slack")) {
assertEquals(errMsg, "file.txt", file.getName());
fileReached.add(file.getParentPath() + file.getName());
}
}
private boolean checkFileInEmbeddedFolder(AbstractFile file) {
if (file.getName().equals("level1.txt")) {
return true;
} else if (file.getNameExtension().equalsIgnoreCase("zip")) {
try {
List<AbstractFile> children = file.listFiles();
for (AbstractFile child : children) {
return checkFileInEmbeddedFolder(child);
}
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
} else {
assertTrue(file.getNameExtension().equalsIgnoreCase("txt"));
}
return false;
}
}

View File

@ -18,20 +18,14 @@
*/
package org.sleuthkit.autopsy.ingest;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import static junit.framework.Assert.assertFalse;
import org.netbeans.junit.NbModuleSuite;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import org.sleuthkit.autopsy.casemodule.CaseDetails;
import junit.framework.Test;
import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions;
import org.python.icu.impl.Assert;
@ -39,7 +33,6 @@ import org.sleuthkit.autopsy.casemodule.ImageDSProcessor;
import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType;
import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;
@ -50,11 +43,10 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.FullNameCond
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCondition;
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition;
import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory;
import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner;
import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner.ProcessorCallback;
import org.sleuthkit.autopsy.testutils.CaseUtils;
import org.sleuthkit.autopsy.testutils.IngestJobRunner;
import org.sleuthkit.autopsy.testutils.IngestUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
@ -77,26 +69,14 @@ public class IngestFileFiltersTest extends NbTestCase {
@Override
public void tearDown() {
try {
Case.closeCurrentCase();
//Seems like we need some time to close the case.
try {
Thread.sleep(2000);
} catch (Exception ex) {
}
assertFalse(Case.isCaseOpen());
} catch (CaseActionException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
CaseUtils.closeCase();
}
public void testBasicDir() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testBasicDir");
createCase(casePath);
CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rule = new HashMap<>();
rule.put("Rule", new Rule("testFileType", null, new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null));
@ -106,8 +86,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, dirFilter);
templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, dirFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = openCase.getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("file.jpg", "dir1");
String mimeType = results.get(0).getMIMEType();
@ -136,9 +117,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testExtAndDirWithOneRule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithOneRule");
createCase(casePath);
CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>();
rules.put("Rule", new Rule("testExtAndDirWithOneRule", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null));
@ -148,8 +129,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter);
templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(62, results.size());
@ -171,9 +153,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testExtAndDirWithTwoRules() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithTwoRules");
createCase(casePath);
CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@ -184,8 +166,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter);
templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(62, results.size());
@ -215,9 +198,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testFullFileNameRule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testFullFileNameRule");
createCase(casePath);
CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>();
rules.put("rule", new Rule("FindFileWithFullName", new FullNameCondition("file.docx"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@ -227,9 +210,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, fullNameFilter);
templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, fullNameFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(62, results.size());
@ -251,9 +234,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testCarvingWithExtRuleAndUnallocSpace() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingWithExtRuleAndUnallocSpace");
createCase(casePath);
CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@ -265,9 +248,10 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, extensionFilter);
templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, extensionFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(70, results.size());
@ -299,9 +283,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testCarvingNoUnallocatedSpace() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingNoUnallocatedSpace");
createCase(casePath);
CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@ -313,8 +297,8 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, extensionFilter);
try {
List<IngestModuleError> errs = IngestJobRunner.runIngestJob(openCase.getDataSources(), ingestJobSettings);
@ -333,9 +317,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testEmbeddedModule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testEmbeddedModule");
createCase(casePath);
CaseUtils.createCase(casePath);
LocalFilesDSProcessor dataSourceProcessor = new LocalFilesDSProcessor();
addDataSourceToCase(dataSourceProcessor, ZIPFILE_PATH);
IngestUtils.addDataSource(dataSourceProcessor, ZIPFILE_PATH);
//Build the filter to find jpg files
HashMap<String, Rule> rules = new HashMap<>();
@ -348,9 +332,10 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, embeddedFilter);
templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, embeddedFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
//get all .jpg files in zip file
List<AbstractFile> results = fileManager.findFiles("%%");
@ -376,64 +361,4 @@ public class IngestFileFiltersTest extends NbTestCase {
Assert.fail(ex);
}
}
private void runIngestJob(List<Content> datasources, ArrayList<IngestModuleTemplate> templates, FilesSet filter) {
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, filter);
try {
List<IngestModuleError> errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings);
for (IngestModuleError err : errs) {
System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString()));
}
assertEquals(0, errs.size());
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
private IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) {
IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings();
IngestModuleTemplate template = new IngestModuleTemplate(factory, settings);
template.setEnabled(true);
return template;
}
private void createCase(Path casePath) {
// Delete the test directory, if it exists
if (casePath.toFile().exists()) {
try {
FileUtils.deleteDirectory(casePath.toFile());
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
assertFalse("Unable to delete existing test directory", casePath.toFile().exists());
// Create the test directory
casePath.toFile().mkdirs();
assertTrue("Unable to create test directory", casePath.toFile().exists());
try {
Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, casePath.toString(), new CaseDetails("IngestFiltersTest"));
} catch (CaseActionException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
assertTrue(casePath.toFile().exists());
}
private void addDataSourceToCase(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) {
try {
ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, dataSourcePath);
List<Content> dataSourceContent = callBack.getDataSourceContent();
assertEquals(1, dataSourceContent.size());
List<String> errorMessages = callBack.getErrorMessages();
assertEquals(0, errorMessages.size());
} catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
}

View File

@ -0,0 +1,193 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.modules.encryptiondetection;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.Assert.assertFalse;
import org.netbeans.junit.NbModuleSuite;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import org.sleuthkit.autopsy.casemodule.CaseDetails;
import junit.framework.Test;
import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions;
import org.python.icu.impl.Assert;
import org.sleuthkit.autopsy.casemodule.ImageDSProcessor;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.ingest.IngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType;
import org.sleuthkit.autopsy.ingest.IngestModuleError;
import org.sleuthkit.autopsy.ingest.IngestModuleFactory;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleTemplate;
import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner;
import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner.ProcessorCallback;
import org.sleuthkit.autopsy.testutils.IngestJobRunner;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
public class EncryptionDetectionTest extends NbTestCase {
private static final String CASE_NAME = "EncryptionDetectionTest";
private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME);
private static final File CASE_DIR = new File(CASE_DIRECTORY_PATH.toString());
private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img");
public static Test suite() {
NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EncryptionDetectionTest.class).
clusters(".*").
enableModules(".*");
return conf.suite();
}
public EncryptionDetectionTest(String name) {
super(name);
}
@Override
public void setUp() {
// Delete the test directory, if it exists
if (CASE_DIRECTORY_PATH.toFile().exists()) {
try {
FileUtils.deleteDirectory(CASE_DIRECTORY_PATH.toFile());
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
assertFalse(String.format("Unable to delete existing test directory '%s'.", CASE_DIRECTORY_PATH.toString()), CASE_DIRECTORY_PATH.toFile().exists());
// Create the test directory
CASE_DIRECTORY_PATH.toFile().mkdirs();
assertTrue(String.format("Unable to create test directory '%s'.", CASE_DIRECTORY_PATH.toString()), CASE_DIRECTORY_PATH.toFile().exists());
try {
Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, CASE_DIRECTORY_PATH.toString(), new CaseDetails(CASE_NAME));
} catch (CaseActionException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
assertTrue(CASE_DIR.exists());
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
try {
ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, IMAGE_PATH);
List<Content> dataSourceContentList = callBack.getDataSourceContent();
String errorMessage = String.format("The data source processor callback should produce 1 data source Content object, but the actual count was %d.", dataSourceContentList.size());
assertEquals(errorMessage, 1, dataSourceContentList.size());
List<String> callbackErrorMessageList = callBack.getErrorMessages();
errorMessage = String.format("The data source processor callback produced %d error messages.", callbackErrorMessageList.size());
assertEquals(errorMessage, 0, callbackErrorMessageList.size());
} catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
@Override
public void tearDown() {
try {
Case.closeCurrentCase();
} catch (CaseActionException ex) {
Exceptions.printStackTrace(ex);
}
}
/**
* Test the Encryption Detection module's password protection detection.
*/
public void testPasswordProtection() {
try {
Case openCase = Case.getOpenCase();
runIngestJob(openCase.getDataSources(), new EncryptionDetectionModuleFactory());
FileManager fileManager = openCase.getServices().getFileManager();
Blackboard bb = openCase.getServices().getBlackboard();
List<AbstractFile> results = fileManager.findFiles("%%", "ole2");
results.addAll(fileManager.findFiles("%%", "ooxml"));
results.addAll(fileManager.findFiles("%%", "pdf"));
for (AbstractFile file : results) {
/*
* Process only non-slack files.
*/
if (file.isFile() && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) {
/*
* Determine which assertions to use for the file based on
* its name.
*/
boolean fileProtected = file.getName().split("\\.")[0].endsWith("-protected");
List<BlackboardArtifact> artifactsList = file.getAllArtifacts();
if (fileProtected) {
/*
* Check that the protected file has one
* TSK_ENCRYPTION_DETECTED artifact.
*/
int artifactsListSize = artifactsList.size();
String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but 1 was expected.", file.getName(), file.getId(), artifactsListSize);
assertEquals(errorMessage, 1, artifactsListSize);
String artifactTypeName = artifactsList.get(0).getArtifactTypeName();
errorMessage = String.format("File '%s' (objId=%d) has an unexpected '%s' artifact.", file.getName(), file.getId(), artifactTypeName);
assertEquals(errorMessage, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.toString(), artifactTypeName);
} else {
/*
* Check that the unprotected file has no artifacts.
*/
int artifactsListSize = artifactsList.size();
String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but none were expected.", file.getName(), file.getId(), artifactsListSize);
assertEquals(errorMessage, 0, artifactsListSize);
}
}
}
} catch (NoCurrentCaseException | TskCoreException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
private void runIngestJob(List<Content> datasources, IngestModuleFactory factory) {
IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings();
IngestModuleTemplate template = new IngestModuleTemplate(factory, settings);
template.setEnabled(true);
ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(template);
IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates);
try {
List<IngestModuleError> ingestModuleErrorsList = IngestJobRunner.runIngestJob(datasources, ingestJobSettings);
String errorMessage = String.format("The ingest job runner produced %d error messages.", ingestModuleErrorsList.size());
assertEquals(errorMessage, 0, ingestModuleErrorsList.size());
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.testutils;
import java.io.IOException;
import java.nio.file.Path;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import org.apache.commons.io.FileUtils;
import org.openide.util.Exceptions;
import org.python.icu.impl.Assert;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import org.sleuthkit.autopsy.casemodule.CaseDetails;
public final class CaseUtils {
private CaseUtils() {
}
public static void createCase(Path caseDirectoryPath) {
//Make sure the test is starting with a clean state. So delete the test directory, if it exists.
deleteCaseDir(caseDirectoryPath);
assertFalse("Unable to delete existing test directory", caseDirectoryPath.toFile().exists());
// Create the test directory
caseDirectoryPath.toFile().mkdirs();
assertTrue("Unable to create test directory", caseDirectoryPath.toFile().exists());
try {
Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), new CaseDetails("IngestFiltersTest"));
} catch (CaseActionException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
assertTrue(caseDirectoryPath.toFile().exists());
}
public static void closeCase() {
try {
Case.closeCurrentCase();
//Seems like we need some time to close the case, so file handler later can delete the case directory.
try {
Thread.sleep(20000);
} catch (Exception ex) {
}
} catch (CaseActionException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public static void deleteCaseDir(Path caseDirectoryPath) {
if (!caseDirectoryPath.toFile().exists()) {
return;
}
try {
FileUtils.deleteDirectory(caseDirectoryPath.toFile());
} catch (IOException ex) {
//We just want to make sure the case directory doesn't exist when the test starts. It shouldn't cause failure if the case directory couldn't be deleted after a test finished.
System.out.println("INFO: Unable to delete case directory: " + caseDirectoryPath.toString());
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.testutils;
import java.nio.file.Path;
import java.util.List;
import static junit.framework.Assert.assertEquals;
import org.openide.util.Exceptions;
import org.python.icu.impl.Assert;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.ingest.IngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleError;
import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleTemplate;
import org.sleuthkit.datamodel.Content;
public final class IngestUtils {
private IngestUtils() {
}
public static void addDataSource(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) {
try {
DataSourceProcessorRunner.ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, dataSourcePath);
List<String> errorMessages = callBack.getErrorMessages();
assertEquals(0, errorMessages.size());
} catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public static void runIngestJob(List<Content> datasources, IngestJobSettings ingestJobSettings) {
try {
List<IngestModuleError> errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings);
for (IngestModuleError err : errs) {
System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString()));
}
assertEquals(0, errs.size());
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex);
}
}
public static IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) {
IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings();
IngestModuleTemplate template = new IngestModuleTemplate(factory, settings);
template.setEnabled(true);
return template;
}
}

View File

@ -33,6 +33,14 @@
<specification-version>1.49.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.swing.outline</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<specification-version>1.34.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.openide.awt</code-name-base>
<build-prerequisite/>
@ -49,6 +57,14 @@
<specification-version>7.41.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.openide.explorer</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<specification-version>6.62.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.openide.filesystems</code-name-base>
<build-prerequisite/>

View File

@ -0,0 +1,155 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.experimental.autoingest;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog;
final class AutoIngestAdminActions {
@NbBundle.Messages({"AutoIngestAdminActions.progressDialogAction.title=Ingest Progress"})
static final class ProgressDialogAction extends AbstractAction {
private static final long serialVersionUID = 1L;
ProgressDialogAction() {
super(Bundle.AutoIngestAdminActions_progressDialogAction_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-3734
final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID);
if (tc != null) {
AutoIngestDashboard dashboard = tc.getAutoIngestDashboard();
if (dashboard != null) {
new IngestProgressSnapshotDialog(dashboard.getTopLevelAncestor(), true);
}
}
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.cancelJobAction.title=Cancel Job"})
static final class CancelJobAction extends AbstractAction {
private static final long serialVersionUID = 1L;
CancelJobAction() {
super(Bundle.AutoIngestAdminActions_cancelJobAction_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-3738
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.cancelModuleAction.title=Cancel Module"})
static final class CancelModuleAction extends AbstractAction {
private static final long serialVersionUID = 1L;
CancelModuleAction() {
super(Bundle.AutoIngestAdminActions_cancelModuleAction_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-3738
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.reprocessJobAction.title=Reprocess Job"})
static final class ReprocessJobAction extends AbstractAction {
private static final long serialVersionUID = 1L;
ReprocessJobAction() {
super(Bundle.AutoIngestAdminActions_reprocessJobAction_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-3739
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.deleteCaseAction.title=Delete Case"})
static final class DeleteCaseAction extends AbstractAction {
private static final long serialVersionUID = 1L;
DeleteCaseAction() {
super(Bundle.AutoIngestAdminActions_deleteCaseAction_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-3740
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.showCaseLogAction.title=Show Case Log"})
static final class ShowCaseLogAction extends AbstractAction {
private static final long serialVersionUID = 1L;
ShowCaseLogAction() {
super(Bundle.AutoIngestAdminActions_showCaseLogAction_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
}

View File

@ -26,37 +26,29 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Component id="pendingScrollPane" max="32767" attributes="0"/>
<Component id="runningScrollPane" alignment="0" max="32767" attributes="0"/>
<Component id="completedScrollPane" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="refreshButton" linkSize="1" min="-2" pref="100" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="prioritizeJobButton" linkSize="1" min="-2" pref="100" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="deprioritizeJobButton" min="-2" pref="127" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="prioritizeCaseButton" linkSize="1" min="-2" pref="100" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="deprioritizeCaseButton" min="-2" pref="127" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="clusterMetricsButton" linkSize="1" min="-2" max="-2" attributes="0"/>
<Component id="lbServicesStatus" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="tbServicesStatusMessage" pref="861" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Component id="lbPending" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lbCompleted" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lbRunning" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="lbServicesStatus" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="tbServicesStatusMessage" min="-2" pref="861" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="refreshButton" linkSize="1" min="-2" pref="100" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="clusterMetricsButton" linkSize="1" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
@ -65,60 +57,48 @@
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lbServicesStatus" alignment="3" min="-2" pref="23" max="-2" attributes="0"/>
<Component id="tbServicesStatusMessage" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="lbPending" min="-2" pref="23" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="1" max="-2" attributes="0"/>
<Component id="pendingScrollPane" min="-2" pref="215" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pendingScrollPane" max="32767" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="lbRunning" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="1" max="-2" attributes="0"/>
<Component id="runningScrollPane" min="-2" pref="133" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="runningScrollPane" pref="133" max="32767" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="lbCompleted" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="completedScrollPane" min="-2" pref="179" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="completedScrollPane" pref="179" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="refreshButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="prioritizeJobButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="prioritizeCaseButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="clusterMetricsButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="deprioritizeJobButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="deprioritizeCaseButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="pendingScrollPane">
<Properties>
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
<Property name="verticalScrollBarPolicy" type="int" value="21"/>
<Property name="opaque" type="boolean" value="false"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[2, 215]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="pendingTable">
<Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="pendingTableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.pendingTable.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="rowHeight" type="int" value="20" postCode="pendingTable.setSelectionModel(new DefaultListSelectionModel() {&#xa; private static final long serialVersionUID = 1L;&#xa; @Override&#xa; public void setSelectionInterval(int index0, int index1) {&#xa; if (index0 == pendingTable.getSelectedRow()) {&#xa; pendingTable.clearSelection();&#xa; } else {&#xa; super.setSelectionInterval(index0, index1);&#xa; }&#xa; }&#xa;});"/>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="0"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="runningScrollPane">
<AuxValues>
@ -126,22 +106,6 @@
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="runningTable">
<Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="runningTableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.runningTable.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="rowHeight" type="int" value="20" postCode="runningTable.setSelectionModel(new DefaultListSelectionModel() {&#xa; private static final long serialVersionUID = 1L;&#xa; @Override&#xa; public void setSelectionInterval(int index0, int index1) {&#xa; if (index0 == runningTable.getSelectedRow()) {&#xa; runningTable.clearSelection();&#xa; } else {&#xa; super.setSelectionInterval(index0, index1);&#xa; }&#xa; }&#xa;});"/>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="0"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="completedScrollPane">
<AuxValues>
@ -149,22 +113,6 @@
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="completedTable">
<Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="completedTableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.completedTable.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="rowHeight" type="int" value="20" postCode="completedTable.setSelectionModel(new DefaultListSelectionModel() {&#xa; private static final long serialVersionUID = 1L;&#xa; @Override&#xa; public void setSelectionInterval(int index0, int index1) {&#xa; if (index0 == completedTable.getSelectedRow()) {&#xa; completedTable.clearSelection();&#xa; } else {&#xa; super.setSelectionInterval(index0, index1);&#xa; }&#xa; }&#xa;});"/>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="0"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JLabel" name="lbPending">
<Properties>
@ -233,34 +181,6 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="prioritizeJobButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.prioritizeJobButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.prioritizeJobButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="prioritizeJobButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="prioritizeCaseButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.prioritizeCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.prioritizeCaseButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="prioritizeCaseButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="clusterMetricsButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -271,33 +191,5 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="clusterMetricsButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="deprioritizeJobButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.deprioritizeJobButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.deprioritizeJobButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deprioritizeJobButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="deprioritizeCaseButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.deprioritizeCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestDashboard.deprioritizeCaseButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deprioritizeCaseButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -19,38 +19,23 @@
package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import javax.swing.DefaultListSelectionModel;
import java.awt.Color;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.core.ServicesMonitor;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot;
import org.sleuthkit.autopsy.guiutils.DurationCellRenderer;
import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer;
import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer;
/**
* A dashboard for monitoring an automated ingest cluster.
@ -58,34 +43,11 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer;
final class AutoIngestDashboard extends JPanel implements Observer {
private static final long serialVersionUID = 1L;
private static final int GENERIC_COL_MIN_WIDTH = 30;
private static final int GENERIC_COL_MAX_WIDTH = 2000;
private static final int PENDING_TABLE_COL_PREFERRED_WIDTH = 280;
private static final int RUNNING_TABLE_COL_PREFERRED_WIDTH = 175;
private static final int PRIORITY_COLUMN_PREFERRED_WIDTH = 60;
private static final int PRIORITY_COLUMN_MAX_WIDTH = 150;
private static final int STAGE_TIME_COL_MIN_WIDTH = 250;
private static final int STAGE_TIME_COL_MAX_WIDTH = 450;
private static final int TIME_COL_MIN_WIDTH = 30;
private static final int TIME_COL_MAX_WIDTH = 250;
private static final int TIME_COL_PREFERRED_WIDTH = 140;
private static final int NAME_COL_MIN_WIDTH = 100;
private static final int NAME_COL_MAX_WIDTH = 250;
private static final int NAME_COL_PREFERRED_WIDTH = 140;
private static final int STAGE_COL_MIN_WIDTH = 70;
private static final int STAGE_COL_MAX_WIDTH = 2000;
private static final int STAGE_COL_PREFERRED_WIDTH = 300;
private static final int STATUS_COL_MIN_WIDTH = 55;
private static final int STATUS_COL_MAX_WIDTH = 250;
private static final int STATUS_COL_PREFERRED_WIDTH = 55;
private static final int COMPLETED_TIME_COL_MIN_WIDTH = 30;
private static final int COMPLETED_TIME_COL_MAX_WIDTH = 2000;
private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280;
private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboard.class.getName());
private final DefaultTableModel pendingTableModel;
private final DefaultTableModel runningTableModel;
private final DefaultTableModel completedTableModel;
private AutoIngestMonitor autoIngestMonitor;
private AutoIngestJobsPanel pendingJobsPanel;
private AutoIngestJobsPanel runningJobsPanel;
private AutoIngestJobsPanel completedJobsPanel;
/**
* Maintain a mapping of each service to it's last status update.
@ -113,30 +75,48 @@ final class AutoIngestDashboard extends JPanel implements Observer {
/**
* Constructs a panel for monitoring an automated ingest cluster.
*/
@Messages({"AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first",
"AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it",
"AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already"})
private AutoIngestDashboard() {
this.statusByService = new ConcurrentHashMap<>();
pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
initComponents();
statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"));
statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"));
statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"));
setServicesStatusMessage();
initPendingJobsTable();
initRunningJobsTable();
initCompletedJobsTable();
pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.PENDING_JOB);
pendingJobsPanel.setSize(pendingScrollPane.getSize());
pendingScrollPane.add(pendingJobsPanel);
pendingScrollPane.setViewportView(pendingJobsPanel);
pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable_toolTipText());
runningJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.RUNNING_JOB);
runningJobsPanel.setSize(runningScrollPane.getSize());
runningScrollPane.add(runningJobsPanel);
runningScrollPane.setViewportView(runningJobsPanel);
runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable_toolTipText());
completedJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.COMPLETED_JOB);
completedJobsPanel.setSize(completedScrollPane.getSize());
completedScrollPane.add(completedJobsPanel);
completedScrollPane.setViewportView(completedJobsPanel);
completedJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_completedTable_toolTipText());
/*
* Must set this flag, otherwise pop up menus don't close properly.
*/
UIManager.put("PopupMenu.consumeEventOnClose", false);
}
AutoIngestMonitor getMonitor() {
return autoIngestMonitor;
}
AutoIngestJobsPanel getPendingJobsPanel() {
return pendingJobsPanel;
}
/**
* Update status of the services on the dashboard
*/
@ -202,233 +182,6 @@ final class AutoIngestDashboard extends JPanel implements Observer {
}.execute();
}
/**
* Sets up the JTable that presents a view of the pending jobs queue for an
* auto ingest cluster.
*/
private void initPendingJobsTable() {
/*
* Remove some of the jobs table model columns from the JTable. This
* does not remove the columns from the model, just from this table.
*/
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader()));
/*
* Set up a column to display the cases associated with the jobs.
*/
TableColumn column;
column = pendingTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader());
column.setMinWidth(GENERIC_COL_MIN_WIDTH);
column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the data sources associated with the jobs.
*/
column = pendingTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader());
column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the create times of the jobs.
*/
column = pendingTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader());
column.setCellRenderer(new LongDateCellRenderer());
column.setMinWidth(TIME_COL_MIN_WIDTH);
column.setMaxWidth(TIME_COL_MAX_WIDTH);
column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH);
column.setWidth(TIME_COL_PREFERRED_WIDTH);
column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader());
column.setCellRenderer(new PrioritizedIconCellRenderer());
column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH);
column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH);
column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH);
/*
* Allow sorting when a column header is clicked.
*/
pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel));
/*
* Create a row selection listener to enable/disable the Prioritize
* button.
*/
pendingTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
if (e.getValueIsAdjusting()) {
return;
}
int row = pendingTable.getSelectedRow();
boolean enablePrioritizeButtons = false;
boolean enableDeprioritizeButtons = false;
if (row >= 0 && row < pendingTable.getRowCount()) {
enablePrioritizeButtons = true;
enableDeprioritizeButtons = (Integer) pendingTableModel.getValueAt(row, JobsTableModelColumns.PRIORITY.ordinal()) > 0;
}
this.prioritizeJobButton.setEnabled(enablePrioritizeButtons);
this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons);
this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons);
this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons);
});
}
/**
* Sets up the JTable that presents a view of the running jobs list for an
* auto ingest cluster.
*/
private void initRunningJobsTable() {
/*
* Remove some of the jobs table model columns from the JTable. This
* does not remove the columns from the model, just from this table.
*/
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()));
/*
* Set up a column to display the cases associated with the jobs.
*/
TableColumn column;
column = runningTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader());
column.setMinWidth(GENERIC_COL_MIN_WIDTH);
column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the image folders associated with the
* jobs.
*/
column = runningTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader());
column.setMinWidth(GENERIC_COL_MIN_WIDTH);
column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the host names of the cluster nodes
* processing the jobs.
*/
column = runningTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader());
column.setMinWidth(NAME_COL_MIN_WIDTH);
column.setMaxWidth(NAME_COL_MAX_WIDTH);
column.setPreferredWidth(NAME_COL_PREFERRED_WIDTH);
column.setWidth(NAME_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the ingest activities associated with the
* jobs.
*/
column = runningTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader());
column.setMinWidth(STAGE_COL_MIN_WIDTH);
column.setMaxWidth(STAGE_COL_MAX_WIDTH);
column.setPreferredWidth(STAGE_COL_PREFERRED_WIDTH);
column.setWidth(STAGE_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the ingest activity times associated with
* the jobs.
*/
column = runningTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader());
column.setCellRenderer(new DurationCellRenderer());
column.setMinWidth(GENERIC_COL_MIN_WIDTH);
column.setMaxWidth(STAGE_TIME_COL_MAX_WIDTH);
column.setPreferredWidth(STAGE_TIME_COL_MIN_WIDTH);
column.setWidth(STAGE_TIME_COL_MIN_WIDTH);
/*
* Prevent sorting when a column header is clicked.
*/
runningTable.setAutoCreateRowSorter(false);
}
/**
* Sets up the JTable that presents a view of the completed jobs list for an
* auto ingest cluster.
*/
private void initCompletedJobsTable() {
/*
* Remove some of the jobs table model columns from the JTable. This
* does not remove the columns from the model, just from this table.
*/
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()));
/*
* Set up a column to display the cases associated with the jobs.
*/
TableColumn column;
column = completedTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader());
column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH);
column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH);
column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the image folders associated with the
* jobs.
*/
column = completedTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader());
column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH);
column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH);
column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the create times of the jobs.
*/
column = completedTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader());
column.setCellRenderer(new LongDateCellRenderer());
column.setMinWidth(TIME_COL_MIN_WIDTH);
column.setMaxWidth(TIME_COL_MAX_WIDTH);
column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH);
column.setWidth(TIME_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the completed times of the jobs.
*/
column = completedTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader());
column.setCellRenderer(new LongDateCellRenderer());
column.setMinWidth(TIME_COL_MIN_WIDTH);
column.setMaxWidth(TIME_COL_MAX_WIDTH);
column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH);
column.setWidth(TIME_COL_PREFERRED_WIDTH);
/*
* Set up a column to display the statuses of the jobs, with a cell
* renderer that will choose an icon to represent the job status.
*/
column = completedTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader());
column.setCellRenderer(new StatusIconCellRenderer());
column.setMinWidth(STATUS_COL_MIN_WIDTH);
column.setMaxWidth(STATUS_COL_MAX_WIDTH);
column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH);
column.setWidth(STATUS_COL_PREFERRED_WIDTH);
/*
* Allow sorting when a column header is clicked.
*/
completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel));
}
/**
* Starts up the auto ingest monitor and adds this panel as an observer,
* subscribes to services monitor events and starts a task to populate the
@ -469,12 +222,29 @@ final class AutoIngestDashboard extends JPanel implements Observer {
autoIngestMonitor = new AutoIngestMonitor();
autoIngestMonitor.addObserver(this);
new Thread(() -> {
try {
autoIngestMonitor.startUp();
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
LOGGER.log(Level.SEVERE, "Unable to start up Auto Ingest Monitor", ex);
}
}).start();
}
/**
* Shut down parts of the AutoIngestDashboard which were initialized
*/
void shutDown() {
if (autoIngestMonitor != null) {
autoIngestMonitor.shutDown();
}
}
@Override
public void update(Observable observable, Object arg) {
EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg));
EventQueue.invokeLater(() -> {
refreshTables();
});
}
/**
@ -483,177 +253,10 @@ final class AutoIngestDashboard extends JPanel implements Observer {
*
* @param jobsSnapshot The jobs snapshot.
*/
private void refreshTables(JobsSnapshot jobsSnapshot) {
List<AutoIngestJob> pendingJobs = jobsSnapshot.getPendingJobs();
List<AutoIngestJob> runningJobs = jobsSnapshot.getRunningJobs();
List<AutoIngestJob> completedJobs = jobsSnapshot.getCompletedJobs();
pendingJobs.sort(new AutoIngestJob.PriorityComparator());
runningJobs.sort(new AutoIngestJob.DataSourceFileNameComparator());
completedJobs.sort(new AutoIngestJob.CompletedDateDescendingComparator());
refreshTable(pendingJobs, pendingTable, pendingTableModel);
refreshTable(runningJobs, runningTable, runningTableModel);
refreshTable(completedJobs, completedTable, completedTableModel);
}
/**
* Reloads the table model for an auto ingest jobs table and refreshes the
* JTable that uses the model.
*
* @param jobs The list of auto ingest jobs.
* @param tableModel The table model.
* @param comparator An optional comparator (may be null) for sorting the
* table model.
*/
private void refreshTable(List<AutoIngestJob> jobs, JTable table, DefaultTableModel tableModel) {
try {
Path currentRow = getSelectedEntry(table, tableModel);
tableModel.setRowCount(0);
for (AutoIngestJob job : jobs) {
AutoIngestJob.StageDetails status = job.getProcessingStageDetails();
tableModel.addRow(new Object[]{
job.getManifest().getCaseName(), // CASE
job.getManifest().getDataSourcePath().getFileName(), job.getProcessingHostName(), // HOST_NAME
job.getManifest().getDateFileCreated(), // CREATED_TIME
job.getProcessingStageStartDate(), // STARTED_TIME
job.getCompletedDate(), // COMPLETED_TIME
status.getDescription(), // STAGE
job.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, // STATUS
((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // STAGE_TIME
job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH
job.getManifest().getFilePath(), // MANIFEST_FILE_PATH
job.getPriority(), // PRIORITY
job
});
}
setSelectedEntry(table, tableModel, currentRow);
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Error refreshing table " + table.toString(), ex);
}
}
/**
* Gets a path representing the current selection in a table.
*
* @param table The table.
* @param tableModel The table model of the table.
*
* @return A path representing the current selection, or null if there is no
* selection.
*/
Path getSelectedEntry(JTable table, DefaultTableModel tableModel) {
try {
int currentlySelectedRow = table.getSelectedRow();
if (currentlySelectedRow >= 0 && currentlySelectedRow < table.getRowCount()) {
return Paths.get(tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.CASE.ordinal()).toString(),
tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString());
}
} catch (Exception ignored) {
return null;
}
return null;
}
/**
* Sets the selection of the table to the passed-in path's item, if that
* item exists in the table. If it does not, clears the table selection.
*
* @param table The table.
* @param tableModel The table model of the table.
* @param path The path of the item to set
*/
void setSelectedEntry(JTable table, DefaultTableModel tableModel, Path path) {
if (path != null) {
try {
for (int row = 0; row < table.getRowCount(); ++row) {
Path temp = Paths.get(tableModel.getValueAt(row, JobsTableModelColumns.CASE.ordinal()).toString(),
tableModel.getValueAt(row, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString());
if (temp.compareTo(path) == 0) { // found it
table.setRowSelectionInterval(row, row);
return;
}
}
} catch (Exception ignored) {
table.clearSelection();
}
}
table.clearSelection();
}
/*
* This enum is used in conjunction with the DefaultTableModel class to
* provide table models for the JTables used to display a view of the
* pending jobs queue, running jobs list, and completed jobs list for an
* auto ingest cluster. The enum allows the columns of the table model to be
* described by either an enum ordinal or a column header string.
*/
private enum JobsTableModelColumns {
@Messages({"AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority=Prioritized"})
CASE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Case")),
DATA_SOURCE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder")),
HOST_NAME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.HostName")),
CREATED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CreatedTime")),
STARTED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StartedTime")),
COMPLETED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime")),
STAGE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage")),
STAGE_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime")),
STATUS(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Status")),
CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder")),
MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath")),
PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority")),
JOB(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Job"));
private final String header;
private JobsTableModelColumns(String header) {
this.header = header;
}
private String getColumnHeader() {
return header;
}
private static final String[] headers = {
CASE.getColumnHeader(),
DATA_SOURCE.getColumnHeader(),
HOST_NAME.getColumnHeader(),
CREATED_TIME.getColumnHeader(),
STARTED_TIME.getColumnHeader(),
COMPLETED_TIME.getColumnHeader(),
STAGE.getColumnHeader(),
STATUS.getColumnHeader(),
STAGE_TIME.getColumnHeader(),
CASE_DIRECTORY_PATH.getColumnHeader(),
MANIFEST_FILE_PATH.getColumnHeader(),
PRIORITY.getColumnHeader(),
JOB.getColumnHeader()
};
};
/**
* A task that refreshes the UI components on this panel to reflect a
* snapshot of the pending, running and completed auto ingest jobs lists of
* an auto ingest cluster.
*/
private class RefreshComponentsTask implements Runnable {
private final JobsSnapshot jobsSnapshot;
/**
* Constructs a task that refreshes the UI components on this panel to
* reflect a snapshot of the pending, running and completed auto ingest
* jobs lists of an auto ingest cluster.
*
* @param jobsSnapshot The jobs snapshot.
*/
RefreshComponentsTask(JobsSnapshot jobsSnapshot) {
this.jobsSnapshot = jobsSnapshot;
}
@Override
public void run() {
refreshTables(jobsSnapshot);
}
void refreshTables() {
pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
}
/**
@ -698,75 +301,22 @@ final class AutoIngestDashboard extends JPanel implements Observer {
jButton1 = new javax.swing.JButton();
pendingScrollPane = new javax.swing.JScrollPane();
pendingTable = new javax.swing.JTable();
runningScrollPane = new javax.swing.JScrollPane();
runningTable = new javax.swing.JTable();
completedScrollPane = new javax.swing.JScrollPane();
completedTable = new javax.swing.JTable();
lbPending = new javax.swing.JLabel();
lbRunning = new javax.swing.JLabel();
lbCompleted = new javax.swing.JLabel();
refreshButton = new javax.swing.JButton();
lbServicesStatus = new javax.swing.JLabel();
tbServicesStatusMessage = new javax.swing.JTextField();
prioritizeJobButton = new javax.swing.JButton();
prioritizeCaseButton = new javax.swing.JButton();
clusterMetricsButton = new javax.swing.JButton();
deprioritizeJobButton = new javax.swing.JButton();
deprioritizeCaseButton = new javax.swing.JButton();
org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N
pendingTable.setModel(pendingTableModel);
pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N
pendingTable.setRowHeight(20);
pendingTable.setSelectionModel(new DefaultListSelectionModel() {
private static final long serialVersionUID = 1L;
@Override
public void setSelectionInterval(int index0, int index1) {
if (index0 == pendingTable.getSelectedRow()) {
pendingTable.clearSelection();
} else {
super.setSelectionInterval(index0, index1);
}
}
});
pendingTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
pendingScrollPane.setViewportView(pendingTable);
runningTable.setModel(runningTableModel);
runningTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.runningTable.toolTipText")); // NOI18N
runningTable.setRowHeight(20);
runningTable.setSelectionModel(new DefaultListSelectionModel() {
private static final long serialVersionUID = 1L;
@Override
public void setSelectionInterval(int index0, int index1) {
if (index0 == runningTable.getSelectedRow()) {
runningTable.clearSelection();
} else {
super.setSelectionInterval(index0, index1);
}
}
});
runningTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
runningScrollPane.setViewportView(runningTable);
completedTable.setModel(completedTableModel);
completedTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.completedTable.toolTipText")); // NOI18N
completedTable.setRowHeight(20);
completedTable.setSelectionModel(new DefaultListSelectionModel() {
private static final long serialVersionUID = 1L;
@Override
public void setSelectionInterval(int index0, int index1) {
if (index0 == completedTable.getSelectedRow()) {
completedTable.clearSelection();
} else {
super.setSelectionInterval(index0, index1);
}
}
});
completedTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
completedScrollPane.setViewportView(completedTable);
pendingScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
pendingScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
pendingScrollPane.setOpaque(false);
pendingScrollPane.setPreferredSize(new java.awt.Dimension(2, 215));
lbPending.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(lbPending, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.lbPending.text")); // NOI18N
@ -793,24 +343,6 @@ final class AutoIngestDashboard extends JPanel implements Observer {
tbServicesStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.text")); // NOI18N
tbServicesStatusMessage.setBorder(null);
org.openide.awt.Mnemonics.setLocalizedText(prioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.text")); // NOI18N
prioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.toolTipText")); // NOI18N
prioritizeJobButton.setEnabled(false);
prioritizeJobButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
prioritizeJobButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(prioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.text")); // NOI18N
prioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.toolTipText")); // NOI18N
prioritizeCaseButton.setEnabled(false);
prioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
prioritizeCaseButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(clusterMetricsButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.clusterMetricsButton.text")); // NOI18N
clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
@ -818,24 +350,6 @@ final class AutoIngestDashboard extends JPanel implements Observer {
}
});
org.openide.awt.Mnemonics.setLocalizedText(deprioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.text")); // NOI18N
deprioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.toolTipText")); // NOI18N
deprioritizeJobButton.setEnabled(false);
deprioritizeJobButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
deprioritizeJobButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(deprioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.text")); // NOI18N
deprioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.toolTipText")); // NOI18N
deprioritizeCaseButton.setEnabled(false);
deprioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
deprioritizeCaseButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@ -843,35 +357,27 @@ final class AutoIngestDashboard extends JPanel implements Observer {
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(pendingScrollPane)
.addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(runningScrollPane, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(completedScrollPane, javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(prioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(deprioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(prioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(deprioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(clusterMetricsButton))
.addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(lbServicesStatus)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addGap(0, 0, Short.MAX_VALUE)))
.addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.DEFAULT_SIZE, 861, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING))
.addGap(0, 0, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(clusterMetricsButton)))
.addContainerGap())
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, prioritizeCaseButton, prioritizeJobButton, refreshButton});
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, refreshButton});
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -883,23 +389,19 @@ final class AutoIngestDashboard extends JPanel implements Observer {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lbPending, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(1, 1, 1)
.addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(lbRunning)
.addGap(1, 1, 1)
.addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(runningScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 133, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(lbCompleted)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 179, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(completedScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(refreshButton)
.addComponent(prioritizeJobButton)
.addComponent(prioritizeCaseButton)
.addComponent(clusterMetricsButton)
.addComponent(deprioritizeJobButton)
.addComponent(deprioritizeCaseButton))
.addComponent(clusterMetricsButton))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
@ -913,135 +415,25 @@ final class AutoIngestDashboard extends JPanel implements Observer {
*/
private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
JobsSnapshot jobsSnapshot = autoIngestMonitor.refreshJobsSnapshot();
refreshTables(jobsSnapshot);
refreshTables();
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}//GEN-LAST:event_refreshButtonActionPerformed
@Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."})
private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed
if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal()));
JobsSnapshot jobsSnapshot;
try {
jobsSnapshot = autoIngestMonitor.prioritizeJob(job);
refreshTables(jobsSnapshot);
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath());
LOGGER.log(Level.SEVERE, errorMessage, ex);
MessageNotifyUtil.Message.error(errorMessage);
}
setCursor(Cursor.getDefaultCursor());
}
}//GEN-LAST:event_prioritizeJobButtonActionPerformed
@Messages({"AutoIngestDashboard.errorMessage.casePrioritization=Failed to prioritize case \"%s\"."})
private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed
if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString();
JobsSnapshot jobsSnapshot;
try {
jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName);
refreshTables(jobsSnapshot);
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName);
LOGGER.log(Level.SEVERE, errorMessage, ex);
MessageNotifyUtil.Message.error(errorMessage);
}
setCursor(Cursor.getDefaultCursor());
}
}//GEN-LAST:event_prioritizeCaseButtonActionPerformed
private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed
new AutoIngestMetricsDialog(this.getTopLevelAncestor());
}//GEN-LAST:event_clusterMetricsButtonActionPerformed
@Messages({"AutoIngestDashboard.errorMessage.jobDeprioritization=Failed to deprioritize job \"%s\"."})
private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed
if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal()));
JobsSnapshot jobsSnapshot;
try {
jobsSnapshot = autoIngestMonitor.deprioritizeJob(job);
refreshTables(jobsSnapshot);
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath());
LOGGER.log(Level.SEVERE, errorMessage, ex);
MessageNotifyUtil.Message.error(errorMessage);
}
setCursor(Cursor.getDefaultCursor());
}
}//GEN-LAST:event_deprioritizeJobButtonActionPerformed
@Messages({"AutoIngestDashboard.errorMessage.caseDeprioritization=Failed to deprioritize case \"%s\"."})
private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed
if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString();
JobsSnapshot jobsSnapshot;
try {
jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName);
refreshTables(jobsSnapshot);
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName);
LOGGER.log(Level.SEVERE, errorMessage, ex);
MessageNotifyUtil.Message.error(errorMessage);
}
setCursor(Cursor.getDefaultCursor());
}
}//GEN-LAST:event_deprioritizeCaseButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton clusterMetricsButton;
private javax.swing.JScrollPane completedScrollPane;
private javax.swing.JTable completedTable;
private javax.swing.JButton deprioritizeCaseButton;
private javax.swing.JButton deprioritizeJobButton;
private javax.swing.JButton jButton1;
private javax.swing.JLabel lbCompleted;
private javax.swing.JLabel lbPending;
private javax.swing.JLabel lbRunning;
private javax.swing.JLabel lbServicesStatus;
private javax.swing.JScrollPane pendingScrollPane;
private javax.swing.JTable pendingTable;
private javax.swing.JButton prioritizeCaseButton;
private javax.swing.JButton prioritizeJobButton;
private javax.swing.JButton refreshButton;
private javax.swing.JScrollPane runningScrollPane;
private javax.swing.JTable runningTable;
private javax.swing.JTextField tbServicesStatusMessage;
// End of variables declaration//GEN-END:variables
private class AutoIngestTableModel extends DefaultTableModel {
private static final long serialVersionUID = 1L;
private AutoIngestTableModel(String[] headers, int i) {
super(headers, i);
}
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) {
return Integer.class;
} else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal()
|| columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal()
|| columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal()
|| columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) {
return Date.class;
} else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) {
return Boolean.class;
} else {
return super.getColumnClass(columnIndex);
}
}
}
}

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Component;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -86,17 +87,24 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
}
}
public static void closeTopComponent() {
@Override
protected void componentClosed() {
if (topComponentInitialized) {
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) {
try {
for (Component comp : getComponents()) {
if (comp instanceof AutoIngestDashboard) {
((AutoIngestDashboard) comp).shutDown();
}
}
tc.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to close " + PREFERRED_ID, e); // NON-NLS
}
}
}
super.componentClosed();
}
public AutoIngestDashboardTopComponent() {
@ -104,6 +112,20 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
setName(Bundle.CTL_AutoIngestDashboardTopComponent());
}
/**
* Get the current AutoIngestDashboard if there is one.
*
* @return the current AutoIngestDashboard or null if there is not one
*/
AutoIngestDashboard getAutoIngestDashboard() {
for (Component comp : getComponents()) {
if (comp instanceof AutoIngestDashboard) {
return (AutoIngestDashboard) comp;
}
}
return null;
}
@Override
public List<Mode> availableModes(List<Mode> modes) {
/*

View File

@ -0,0 +1,235 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.experimental.autoingest;
import java.io.File;
import javax.swing.Action;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.openide.modules.Places;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot;
import org.sleuthkit.autopsy.guiutils.DurationCellRenderer;
import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer;
/**
* A node which represents all AutoIngestJobs of a given AutoIngestJobStatus.
* Each job with the specified status will have a child node representing it.
*/
final class AutoIngestJobsNode extends AbstractNode {
private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess";
private final static String ADMIN_ACCESS_FILE_PATH = Places.getUserDirectory().getAbsolutePath() + File.separator + ADMIN_ACCESS_FILE_NAME;
@Messages({
"AutoIngestJobsNode.caseName.text=Case Name",
"AutoIngestJobsNode.dataSource.text=Data Source",
"AutoIngestJobsNode.hostName.text=Host Name",
"AutoIngestJobsNode.stage.text=Stage",
"AutoIngestJobsNode.stageTime.text=Time in Stage",
"AutoIngestJobsNode.jobCreated.text=Job Created",
"AutoIngestJobsNode.jobCompleted.text=Job Completed",
"AutoIngestJobsNode.priority.text=Prioritized",
"AutoIngestJobsNode.status.text=Status"
})
/**
* Construct a new AutoIngestJobsNode.
*/
AutoIngestJobsNode(JobsSnapshot jobsSnapshot, AutoIngestJobStatus status) {
super(Children.create(new AutoIngestNodeChildren(jobsSnapshot, status), false));
}
/**
* A ChildFactory for generating JobNodes.
*/
static class AutoIngestNodeChildren extends ChildFactory<AutoIngestJob> {
private final AutoIngestJobStatus autoIngestJobStatus;
private final JobsSnapshot jobsSnapshot;
/**
* Create children nodes for the AutoIngestJobsNode which will each
* represent a single AutoIngestJob
*
* @param snapshot the snapshot which contains the AutoIngestJobs
* @param status the status of the jobs being displayed
*/
AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status) {
jobsSnapshot = snapshot;
autoIngestJobStatus = status;
}
@Override
protected boolean createKeys(List<AutoIngestJob> list) {
List<AutoIngestJob> jobs;
switch (autoIngestJobStatus) {
case PENDING_JOB:
jobs = jobsSnapshot.getPendingJobs();
jobs.sort(new AutoIngestJob.PriorityComparator());
break;
case RUNNING_JOB:
jobs = jobsSnapshot.getRunningJobs();
break;
case COMPLETED_JOB:
jobs = jobsSnapshot.getCompletedJobs();
break;
default:
jobs = new ArrayList<>();
}
if (jobs != null && jobs.size() > 0) {
list.addAll(jobs);
}
return true;
}
@Override
protected Node createNodeForKey(AutoIngestJob key) {
return new JobNode(key, autoIngestJobStatus);
}
}
/**
* A node which represents a single auto ingest job.
*/
static final class JobNode extends AbstractNode {
private final AutoIngestJob autoIngestJob;
private final AutoIngestJobStatus jobStatus;
/**
* Construct a new JobNode to represent an AutoIngestJob and its status.
*
* @param job - the AutoIngestJob being represented by this node
* @param status - the current status of the AutoIngestJob being
* represented
*/
JobNode(AutoIngestJob job, AutoIngestJobStatus status) {
super(Children.LEAF);
jobStatus = status;
autoIngestJob = job;
super.setName(autoIngestJob.getManifest().getCaseName());
setName(autoIngestJob.getManifest().getCaseName());
setDisplayName(autoIngestJob.getManifest().getCaseName());
}
/**
* Get the AutoIngestJob which this node represents.
*
* @return autoIngestJob
*/
AutoIngestJob getAutoIngestJob() {
return autoIngestJob;
}
@Override
@Messages({"AutoIngestJobsNode.prioritized.true=Yes",
"AutoIngestJobsNode.prioritized.false=No"
})
protected Sheet createSheet() {
Sheet s = super.createSheet();
Sheet.Set ss = s.get(Sheet.PROPERTIES);
if (ss == null) {
ss = Sheet.createPropertiesSet();
s.put(ss);
}
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(),
autoIngestJob.getManifest().getCaseName()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(),
autoIngestJob.getManifest().getDataSourcePath().getFileName().toString()));
switch (jobStatus) {
case PENDING_JOB:
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(),
autoIngestJob.getManifest().getDateFileCreated()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(),
autoIngestJob.getPriority() > 0 ? Bundle.AutoIngestJobsNode_prioritized_true() : Bundle.AutoIngestJobsNode_prioritized_false()));
break;
case RUNNING_JOB:
AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails();
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(),
autoIngestJob.getProcessingHostName()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(),
status.getDescription()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(),
DurationCellRenderer.longToDurationString((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime()))));
break;
case COMPLETED_JOB:
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(),
autoIngestJob.getManifest().getDateFileCreated()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(),
autoIngestJob.getCompletedDate()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(),
autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK));
break;
default:
}
return s;
}
@Override
public Action[] getActions(boolean context) {
List<Action> actions = new ArrayList<>();
File f = new File(ADMIN_ACCESS_FILE_PATH);
if (f.exists()) {
switch (jobStatus) {
case PENDING_JOB:
actions.add(new PrioritizationAction.PrioritizeJobAction(autoIngestJob));
actions.add(new PrioritizationAction.PrioritizeCaseAction(autoIngestJob));
PrioritizationAction.DeprioritizeJobAction deprioritizeJobAction = new PrioritizationAction.DeprioritizeJobAction(autoIngestJob);
deprioritizeJobAction.setEnabled(autoIngestJob.getPriority() > 0);
actions.add(deprioritizeJobAction);
PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(autoIngestJob);
deprioritizeCaseAction.setEnabled(autoIngestJob.getPriority() > 0);
actions.add(deprioritizeCaseAction);
break;
case RUNNING_JOB:
actions.add(new AutoIngestAdminActions.ProgressDialogAction());
actions.add(new AutoIngestAdminActions.CancelJobAction());
actions.add(new AutoIngestAdminActions.CancelModuleAction());
break;
case COMPLETED_JOB:
actions.add(new AutoIngestAdminActions.ReprocessJobAction());
actions.add(new AutoIngestAdminActions.DeleteCaseAction());
actions.add(new AutoIngestAdminActions.ShowCaseLogAction());
break;
default:
}
}
return actions.toArray(new Action[actions.size()]);
}
}
/**
* An enumeration used to indicate the current status of an auto ingest job
* node.
*/
enum AutoIngestJobStatus {
PENDING_JOB, //NON-NLS
RUNNING_JOB, //NON-NLS
COMPLETED_JOB //NON-NLS
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,7,0,0,0,29"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Form>

View File

@ -0,0 +1,211 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.experimental.autoingest;
import java.awt.Dimension;
import java.beans.PropertyVetoException;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionListener;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datamodel.EmptyNode;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot;
/**
* A panel which displays an outline view with all jobs for a specified status.
*/
final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerManager.Provider {
private static final long serialVersionUID = 1L;
private static final int INITIAL_CASENAME_WIDTH = 170;
private static final int INITIAL_DATASOURCE_WIDTH = 270;
private static final int INITIAL_PRIORITIZED_WIDTH = 20;
private static final int INITIAL_STATUS_WIDTH = 20;
private static final int INVALID_INDEX = -1;
private final org.openide.explorer.view.OutlineView outlineView;
private final Outline outline;
private ExplorerManager explorerManager;
private final AutoIngestJobStatus status;
/**
* Creates a new AutoIngestJobsPanel of the specified jobStatus
*
* @param jobStatus the status of the jbos to be displayed on this panel
*/
AutoIngestJobsPanel(AutoIngestJobStatus jobStatus) {
initComponents();
status = jobStatus;
outlineView = new org.openide.explorer.view.OutlineView();
outline = outlineView.getOutline();
customize();
}
/**
* Set up the AutoIngestJobsPanel's so that its outlineView is displaying
* the correct columns for the specified AutoIngestJobStatus
*/
@Messages({"AutoIngestJobsPanel.waitNode.text=Please Wait..."})
void customize() {
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestJobsNode_caseName_text());
outline.setRowSelectionAllowed(false); //rows will be made selectable after table has been populated
outline.setFocusable(false); //table will be made focusable after table has been populated
if (null == explorerManager) {
explorerManager = new ExplorerManager();
}
explorerManager.setRootContext(new EmptyNode(Bundle.AutoIngestJobsPanel_waitNode_text()));
int indexOfColumn;
switch (status) {
case PENDING_JOB:
outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(),
Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(),
Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text());
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_priority_text());
if (indexOfColumn != INVALID_INDEX) {
outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_PRIORITIZED_WIDTH);
}
break;
case RUNNING_JOB:
outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(),
Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(),
Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(),
Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text());
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text());
if (indexOfColumn != INVALID_INDEX) {
outline.setColumnSorted(indexOfColumn, true, 1);
}
break;
case COMPLETED_JOB:
outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(),
Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(),
Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(),
Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text());
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_jobCompleted_text());
if (indexOfColumn != INVALID_INDEX) {
outline.setColumnSorted(indexOfColumn, false, 1);
}
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_status_text());
if (indexOfColumn != INVALID_INDEX) {
outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_STATUS_WIDTH);
}
break;
default:
}
outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
outline.setRootVisible(false);
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text());
if (indexOfColumn != INVALID_INDEX) {
outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_CASENAME_WIDTH);
}
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_dataSource_text());
if (indexOfColumn != INVALID_INDEX) {
outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_DATASOURCE_WIDTH);
}
add(outlineView, java.awt.BorderLayout.CENTER);
}
private int getColumnIndexByName(String columnName) {
for (int index = 0; index < outline.getColumnModel().getColumnCount(); index++) {
if (outline.getColumnModel().getColumn(index).getHeaderValue().toString().equals(columnName)) {
return index;
}
}
return INVALID_INDEX;
}
@Override
public void setSize(Dimension d) {
super.setSize(d);
outlineView.setMaximumSize(new Dimension(400, 100));
outline.setPreferredScrollableViewportSize(new Dimension(400, 100));
}
/**
* Add a list selection listener to the selection model of the outline being
* used in this panel.
*
* @param listener the ListSelectionListener to add
*/
void addListSelectionListener(ListSelectionListener listener) {
outline.getSelectionModel().addListSelectionListener(listener);
}
@Override
public ExplorerManager getExplorerManager() {
return explorerManager;
}
/**
* Update the contents of this AutoIngestJobsPanel while retaining currently
* selected node.
*
* @param jobsSnapshot - the JobsSnapshot which will provide the new
* contents
*/
void refresh(JobsSnapshot jobsSnapshot) {
synchronized (this) {
outline.setRowSelectionAllowed(false);
Node[] selectedNodes = explorerManager.getSelectedNodes();
AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status);
explorerManager.setRootContext(autoIngestNode);
outline.setRowSelectionAllowed(true);
if (selectedNodes.length > 0 && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored
try {
explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())});
} catch (PropertyVetoException ignore) {
//Unable to select previously selected node
}
}
outline.setFocusable(true);
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setLayout(new java.awt.BorderLayout());
}// </editor-fold>//GEN-END:initComponents
/**
* Get the AutoIngestJob for the currently selected node of this panel.
*
* @return AutoIngestJob which is currently selected in this panel
*/
AutoIngestJob getSelectedAutoIngestJob() {
Node[] selectedRows = explorerManager.getSelectedNodes();
if (selectedRows.length == 1) {
return ((JobNode) selectedRows[0]).getAutoIngestJob();
}
return null;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}

View File

@ -10,9 +10,6 @@ AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Job Completed
AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage=Stage
AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status
AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path
AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first
AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it
AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already
AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime=Time in Stage
AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case
AutoIngestDashboard.JobsTableModel.ColumnHeader.Job=Job
@ -222,10 +219,6 @@ FileExporterSettingsPanel.SaveTooltip_1=Save the current rule
AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables
AutoIngestDashboard.refreshButton.text=&Refresh
AutoIngestDashboard.jButton1.text=jButton1
AutoIngestDashboard.prioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue.
AutoIngestDashboard.prioritizeJobButton.text=Prioritize &Job
AutoIngestDashboard.prioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue.
AutoIngestDashboard.prioritizeCaseButton.text=Prioritize &Case
AutoIngestMetricsDialog.reportTextArea.text=
AutoIngestDashboard.clusterMetricsButton.text=Auto Ingest &Metrics
AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report
@ -236,10 +229,6 @@ ArchiveFilePanel.browseButton.text=Browse
ArchiveFilePanel.pathTextField.text=
ArchiveFilePanel.errorLabel.text=Error Label
AutoIngestMetricsDialog.startingDataLabel.text=Starting Date:
AutoIngestDashboard.deprioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue.
AutoIngestDashboard.deprioritizeJobButton.text=Deprioritize J&ob
AutoIngestDashboard.deprioritizeCaseButton.text=Deprioritize C&ase
AutoIngestDashboard.deprioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue.
AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job
AutoIngestControlPanel.bnPrioritizeCase.text=Prioritize Case

View File

@ -0,0 +1,249 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.experimental.autoingest;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
/**
* Abstract actions which are for the modification of AutoIngestJob or Case
* priority.
*/
abstract class PrioritizationAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(PrioritizationAction.class.getName());
private final AutoIngestJob job;
/**
* Construct a new Prioritization action for the selected job
*
* @param selectedJob The job which will be used to determine what has it's
* priority modified
* @param title - the string to represent the action in menus
*/
PrioritizationAction(AutoIngestJob selectedJob, String title) {
super(title);
job = selectedJob;
}
/**
* The implementation specific method which modifies job or case priority
*
* @param monitor - the AutoIngestMonitor which can be accessed to change
* the job or case priority
*
* @throws
* org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestMonitorException
*/
protected abstract void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException;
/**
* Get the implementation specific error message for if modifyPriority fails
*
* @return the error message for the current implementation
*/
protected abstract String getErrorMessage();
/**
* Gets the job this action is constructed for
*
* @return job - the AutoIngestJob
*/
protected AutoIngestJob getJob() {
return job;
}
@Override
public void actionPerformed(ActionEvent e) {
if (job != null) {
final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID);
if (tc != null) {
AutoIngestDashboard dashboard = tc.getAutoIngestDashboard();
if (dashboard != null) {
dashboard.getPendingJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
EventQueue.invokeLater(() -> {
try {
modifyPriority(dashboard.getMonitor());
dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot());
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
String errorMessage = getErrorMessage();
logger.log(Level.SEVERE, errorMessage, ex);
MessageNotifyUtil.Message.error(errorMessage);
} finally {
dashboard.getPendingJobsPanel().setCursor(Cursor.getDefaultCursor());
}
});
}
}
}
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
/**
* Action to prioritize the specified AutoIngestJob
*/
@Messages({"PrioritizationAction.prioritizeJobAction.title=Prioritize Job",
"PrioritizationAction.prioritizeJobAction.error=Failed to prioritize job \"%s\"."})
static final class PrioritizeJobAction extends PrioritizationAction {
private static final long serialVersionUID = 1L;
/**
* Construct a new PrioritizeJobAction
*
* @param selectedJob - the AutoIngestJob to be prioritized
*/
PrioritizeJobAction(AutoIngestJob selectedJob) {
super(selectedJob, Bundle.PrioritizationAction_prioritizeJobAction_title());
}
@Override
protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException {
monitor.prioritizeJob(getJob());
}
@Override
protected String getErrorMessage() {
return String.format(Bundle.PrioritizationAction_prioritizeJobAction_error(), getJob().getManifest().getFilePath());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
/**
* Action to deprioritize the specified AutoIngestJob
*/
@Messages({"PrioritizationAction.deprioritizeJobAction.title=Deprioritize Job",
"PrioritizationAction.deprioritizeJobAction.error=Failed to deprioritize job \"%s\"."})
static final class DeprioritizeJobAction extends PrioritizationAction {
private static final long serialVersionUID = 1L;
/**
* Construct a new DeprioritizeJobAction
*
* @param selectedJob - the AutoIngestJob to be deprioritized
*/
DeprioritizeJobAction(AutoIngestJob selectedJob) {
super(selectedJob, Bundle.PrioritizationAction_deprioritizeJobAction_title());
}
@Override
protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException {
monitor.deprioritizeJob(getJob());
}
@Override
protected String getErrorMessage() {
return String.format(Bundle.PrioritizationAction_deprioritizeJobAction_error(), getJob().getManifest().getFilePath());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
/**
* Action to prioritize all jobs for the case which the specified
* AutoIngestJob is a part of.
*/
@Messages({"PrioritizationAction.prioritizeCaseAction.title=Prioritize Case",
"PrioritizationAction.prioritizeCaseAction.error==Failed to prioritize case \"%s\"."})
static final class PrioritizeCaseAction extends PrioritizationAction {
private static final long serialVersionUID = 1L;
/**
* Construct a new PrioritizeCaseAction
*
* @param selectedJob - the AutoIngestJob which should have it's case
* prioritized
*/
PrioritizeCaseAction(AutoIngestJob selectedJob) {
super(selectedJob, Bundle.PrioritizationAction_prioritizeCaseAction_title());
}
@Override
protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException {
monitor.prioritizeCase(getJob().getManifest().getCaseName());
}
@Override
protected String getErrorMessage() {
return String.format(Bundle.PrioritizationAction_prioritizeCaseAction_error(), getJob().getManifest().getCaseName());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
/**
* Action to deprioritize all jobs for the case which the specified
* AutoIngestJob is a part of.
*/
@Messages({"PrioritizationAction.deprioritizeCaseAction.title=Deprioritize Case",
"PrioritizationAction.deprioritizeCaseAction.error=Failed to deprioritize case \"%s\"."})
static final class DeprioritizeCaseAction extends PrioritizationAction {
private static final long serialVersionUID = 1L;
/**
* Construct a new DeprioritizeCaseAction
*
* @param selectedJob - the AutoIngestJob which should have it's case
* deprioritized
*/
DeprioritizeCaseAction(AutoIngestJob selectedJob) {
super(selectedJob, Bundle.PrioritizationAction_deprioritizeCaseAction_title());
}
@Override
protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException {
monitor.deprioritizeCase(getJob().getManifest().getCaseName());
}
@Override
protected String getErrorMessage() {
return String.format(Bundle.PrioritizationAction_deprioritizeCaseAction_error(), getJob().getManifest().getCaseName());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
}

View File

@ -49,6 +49,7 @@ final class AddMemoryImageTask implements Runnable {
private final DataSourceProcessorCallback callback;
private volatile VolatilityProcessor volatilityProcessor;
private volatile boolean isCancelled;
private final String profile; // empty for autodetect
/**
* Constructs a runnable that adds a memory image to a case database.
@ -57,6 +58,7 @@ final class AddMemoryImageTask implements Runnable {
* associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID).
* @param memoryImagePath Path to the memory image file.
* @param profile Volatility profile to run or empty string to autodetect
* @param pluginsToRun The Volatility plugins to run.
* @param timeZone The time zone to use when processing dates and
* times for the image, obtained from
@ -65,9 +67,10 @@ final class AddMemoryImageTask implements Runnable {
* during processing.
* @param callback Callback to call when processing is done.
*/
AddMemoryImageTask(String deviceId, String memoryImagePath, List<String> pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
AddMemoryImageTask(String deviceId, String memoryImagePath, String profile, List<String> pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
this.deviceId = deviceId;
this.memoryImagePath = memoryImagePath;
this.profile = profile;
this.pluginsToRun = pluginsToRun;
this.timeZone = timeZone;
this.callback = callback;
@ -94,7 +97,7 @@ final class AddMemoryImageTask implements Runnable {
try {
Image dataSource = addImageToCase();
dataSources.add(dataSource);
volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, pluginsToRun, progressMonitor);
volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, profile, pluginsToRun, progressMonitor);
volatilityProcessor.run();
} catch (NoCurrentCaseException | TskCoreException | VolatilityProcessor.VolatilityProcessorException ex) {
criticalErrorOccurred = true;

View File

@ -3,11 +3,10 @@
# and open the template in the editor.
MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName=Browse for a memory image file:
MemoryDSInputPanel.PluginsToRunLabel.text=Available plugins to run:
MemoryDSInputPanel.volExecutableLabel.text=Version of Volatility to Run:
MemoryDSInputPanel.PluginsToRunLabel.text=Plugins to run:
MemoryDSInputPanel.pathLabel.text=Browse for a memory image file:
MemoryDSInputPanel.pathTextField.text=
MemoryDSInputPanel.errorLabel.text=Error Label
MemoryDSInputPanel.browseButton.text=Browse
MemoryDSImputPanel.pathTextField.text=
MemoryDSInputPanel.timeZoneLabel.text=Please select the input timezone:
MemoryDSInputPanel.timeZoneLabel.text=Timezone:
MemoryDSInputPanel.profileLabel.text=Profile:

View File

@ -28,13 +28,15 @@
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="pathLabel" min="-2" pref="218" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="timeZoneLabel" min="-2" pref="168" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<Component id="timeZoneLabel" min="-2" pref="134" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="volExecutableComboBox" min="-2" max="-2" attributes="0"/>
<Component id="timeZoneComboBox" min="-2" pref="199" max="-2" attributes="0"/>
<Component id="listsScrollPane" alignment="0" min="-2" pref="248" max="-2" attributes="0"/>
<Group type="103" alignment="0" groupAlignment="1" max="-2" attributes="0">
<Component id="profileComboBox" alignment="0" pref="243" max="32767" attributes="0"/>
<Component id="timeZoneComboBox" alignment="0" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
</Group>
@ -43,8 +45,8 @@
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="errorLabel" min="-2" max="-2" attributes="0"/>
<Component id="volExecutableLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="PluginsToRunLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="profileLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
@ -66,17 +68,17 @@
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="errorLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="volExecutableLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="volExecutableComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="profileLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="profileComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="PluginsToRunLabel" min="-2" max="-2" attributes="0"/>
<Component id="listsScrollPane" min="-2" pref="132" max="-2" attributes="0"/>
<Component id="listsScrollPane" min="-2" pref="122" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="30" max="32767" attributes="0"/>
<EmptySpace pref="73" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -85,7 +87,7 @@
<Component class="javax.swing.JLabel" name="pathLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties" key="RawDSInputPanel.pathLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties" key="MemoryDSInputPanel.pathLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AccessibilityProperties>
@ -97,14 +99,17 @@
<Component class="javax.swing.JTextField" name="pathTextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties" key="RawDSInputPanel.pathTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties" key="MemoryDSInputPanel.pathTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pathTextFieldActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="browseButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties" key="RawDSInputPanel.browseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties" key="MemoryDSInputPanel.browseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
@ -117,14 +122,14 @@
<Color blue="0" green="0" red="ff" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties" key="RawDSInputPanel.errorLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties" key="MemoryDSInputPanel.errorLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="timeZoneLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties" key="RawDSInputPanel.timeZoneLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties" key="MemoryDSInputPanel.timeZoneLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
@ -139,27 +144,6 @@
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="volExecutableLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties" key="MemoryDSInputPanel.volExecutableLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="volExecutableComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="0"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="volExecutableComboBoxActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="PluginsToRunLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -174,7 +158,7 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="PluginList">
<Component class="javax.swing.JTable" name="pluginTable">
<Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
<Table columnCount="0" rowCount="4"/>
@ -189,5 +173,20 @@
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JLabel" name="profileLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties" key="MemoryDSInputPanel.profileLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="profileComboBox">
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="profileComboBoxActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Form>

View File

@ -26,7 +26,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JTable;
@ -42,6 +44,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PathValidator;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private static final long serialVersionUID = 1L; //default
@ -53,13 +56,26 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private final PluginListTableModel tableModel = new PluginListTableModel();
private final List<String> PluginListNames = new ArrayList<>();
private final Map<String, Boolean> pluginListStates = new HashMap<>(); // is set by listeners when users select and deselect items
private final Boolean isEnabled = true;
private final SortedSet<String> profileList = new TreeSet<>(Arrays.asList(
"VistaSP0x64", "VistaSP0x86", "VistaSP1x64", "VistaSP1x86",
"VistaSP2x64", "VistaSP2x86", "Win10x64", "Win10x64_10586",
"Win10x64_14393", "Win10x86", "Win10x86_10586", "Win10x86_14393",
"Win2003SP0x86", "Win2003SP1x64", "Win2003SP1x86", "Win2003SP2x64",
"Win2003SP2x86", "Win2008R2SP0x64", "Win2008R2SP1x64", "Win2008R2SP1x64_23418",
"Win2008SP1x64", "Win2008SP1x86", "Win2008SP2x64", "Win2008SP2x86",
"Win2012R2x64", "Win2012R2x64_18340", "Win2012x64", "Win2016x64_14393",
"Win7SP0x64", "Win7SP0x86", "Win7SP1x64", "Win7SP1x64_23418", "Win7SP1x86_23418",
"Win81U1x64", "Win81U1x86", "Win8SP0x64", "Win8SP0x86", "Win8SP1x64",
"Win8SP1x64_18340", "Win8SP1x86", "WinXPSP1x64", "WinXPSP2x64", "WinXPSP2x86",
"WinXPSP3x86"));
private final static String AUTODETECT_PROFILE = "Auto Detect";
/**
* Creates new MemoryDSInputPanel panel for user input
*/
private MemoryDSInputPanel(String context) {
this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo"};
this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo", "dlldump", "moddump", "procdump", "dumpfiles", "dumpregistry"};
Arrays.sort(this.pluginList);
initComponents();
@ -82,7 +98,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
instance.postInit();
instance.customizePluginListTable();
instance.createTimeZoneList();
instance.createVolatilityVersionList();
instance.populateProfileCombobox();
instance.createPluginList();
return instance;
@ -95,14 +111,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
}
private void customizePluginListTable() {
PluginList.setModel(tableModel);
PluginList.setTableHeader(null);
PluginList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
pluginTable.setModel(tableModel);
pluginTable.setTableHeader(null);
pluginTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
final int width = listsScrollPane.getPreferredSize().width;
PluginList.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
pluginTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
TableColumn column;
for (int i = 0; i < PluginList.getColumnCount(); i++) {
column = PluginList.getColumnModel().getColumn(i);
for (int i = 0; i < pluginTable.getColumnCount(); i++) {
column = pluginTable.getColumnModel().getColumn(i);
if (i == 0) {
column.setPreferredWidth(((int) (width * 0.07)));
} else {
@ -138,11 +154,12 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
timeZoneComboBox.setSelectedItem(formatted);
}
private void createVolatilityVersionList() {
volExecutableComboBox.addItem("2.6");
volExecutableComboBox.addItem("2.5");
private void populateProfileCombobox() {
profileComboBox.addItem(AUTODETECT_PROFILE);
profileList.forEach((profile) -> {
profileComboBox.addItem(profile);
});
}
private void createPluginList() {
@ -157,8 +174,10 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
PluginListNames.add(plugin);
if (allEnabled) {
pluginListStates.put(plugin, true);
} else if ((pluginMap.containsKey(plugin) && pluginMap.get(plugin).equals("false"))) {
pluginListStates.put(plugin, false);
} else {
pluginListStates.put(plugin, pluginMap.containsKey(plugin));
pluginListStates.put(plugin, true);
}
}
tableModel.fireTableDataChanged();
@ -181,15 +200,20 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
errorLabel = new javax.swing.JLabel();
timeZoneLabel = new javax.swing.JLabel();
timeZoneComboBox = new javax.swing.JComboBox<>();
volExecutableLabel = new javax.swing.JLabel();
volExecutableComboBox = new javax.swing.JComboBox<>();
PluginsToRunLabel = new javax.swing.JLabel();
listsScrollPane = new javax.swing.JScrollPane();
PluginList = new javax.swing.JTable();
pluginTable = new javax.swing.JTable();
profileLabel = new javax.swing.JLabel();
profileComboBox = new javax.swing.JComboBox<>();
org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.text")); // NOI18N
pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathTextField.text")); // NOI18N
pathTextField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pathTextFieldActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.browseButton.text")); // NOI18N
browseButton.addActionListener(new java.awt.event.ActionListener() {
@ -205,18 +229,9 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
timeZoneComboBox.setMaximumRowCount(30);
org.openide.awt.Mnemonics.setLocalizedText(volExecutableLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.volExecutableLabel.text")); // NOI18N
volExecutableComboBox.setEnabled(false);
volExecutableComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
volExecutableComboBoxActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(PluginsToRunLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.PluginsToRunLabel.text")); // NOI18N
PluginList.setModel(new javax.swing.table.DefaultTableModel(
pluginTable.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
{},
{},
@ -227,7 +242,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
}
));
listsScrollPane.setViewportView(PluginList);
listsScrollPane.setViewportView(pluginTable);
org.openide.awt.Mnemonics.setLocalizedText(profileLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.profileLabel.text")); // NOI18N
profileComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
profileComboBoxActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@ -241,18 +264,19 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 218, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
.addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 168, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 134, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 199, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE))))
.addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(profileComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, 243, Short.MAX_VALUE)
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))))
.addGap(0, 163, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(errorLabel)
.addComponent(volExecutableLabel)
.addComponent(PluginsToRunLabel))
.addComponent(PluginsToRunLabel)
.addComponent(profileLabel))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
layout.setVerticalGroup(
@ -269,15 +293,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(errorLabel)
.addGap(18, 18, 18)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(volExecutableLabel)
.addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(profileLabel)
.addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(PluginsToRunLabel)
.addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(30, Short.MAX_VALUE))
.addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(73, Short.MAX_VALUE))
);
pathLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName")); // NOI18N
@ -298,12 +322,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
}
}//GEN-LAST:event_browseButtonActionPerformed
private void volExecutableComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_volExecutableComboBoxActionPerformed
private void profileComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_profileComboBoxActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_volExecutableComboBoxActionPerformed
}//GEN-LAST:event_profileComboBoxActionPerformed
private void pathTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pathTextFieldActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_pathTextFieldActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTable PluginList;
private javax.swing.JLabel PluginsToRunLabel;
private javax.swing.JButton browseButton;
private javax.swing.JLabel errorLabel;
@ -311,10 +338,11 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private javax.swing.JScrollPane listsScrollPane;
private javax.swing.JLabel pathLabel;
private javax.swing.JTextField pathTextField;
private javax.swing.JTable pluginTable;
private javax.swing.JComboBox<String> profileComboBox;
private javax.swing.JLabel profileLabel;
private javax.swing.JComboBox<String> timeZoneComboBox;
private javax.swing.JLabel timeZoneLabel;
private javax.swing.JComboBox<String> volExecutableComboBox;
private javax.swing.JLabel volExecutableLabel;
// End of variables declaration//GEN-END:variables
/**
* Get the path of the user selected image.
@ -325,17 +353,28 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
return pathTextField.getText();
}
/**
*
* @return Profile or empty string if auto detect
*/
String getProfile() {
String profile = (String)profileComboBox.getSelectedItem();
if (profile.equals(AUTODETECT_PROFILE)) {
return "";
}
return profile;
}
List<String> getPluginsToRun() {
List<String> enabledPlugins = new ArrayList<>();
Map<String, String> pluginMap = new HashMap<>();
Map<String, String> pluginSettingsToSave = new HashMap<>();
for (String plugin : PluginListNames) {
if (pluginListStates.get(plugin)) {
enabledPlugins.add(plugin);
pluginMap.put(plugin, "");
}
pluginSettingsToSave.put(plugin, pluginListStates.get(plugin).toString());
}
ModuleSettings.setConfigSettings(this.contextName, pluginMap);
ModuleSettings.setConfigSettings(this.contextName, pluginSettingsToSave);
// @@ Could return keys of set
return enabledPlugins;
}

View File

@ -117,7 +117,7 @@ public class MemoryDSProcessor implements DataSourceProcessor {
@Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
configPanel.storeSettings();
run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback);
run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getProfile(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback);
}
/**
@ -131,6 +131,7 @@ public class MemoryDSProcessor implements DataSourceProcessor {
* associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID).
* @param memoryImagePath Path to the memory image file.
* @param profile Volatility profile to run or empty string to autodetect
* @param pluginsToRun The Volatility plugins to run.
* @param timeZone The time zone to use when processing dates and
* times for the image, obtained from
@ -139,8 +140,8 @@ public class MemoryDSProcessor implements DataSourceProcessor {
* processing.
* @param callback Callback to call when processing is done.
*/
private void run(String deviceId, String memoryImagePath, List<String> pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, pluginsToRun, timeZone, progressMonitor, callback);
private void run(String deviceId, String memoryImagePath, String profile, List<String> pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, profile, pluginsToRun, timeZone, progressMonitor, callback);
new Thread(addImageTask).start();
}

View File

@ -29,7 +29,6 @@ import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -41,13 +40,13 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData.EncodingType;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
/**
@ -70,6 +69,8 @@ class VolatilityProcessor {
private String moduleOutputPath;
private FileManager fileManager;
private volatile boolean isCancelled;
private Content outputVirtDir;
private String profile;
/**
* Constructs a processor that runs Volatility on a given memory image file
@ -77,11 +78,13 @@ class VolatilityProcessor {
*
* @param memoryImagePath Path to memory image file.
* @param dataSource The memory image data source.
* @param profile Volatility profile to run or empty string to autodetect
* @param plugInToRuns Volatility plugins to run.
* @param progressMonitor Progress monitor for reporting progress during
* processing.
*/
VolatilityProcessor(String memoryImagePath, Image dataSource, List<String> plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) {
VolatilityProcessor(String memoryImagePath, Image dataSource, String profile, List<String> plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) {
this.profile = profile;
this.memoryImagePath = memoryImagePath;
this.pluginsToRun = plugInToRun;
this.dataSource = dataSource;
@ -117,6 +120,13 @@ class VolatilityProcessor {
fileManager = currentCase.getServices().getFileManager();
try {
// make a virtual directory to store the reports
outputVirtDir = currentCase.getSleuthkitCase().addVirtualDirectory(dataSource.getId(), "ModuleOutput");
} catch (TskCoreException ex) {
throw new VolatilityProcessorException("Error creating virtual directory", ex);
}
/*
* Make an output folder unique to this data source.
*/
@ -125,8 +135,13 @@ class VolatilityProcessor {
File directory = new File(String.valueOf(moduleOutputPath));
if (!directory.exists()) {
directory.mkdirs();
}
// if they did not specify a profile, then run imageinfo to get one
if (profile.isEmpty() ) {
progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo("imageinfo")); //NON-NLS
runVolatilityPlugin("imageinfo"); //NON-NLS
profile = getProfileFromImageInfoOutput();
}
progressMonitor.setIndeterminate(false);
@ -177,24 +192,38 @@ class VolatilityProcessor {
commandLine.add("\"" + executableFile + "\""); //NON-NLS
File memoryImage = new File(memoryImagePath);
commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS
File imageInfoOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS
if (imageInfoOutputFile.exists()) {
String memoryProfile = parseImageInfoOutput(imageInfoOutputFile);
commandLine.add("--profile=" + memoryProfile); //NON-NLS
if (!profile.isEmpty()) {
commandLine.add("--profile=" + profile); //NON-NLS
}
commandLine.add(pluginToRun);
String outputFile = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS
switch (pluginToRun) {
case "dlldump":
case "moddump":
case "procdump":
case "dumpregistry":
case "dumpfiles":
String outputDir = moduleOutputPath + File.separator + pluginToRun;
File directory = new File(outputDir);
if (!directory.exists()) {
directory.mkdirs();
}
commandLine.add("--dump-dir=" + outputDir); //NON-NLS
break;
default:
break;
}
String outputFileAsString = moduleOutputPath + File.separator + pluginToRun + ".txt"; //NON-NLS
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
/*
* Add an environment variable to force Volatility to run with the same
* permissions Autopsy uses.
*/
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
processBuilder.redirectOutput(new File(outputFile));
processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); //NON-NLS
File outputFile = new File(outputFileAsString);
processBuilder.redirectOutput(outputFile);
processBuilder.redirectError(new File(moduleOutputPath + File.separator + "Volatility_err.txt")); //NON-NLS
processBuilder.directory(new File(memoryImage.getParent()));
try {
@ -211,31 +240,15 @@ class VolatilityProcessor {
return;
}
/*
* Add the plugin output file to the case as a report.
*/
try {
Report report = currentCase.getSleuthkitCase().addReport(outputFile, VOLATILITY, VOLATILITY + " " + pluginToRun + " Plugin"); //NON-NLS
try {
KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class);
if (searchService != null) {
searchService.index(report);
} else {
errorMsgs.add(Bundle.VolatilityProcessor_exceptionMessage_searchServiceNotFound(pluginToRun));
/*
* Log the exception as well as add it to the error
* messages, to ensure that the stack trace is not lost.
*/
logger.log(Level.WARNING, Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun));
}
String relativePath = new File(currentCase.getCaseDirectory()).toURI().relativize(new File(outputFileAsString).toURI()).getPath();
fileManager.addDerivedFile(pluginToRun, relativePath, outputFile.length(), 0, 0, 0, 0, true, outputVirtDir, null, null, null, null, EncodingType.NONE);
} catch (TskCoreException ex) {
throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun), ex);
}
} catch (TskCoreException ex) {
throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorAddingOutput(pluginToRun), ex);
errorMsgs.add("Error adding " + pluginToRun + " volatility report as a file");
logger.log(Level.WARNING, "Error adding report as derived file", ex);
}
createArtifactsFromPluginOutput(pluginToRun, new File(outputFile));
createArtifactsFromPluginOutput(pluginToRun, new File(outputFileAsString));
}
/**
@ -264,12 +277,18 @@ class VolatilityProcessor {
@NbBundle.Messages({
"VolatilityProcessor_exceptionMessage_failedToParseImageInfo=Could not parse image info"
})
private String parseImageInfoOutput(File imageOutputFile) throws VolatilityProcessorException {
private String getProfileFromImageInfoOutput() throws VolatilityProcessorException {
File imageOutputFile = new File(moduleOutputPath + File.separator + "imageinfo.txt"); //NON-NLS
try (BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) {
String fileRead = br.readLine();
if (fileRead != null) {
String[] profileLine = fileRead.split(":"); //NON-NLS
String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS
return memProfile[0].replaceAll("\\s+", ""); //NON-NLS
}
else {
throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo());
}
} catch (IOException ex) {
throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo(), ex);
}

View File

@ -23,7 +23,7 @@ import java.util.Map;
/**
* Stores data about a search before it is done.
*/
final class QueryRequest {
final class AdHocQueryRequest {
private final KeywordSearchQuery query;
private final String queryString;
@ -36,7 +36,7 @@ final class QueryRequest {
* @param id ID that callers simply increment from 0
* @param query Query that will be performed.
*/
QueryRequest(Map<String, Object> map, int id, KeywordSearchQuery query) {
AdHocQueryRequest(Map<String, Object> map, int id, KeywordSearchQuery query) {
this.queryString = query.getEscapedQueryString();
this.queryProperties = map;
this.query = query;

View File

@ -48,7 +48,7 @@ import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
import org.sleuthkit.autopsy.datamodel.EmptyNode;
import org.sleuthkit.autopsy.datamodel.KeyValue;
import org.sleuthkit.autopsy.datamodel.KeyValueNode;
import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.KeyValueQueryContent;
import org.sleuthkit.autopsy.keywordsearch.AdHocSearchChildFactory.KeywordHitKey;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
@ -66,9 +66,9 @@ import org.sleuthkit.datamodel.TskCoreException;
* Responsible for assembling nodes and columns in the right way and performing
* lazy queries as needed.
*/
class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
class AdHocSearchChildFactory extends ChildFactory<KeyValue> {
private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName());
private static final Logger logger = Logger.getLogger(AdHocSearchChildFactory.class.getName());
//common properties (superset of all Node properties) to be displayed as columns
static final List<String> COMMON_PROPERTIES
@ -82,9 +82,9 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
.map(Object::toString))
.collect(Collectors.toList());
private final Collection<QueryRequest> queryRequests;
private final Collection<AdHocQueryRequest> queryRequests;
KeywordSearchResultFactory(Collection<QueryRequest> queryRequests) {
AdHocSearchChildFactory(Collection<AdHocQueryRequest> queryRequests) {
this.queryRequests = queryRequests;
}
@ -98,7 +98,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
@Override
protected boolean createKeys(List<KeyValue> toPopulate) {
for (QueryRequest queryRequest : queryRequests) {
for (AdHocQueryRequest queryRequest : queryRequests) {
/**
* Check the validity of the requested query.
*/
@ -155,7 +155,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
}
int hitNumber = 0;
List<KeyValueQueryContent> tempList = new ArrayList<>();
List<KeywordHitKey> tempList = new ArrayList<>();
for (KeywordHit hit : getOneHitPerObject(queryResults)) {
/**
@ -203,7 +203,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
hitName = contentName;
}
hitNumber++;
tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
tempList.add(new KeywordHitKey(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
}
@ -253,8 +253,8 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
protected Node createNodeForKey(KeyValue key) {
Node resultNode;
if (key instanceof KeyValueQueryContent) {
AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key);
if (key instanceof KeywordHitKey) {
AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeywordHitKey) key);
/**
* Place the Content, Artifact and hit results into the lookup for
@ -262,17 +262,17 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
*/
ArrayList<Object> lookups = new ArrayList<>();
lookups.add(adHocQueryResult);
if (((KeyValueQueryContent) key).getContent() != null) {
lookups.add(((KeyValueQueryContent) key).getContent());
if (((KeywordHitKey) key).getContent() != null) {
lookups.add(((KeywordHitKey) key).getContent());
}
if (((KeyValueQueryContent) key).getArtifact() != null) {
lookups.add(((KeyValueQueryContent) key).getArtifact());
if (((KeywordHitKey) key).getArtifact() != null) {
lookups.add(((KeywordHitKey) key).getArtifact());
}
Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.fixed(lookups.toArray()));
//wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
resultNode = new KeywordSearchFilterNode(kvNode);
resultNode = new AdHocSearchFilterNode(kvNode);
} else {
resultNode = new EmptyNode("This Node Is Empty");
resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text"));
@ -298,7 +298,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
* which the hit was found.
* @param results The query results.
*/
AdHocQueryResult(KeyValueQueryContent key) {
AdHocQueryResult(KeywordHitKey key) {
this.solrObjectId = key.getSolrObjectId();
this.results = key.getHits();
}
@ -327,7 +327,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
* Used to display keyword search results in table. Eventually turned into a
* node.
*/
class KeyValueQueryContent extends KeyValue {
class KeywordHitKey extends KeyValue {
private final long solrObjectId;
@ -350,7 +350,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
* @param query Query used in search
* @param hits Full set of search results (for all files! @@@)
*/
KeyValueQueryContent(String name, Map<String, Object> map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) {
KeywordHitKey(String name, Map<String, Object> map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) {
super(name, map, id);
this.solrObjectId = solrObjectId;
this.content = content;

View File

@ -37,14 +37,14 @@ import org.sleuthkit.autopsy.coreutils.Logger;
* Delegates the actual work to the various implementations of
* KeywordSearchQuery.
*/
class KeywordSearchQueryDelegator {
class AdHocSearchDelegator {
private List<KeywordList> keywordLists;
private final List<KeywordList> keywordLists;
private List<KeywordSearchQuery> queryDelegates;
private static int resultWindowCount = 0; //keep track of unique window ids to display
private static Logger logger = Logger.getLogger(KeywordSearchQueryDelegator.class.getName());
private static final Logger logger = Logger.getLogger(AdHocSearchDelegator.class.getName());
public KeywordSearchQueryDelegator(List<KeywordList> keywordLists) {
public AdHocSearchDelegator(List<KeywordList> keywordLists) {
this.keywordLists = keywordLists;
init();
}
@ -66,14 +66,14 @@ class KeywordSearchQueryDelegator {
* Post results into a new DataResultViewer.
*/
public void execute() {
Collection<QueryRequest> queryRequests = new ArrayList<>();
Collection<AdHocQueryRequest> queryRequests = new ArrayList<>();
int queryID = 0;
StringBuilder queryConcat = new StringBuilder(); // concatenation of all query strings
for (KeywordSearchQuery q : queryDelegates) {
Map<String, Object> kvs = new LinkedHashMap<>();
final String queryStr = q.getQueryString();
queryConcat.append(queryStr).append(" ");
queryRequests.add(new QueryRequest(kvs, ++queryID, q));
queryRequests.add(new AdHocQueryRequest(kvs, ++queryID, q));
}
String queryConcatStr = queryConcat.toString();
@ -85,7 +85,7 @@ class KeywordSearchQueryDelegator {
Node rootNode;
if (queryRequests.size() > 0) {
Children childNodes =
Children.create(new KeywordSearchResultFactory(queryRequests), true);
Children.create(new AdHocSearchChildFactory(queryRequests), true);
rootNode = new AbstractNode(childNodes);
} else {

View File

@ -53,15 +53,17 @@ import org.sleuthkit.datamodel.VirtualDirectory;
/**
* FilterNode containing properties and actions for keyword search.
*
* Wraps the generic KeyValue node and customizes the property sheet and lookup
*/
class KeywordSearchFilterNode extends FilterNode {
class AdHocSearchFilterNode extends FilterNode {
/**
* Instantiate a KeywordSearchFilterNode.
*
* @param original The original source node.
*/
KeywordSearchFilterNode(Node original) {
AdHocSearchFilterNode(Node original) {
super(original, null, new ProxyLookup(original.getLookup()));
}
@ -116,7 +118,7 @@ class KeywordSearchFilterNode extends FilterNode {
@Override
public List<Action> visit(Report r) {
List<Action> actionsList = new ArrayList<>();
actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), KeywordSearchFilterNode.this));
actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this));
actionsList.addAll(ContextMenuExtensionPoint.getActions());
return actionsList;
@ -161,7 +163,7 @@ class KeywordSearchFilterNode extends FilterNode {
private List<Action> getFileActions(boolean enableHashSearch) {
List<Action> actionsList = new ArrayList<>();
actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), KeywordSearchFilterNode.this));
actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this));
actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal()));
actionsList.add(null);
actionsList.add(ExtractAction.getInstance());

View File

@ -30,12 +30,12 @@ import org.openide.util.NbBundle;
* class and extended classes model the user's intentions, not necessarily how
* the search manager and 3rd party tools actually perform the search.
*/
abstract class KeywordSearchPanel extends javax.swing.JPanel {
abstract class AdHocSearchPanel extends javax.swing.JPanel {
private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader");
protected int filesIndexed;
KeywordSearchPanel() {
AdHocSearchPanel() {
initListeners();
}
@ -110,7 +110,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel {
}
}
KeywordSearchQueryDelegator man = null;
AdHocSearchDelegator man = null;
final List<KeywordList> keywordLists = getKeywordLists();
if (keywordLists.isEmpty()) {
@ -119,7 +119,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel {
KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
return;
}
man = new KeywordSearchQueryDelegator(keywordLists);
man = new AdHocSearchDelegator(keywordLists);
if (man.validate()) {
man.execute();

View File

@ -44,7 +44,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
* Viewer panel widget for keyword lists that is used in the ingest config and
* options area.
*/
class DropdownListSearchPanel extends KeywordSearchPanel {
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
class DropdownListSearchPanel extends AdHocSearchPanel {
private static final Logger logger = Logger.getLogger(DropdownListSearchPanel.class.getName());
private static DropdownListSearchPanel instance;
@ -131,7 +132,7 @@ class DropdownListSearchPanel extends KeywordSearchPanel {
@Override
public void actionPerformed(ActionEvent e) {
if (ingestRunning) {
SearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists());
IngestSearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists());
logger.log(Level.INFO, "Submitted enqueued lists to ingest"); //NON-NLS
} else {
searchAction(e);

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JMenuItem;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
@ -42,7 +43,8 @@ import org.sleuthkit.autopsy.coreutils.Logger;
* perform this task at the desired size, and neither could numerous other
* fonts.
*/
public class DropdownSingleTermSearchPanel extends KeywordSearchPanel {
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class DropdownSingleTermSearchPanel extends AdHocSearchPanel {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(DropdownSingleTermSearchPanel.class.getName());
@ -135,8 +137,22 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel {
*
* @return The keyword list.
*/
@NbBundle.Messages({"DropdownSingleTermSearchPanel.warning.title=Warning",
"DropdownSingleTermSearchPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"})
@Override
List<KeywordList> getKeywordLists() {
if (regexRadioButton.isSelected()) {
if((keywordTextField.getText() != null) &&
(keywordTextField.getText().startsWith("^") ||
(keywordTextField.getText().endsWith("$") && ! keywordTextField.getText().endsWith("\\$")))) {
KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "DropdownSingleTermSearchPanel.warning.title"),
NbBundle.getMessage(this.getClass(), "DropdownSingleTermSearchPanel.warning.text"),
KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN);
}
}
List<Keyword> keywords = new ArrayList<>();
keywords.add(new Keyword(keywordTextField.getText(), !regexRadioButton.isSelected(), exactRadioButton.isSelected()));
List<KeywordList> keywordLists = new ArrayList<>();

View File

@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.AdHocQueryResult;
import org.sleuthkit.autopsy.keywordsearch.AdHocSearchChildFactory.AdHocQueryResult;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact;

View File

@ -125,7 +125,10 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
}
}
@NbBundle.Messages("GlobalEditListPanel.editKeyword.title=Edit Keyword")
@NbBundle.Messages({"GlobalEditListPanel.editKeyword.title=Edit Keyword",
"GlobalEditListPanel.warning.title=Warning",
"GlobalEditListPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"})
/**
* Adds keywords to a keyword list, returns true if at least one keyword was
* successfully added and no duplicates were found.
@ -151,6 +154,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
dupeCount = 0;
badCount = 0;
keywordsToRedisplay = "";
boolean displayedBoundaryWarning = false;
if (!dialog.getKeywords().isEmpty()) {
@ -165,6 +169,19 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
continue;
}
// Check if it is a regex and starts or ends with a boundary character
if (( ! displayedBoundaryWarning) && dialog.isKeywordRegex()) {
if(newWord.startsWith("^") ||
(newWord.endsWith("$") && ! newWord.endsWith("\\$"))) {
KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.title"),
NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.text"),
KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN);
// Only display the warning once
displayedBoundaryWarning = true;
}
}
//check if valid
boolean valid = true;
try {

View File

@ -55,10 +55,10 @@ import org.sleuthkit.autopsy.ingest.IngestServices;
* Singleton keyword search manager: Launches search threads for each job and
* performs commits, both on timed intervals.
*/
final class SearchRunner {
final class IngestSearchRunner {
private static final Logger logger = Logger.getLogger(SearchRunner.class.getName());
private static SearchRunner instance = null;
private static final Logger logger = Logger.getLogger(IngestSearchRunner.class.getName());
private static IngestSearchRunner instance = null;
private IngestServices services = IngestServices.getInstance();
private Ingester ingester = null;
private long currentUpdateIntervalMs;
@ -71,7 +71,7 @@ final class SearchRunner {
// maps a jobID to the search
private Map<Long, SearchJobInfo> jobs = new ConcurrentHashMap<>();
SearchRunner() {
IngestSearchRunner() {
currentUpdateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000;
ingester = Ingester.getDefault();
jobProcessingExecutor = new ScheduledThreadPoolExecutor(NUM_SEARCH_SCHEDULING_THREADS, new ThreadFactoryBuilder().setNameFormat(SEARCH_SCHEDULER_THREAD_NAME).build());
@ -81,9 +81,9 @@ final class SearchRunner {
*
* @return the singleton object
*/
public static synchronized SearchRunner getInstance() {
public static synchronized IngestSearchRunner getInstance() {
if (instance == null) {
instance = new SearchRunner();
instance = new IngestSearchRunner();
}
return instance;
}
@ -167,7 +167,7 @@ final class SearchRunner {
}
//stop currentSearcher
SearchRunner.Searcher currentSearcher = job.getCurrentSearcher();
IngestSearchRunner.Searcher currentSearcher = job.getCurrentSearcher();
if ((currentSearcher != null) && (!currentSearcher.isDone())) {
logger.log(Level.INFO, "Cancelling search job {0}", jobId); //NON-NLS
currentSearcher.cancel(true);
@ -229,7 +229,7 @@ final class SearchRunner {
logger.log(Level.INFO, "Checking for previous search for search job {0} before executing final search", job.getJobId()); //NON-NLS
job.waitForCurrentWorker();
SearchRunner.Searcher finalSearcher = new SearchRunner.Searcher(job, true);
IngestSearchRunner.Searcher finalSearcher = new IngestSearchRunner.Searcher(job, true);
job.setCurrentSearcher(finalSearcher); //save the ref
logger.log(Level.INFO, "Kicking off final search for search job {0}", job.getJobId()); //NON-NLS
finalSearcher.execute(); //start thread
@ -252,7 +252,7 @@ final class SearchRunner {
*/
private final class PeriodicSearchTask implements Runnable {
private final Logger logger = Logger.getLogger(SearchRunner.PeriodicSearchTask.class.getName());
private final Logger logger = Logger.getLogger(IngestSearchRunner.PeriodicSearchTask.class.getName());
@Override
public void run() {
@ -341,7 +341,7 @@ final class SearchRunner {
// Map of keyword to the object ids that contain a hit
private Map<Keyword, Set<Long>> currentResults; //guarded by SearchJobInfo.this
private SearchRunner.Searcher currentSearcher;
private IngestSearchRunner.Searcher currentSearcher;
private AtomicLong moduleReferenceCount = new AtomicLong(0);
private final Object finalSearchLock = new Object(); //used for a condition wait
@ -393,11 +393,11 @@ final class SearchRunner {
workerRunning = flag;
}
private synchronized SearchRunner.Searcher getCurrentSearcher() {
private synchronized IngestSearchRunner.Searcher getCurrentSearcher() {
return currentSearcher;
}
private synchronized void setCurrentSearcher(SearchRunner.Searcher searchRunner) {
private synchronized void setCurrentSearcher(IngestSearchRunner.Searcher searchRunner) {
currentSearcher = searchRunner;
}
@ -453,7 +453,7 @@ final class SearchRunner {
private List<KeywordList> keywordLists;
private Map<Keyword, KeywordList> keywordToList; //keyword to list name mapping
private AggregateProgressHandle progressGroup;
private final Logger logger = Logger.getLogger(SearchRunner.Searcher.class.getName());
private final Logger logger = Logger.getLogger(IngestSearchRunner.Searcher.class.getName());
private boolean finalRun = false;
Searcher(SearchJobInfo job) {
@ -483,7 +483,7 @@ final class SearchRunner {
if (progressGroup != null) {
progressGroup.setDisplayName(displayName + " " + NbBundle.getMessage(this.getClass(), "SearchRunner.doInBackGround.cancelMsg"));
}
return SearchRunner.Searcher.this.cancel(true);
return IngestSearchRunner.Searcher.this.cancel(true);
}
}, null);

View File

@ -27,6 +27,8 @@ import org.apache.solr.common.SolrInputDocument;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk;
import org.sleuthkit.datamodel.AbstractFile;
@ -235,7 +237,9 @@ class Ingester {
try {
//TODO: consider timeout thread, or vary socket timeout based on size of indexed content
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk");
solrServer.addDocument(updateDoc);
EnterpriseHealthMonitor.submitTimingMetric(metric);
uncommitedIngests = true;
} catch (KeywordSearchModuleException | NoOpenCoreException ex) {

View File

@ -288,7 +288,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
return ProcessResult.OK;
}
List<String> keywordListNames = settings.getNamesOfEnabledKeyWordLists();
SearchRunner.getInstance().startJob(context, keywordListNames);
IngestSearchRunner.getInstance().startJob(context, keywordListNames);
startedSearching = true;
}
@ -309,13 +309,13 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
if (context.fileIngestIsCancelled()) {
logger.log(Level.INFO, "Keyword search ingest module instance {0} stopping search job due to ingest cancellation", instanceNum); //NON-NLS
SearchRunner.getInstance().stopJob(jobId);
IngestSearchRunner.getInstance().stopJob(jobId);
cleanup();
return;
}
// Remove from the search list and trigger final commit and final search
SearchRunner.getInstance().endJob(jobId);
IngestSearchRunner.getInstance().endJob(jobId);
// We only need to post the summary msg from the last module per job
if (refCounter.decrementAndGet(jobId) == 0) {

View File

@ -170,10 +170,13 @@ final class RegexQuery implements KeywordSearchQuery {
*/
// We construct the query by surrounding it with slashes (to indicate it is
// a regular expression search) and .* as anchors (if the query doesn't
// already have them).
// already have them). We do not add .* if there is a boundary character.
boolean skipWildcardPrefix = queryStringContainsWildcardPrefix || getQueryString().startsWith("^");
boolean skipWildcardSuffix = queryStringContainsWildcardSuffix ||
(getQueryString().endsWith("$") && ( ! getQueryString().endsWith("\\$")));
solrQuery.setQuery((field == null ? Server.Schema.CONTENT_STR.toString() : field) + ":/"
+ (queryStringContainsWildcardPrefix ? "" : ".*") + getQueryString()
+ (queryStringContainsWildcardSuffix ? "" : ".*") + "/");
+ (skipWildcardPrefix ? "" : ".*") + getQueryString()
+ (skipWildcardSuffix ? "" : ".*") + "/");
// Set the fields we want to have returned by the query.
solrQuery.setFields(Server.Schema.CONTENT_STR.toString(), Server.Schema.ID.toString(), Server.Schema.CHUNK_SIZE.toString());

View File

@ -70,6 +70,8 @@ import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
import org.sleuthkit.datamodel.Content;
@ -708,7 +710,9 @@ public class Server {
if (null == currentCore) {
throw new NoOpenCoreException();
}
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk");
currentCore.addDocument(doc);
EnterpriseHealthMonitor.submitTimingMetric(metric);
} finally {
currentCoreLock.readLock().unlock();
}
@ -773,7 +777,9 @@ public class Server {
IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory());
currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS
}
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check");
connectToSolrServer(currentSolrServer);
EnterpriseHealthMonitor.submitTimingMetric(metric);
} catch (SolrServerException | IOException ex) {
throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
@ -1315,11 +1321,13 @@ public class Server {
* @throws IOException
*/
void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException {
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check");
CoreAdminRequest statusRequest = new CoreAdminRequest();
statusRequest.setCoreName( null );
statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS );
statusRequest.setIndexInfoNeeded(false);
statusRequest.process(solrServer);
EnterpriseHealthMonitor.submitTimingMetric(metric);
}
/**

View File

@ -390,7 +390,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
* in less than roughly two seconds. This stuff should be reworked using
* an ExecutorService and tasks with Futures.
*/
KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters();
AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {

View File

@ -1,3 +1,3 @@
<project name="TSK_VERSION">
<property name="TSK_VERSION" value="4.6.0"/>
<property name="TSK_VERSION" value="4.6.1"/>
</project>

View File

@ -30,14 +30,14 @@ install:
- ps: pushd C:\
- git clone https://github.com/sleuthkit/sleuthkit
- ps: popd
services:
- postgresql95
build_script:
- cd %TSK_HOME%
- python setupLibs.py
- python win32\updateBuildLibs.py -m
- python setupDevRepos.py
- python win32\updateAndBuildAll.py -m
- ps: pushd bindings/java
- ps: ant -version
- cmd: ant dist-PostgreSQL

View File

@ -91,19 +91,10 @@
<copy file="${basedir}/LICENSE-2.0.txt" tofile="${zip-tmp}/${app.name}/LICENSE-2.0.txt"/>
<copy file="${basedir}/NEWS.txt" tofile="${zip-tmp}/${app.name}/NEWS.txt"/>
<copy file="${basedir}/KNOWN_ISSUES.txt" tofile="${zip-tmp}/${app.name}/KNOWN_ISSUES.txt"/>
<if>
<equals arg1="${os.family}" arg2="unix"/>
<then>
<copy file="${basedir}/unix_setup.sh" tofile="${zip-tmp}/${app.name}/unix_setup.sh"/>
<replaceregexp file="${zip-tmp}/${app.name}/unix_setup.sh" match="TSK_VERSION=(.*)" replace="TSK_VERSION=${TSK_VERSION}" byline="true"/>
</then>
</if>
<if>
<equals arg1="${os.family}" arg2="windows"/>
<then>
<unzip src="${thirdparty.dir}/gstreamer/${os.family}/i386/0.10.7/gstreamer.zip" dest="${zip-tmp}/${app.name}/gstreamer"/>
</then>
</if>
<unzip src="${thirdparty.dir}/gstreamer/windows/i386/0.10.7/gstreamer.zip" dest="${zip-tmp}/${app.name}/gstreamer"/>
<copy file="${basedir}/icons/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/>

View File

@ -39,6 +39,11 @@ Substring match should be used where the search term is just part of a word, or
Regex match can be used to search for a specific pattern. Regular expressions are supported using Lucene Regex Syntax which is documented here: https://www.elastic.co/guide/en/elasticsearch/reference/1.6/query-dsl-regexp-query.html#regexp-syntax. Wildcards are automatically added to the beginning and end of the regular expressions to ensure all matches are found. Additionally, the resulting hits are split on common token separator boundaries (e.g. space, newline, colon, exclamation point etc.) to make the resulting keyword hit more amenable to highlighting.
<b>Note:</b> Since Autopsy 4.4, boundary characters ('^' and '$') no longer work as word boundaries. Previously a search for "^[0-9]{5}$" would return all five
digit strings surrounded by some type of non-word characters. For example, "The number 12345 is.." would contain a match, while "123456789 people" would not. This was because the regex
was compared against each "word" in the document. In newer versions, the text is not split into words internally so this type of search no longer works. To get similar results, replace the
boundary characters with the specific characters that should represent a word break. For example, "^[0-9]{5}$" could become "[ \.\-\,][0-9]{5}[ \.\-\,]".
There is some validation on the regex but it's best to test on a sample image to make sure your regexes are correct and working as expected. One simple way to test is by creating a sample text file that your expression should match, ingesting it as a \ref ds_log "Logical File Set" and then running the regex query.
> In the year 1885 in an article titled Current Notes, the quick brown fox first jumped over the lazy dog.

View File

@ -34,7 +34,7 @@ To open a case, either:
"Open Recent Case" will always bring up a screen allowing you to select one of the recently opened cases. "Open Case" will do one of two things;
- If multi-user cases are not enabled, it will bring up a file chooser that can be used to browse to the ".aut" file in the case directory of the desired case
- If multi-user case are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The following shows the multi-user case selection screen:
- If multi-user cases are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The multi-user case selection screen has a \ref quick_search feature which can be used to quickly find a case in the table. The following shows the multi-user case selection screen:
\image html multi_user_case_select.png

View File

@ -20,6 +20,8 @@ The middle column displays each account, its device and type, and the number of
Selecting an account in the middle column will bring up the messages for that account in the right hand column. Here data about each message is displayed in the top section, and the messages itself can be seen in the bottom section (if applicable).
The middle column and the right hand column both have a \ref quick_search feature which can be used to quickly find a visible item in their section's table.
\image html cvt_messages.png
*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -39,6 +39,7 @@ The following topics are available here:
- \subpage tree_viewer_page
- \subpage result_viewer_page
- \subpage content_viewer_page
- \subpage quick_search
- \subpage image_gallery_page
- \subpage file_search_page
- \subpage ad_hoc_keyword_search_page

View File

@ -0,0 +1,24 @@
/*! \page quick_search Quick Search
The quick search feature allows you to search within the data on a panel for a given string, it will not search data in hidden columns or collapsed nodes.
How to use it
-----
In order to use the search you need to select any item in the area you wish to search, and start typing. If quick search is available in the area you have selected a search field will appear in the bottom left hand corner of the area. As you type the string you are searching for it will auto-update to select one of the results which matches your string. You can switch between the results which match the string you have typed with the up and down keys. The search does not support the use of regular expressions but will match against any sub-sting in the fields it searches, not just at the beginning of the field.
\image html quick_search_result.PNG
Configuration
-----
By default the search will match against the data in all fields which are in the currently selected area. The search will also ignore case by default. If you want to change either of these default behaviors you can click the magnifying glass with the down arrow icon and configure which columns will be searched as well as if the search should ignore case.
\image html quick_search_configuration.PNG
Where it can be used
-----
- The \ref tree_viewer_page "tree viewer"
- The \ref result_viewer_page "table view"
- The \ref case_open "open multi-user case panel"
- The \ref timeline_page tools table view
- The \ref communications_page "Communication Visualization Tool's" browse panel
- The \ref communications_page "Communication Visualization Tool's" message panel
*/

View File

@ -59,6 +59,8 @@ If you are viewing files from the Views and Results nodes, you can right-click o
If you want to search for single keywords, then you can use the search box in the upper right of the program. The results will be shown in a table in the upper right.
The tree on the left as well as the table on the right have a \ref quick_search feature which can be used to quickly find a visible node.
You can tag (bookmark) arbitrary files so that you can more quickly find them later or so that you can include them specifically in a report.
\subsection s2a Ingest Inbox

View File

@ -69,6 +69,7 @@ The __Counts View__ shows a stacked bar chart. Use this type of graph to show ho
The __Details View__ shows individual or groups of related events. Date/time is represented horizontally along the x-axis, but the vertical axis does not represent any specific units. You would use this interface to answer questions about what specific events happened in a given time frame or what events occurred before or after a given event. You would generally use this type of interface after using the Counts View to identify a period of time that you wanted details on. There can be a lot of details in this view and we have introduced zooming concepts, as described in the next section, to help with this.
The table on the bottom left hand side of the panel has a \ref quick_search feature which can be used to quickly find a node in the table.
Visualization settings
----------------------

View File

@ -306,7 +306,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
* @throws NoCurrentCaseException if there is no open case.
* @return the temporary folder
*/
public static String getTempPath() throws NoCurrentCaseException {
static String getTempPath() throws NoCurrentCaseException {
String tmpDir = Case.getOpenCase().getTempDirectory() + File.separator
+ "EmailParser"; //NON-NLS
File dir = new File(tmpDir);
@ -322,7 +322,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
* @throws NoCurrentCaseException if there is no open case.
* @return the module output folder
*/
public static String getModuleOutputPath() throws NoCurrentCaseException {
static String getModuleOutputPath() throws NoCurrentCaseException {
String outDir = Case.getOpenCase().getModuleDirectory() + File.separator
+ EmailParserModuleFactory.getModuleName();
File dir = new File(outDir);
@ -338,7 +338,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
* @throws NoCurrentCaseException if there is no open case.
* @return the relative path of the module output folder
*/
public static String getRelModuleOutputPath() throws NoCurrentCaseException {
static String getRelModuleOutputPath() throws NoCurrentCaseException {
return Case.getOpenCase().getModuleOutputDirectoryRelativePath() + File.separator
+ EmailParserModuleFactory.getModuleName();
}

View File

@ -6,8 +6,9 @@ TSK_VERSION=4.6.0
# Verify PhotoRec was installed
photorec_filepath=/usr/bin/photorec
if [ -f "$photorec_filepath" ]; then
echo "$photorec_filepath found"
photorec_osx_filepath=/usr/local/bin/photorec
if [ -f "$photorec_filepath" ] || [ -f "$photorec_osx_filepath" ]; then
echo "photorec found"
else
echo "ERROR: Photorec not found, please install the testdisk package"
exit 1
@ -27,7 +28,18 @@ else
fi
# Verify Sleuth Kit Java was installed
sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar;
if [ -f "/usr/share/java/sleuthkit-$TSK_VERSION.jar" ]; then
sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar
elif [ -f "/usr/local/share/java/sleuthkit-$TSK_VERSION.jar" ]; then
sleuthkit_jar_filepath=/usr/local/share/java/sleuthkit-$TSK_VERSION.jar
else
echo "sleuthkit.jar file not found"
echo "exiting .."
exit 1
fi
ext_jar_filepath=$PWD/autopsy/modules/ext/sleuthkit-postgresql-$TSK_VERSION.jar;
if [ -f "$sleuthkit_jar_filepath" ]; then
echo "$sleuthkit_jar_filepath found"