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: install:
- sudo apt-get install testdisk - sudo apt-get install testdisk
- cd sleuthkit/sleuthkit - cd sleuthkit/sleuthkit
- sh install-sleuthkit.sh - sh travis_build.sh
script: script:
- set -e - set -e
- echo "building autopsy..." && echo -en 'travis_fold:start:script.build\\r' - echo "building autopsy..." && echo -en 'travis_fold:start:script.build\\r'

View File

@ -86,7 +86,10 @@
<mkdir dir="${basedir}/test/qa-functional/data"/> <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=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=1bghoSm7z7nhmGIxlllyY1MMlbLntxm7n" dest="${test-input}/local_files_test.zip" skipexisting="true"/>
</target> <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"> <target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist, getTestDataFiles">
<mkdir dir="${ext.dir}"/> <mkdir dir="${ext.dir}"/>
<copy file="${thirdparty.dir}/LICENSE-2.0.txt" todir="${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.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.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.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-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.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 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.events</package>
<package>org.sleuthkit.autopsy.filesearch</package> <package>org.sleuthkit.autopsy.filesearch</package>
<package>org.sleuthkit.autopsy.guiutils</package> <package>org.sleuthkit.autopsy.guiutils</package>
<package>org.sleuthkit.autopsy.healthmonitor</package>
<package>org.sleuthkit.autopsy.ingest</package> <package>org.sleuthkit.autopsy.ingest</package>
<package>org.sleuthkit.autopsy.keywordsearchservice</package> <package>org.sleuthkit.autopsy.keywordsearchservice</package>
<package>org.sleuthkit.autopsy.menuactions</package> <package>org.sleuthkit.autopsy.menuactions</package>
@ -347,8 +348,8 @@
<binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin> <binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin>
</class-path-extension> </class-path-extension>
<class-path-extension> <class-path-extension>
<runtime-relative-path>ext/sleuthkit-postgresql-4.6.0.jar</runtime-relative-path> <runtime-relative-path>ext/sleuthkit-postgresql-4.6.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-postgresql-4.6.0.jar</binary-origin> <binary-origin>release/modules/ext/sleuthkit-postgresql-4.6.1.jar</binary-origin>
</class-path-extension> </class-path-extension>
<class-path-extension> <class-path-extension>
<runtime-relative-path>ext/opencv-248.jar</runtime-relative-path> <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> <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> <binary-origin>release/modules/ext/curator-client-2.8.0.jar</binary-origin>
</class-path-extension> </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> <class-path-extension>
<runtime-relative-path>ext/tika-parsers-1.17.jar</runtime-relative-path> <runtime-relative-path>ext/tika-parsers-1.17.jar</runtime-relative-path>
<binary-origin>release/modules/ext/tika-parsers-1.17.jar</binary-origin> <binary-origin>release/modules/ext/tika-parsers-1.17.jar</binary-origin>
</class-path-extension> </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> <class-path-extension>
<runtime-relative-path>ext/sqlite-jdbc-3.8.11.jar</runtime-relative-path> <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> <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 * @throws TskCoreException if there is a problem adding the report to the
* case database. * case database.
*/ */
public Report addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException { public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
return addReport(localPath, srcModuleName, reportName, null); addReport(localPath, srcModuleName, reportName, null);
} }
/** /**

View File

@ -68,7 +68,17 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter {
@Override @Override
public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) {
return new IngestModule((IngestSettings) 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 @Override

View File

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

View File

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

View File

@ -98,7 +98,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
@NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments") @NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments")
public MessageContentViewer() { public MessageContentViewer() {
initComponents(); 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); attachmentsScrollPane.setViewportView(drp);
msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true);

View File

@ -455,7 +455,8 @@ public final class CoordinationService {
CASES("cases"), CASES("cases"),
MANIFESTS("manifests"), MANIFESTS("manifests"),
CONFIG("config"), CONFIG("config"),
CENTRAL_REPO("centralRepository"); CENTRAL_REPO("centralRepository"),
HEALTH_MONITOR("healthMonitor");
private final String displayName; 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.datamodel.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.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 * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,41 +22,58 @@ import java.util.List;
import org.openide.nodes.Node; 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 { 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(); 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); 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); 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(); public boolean isMain();

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-17 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,76 +22,120 @@ import java.awt.Component;
import org.openide.nodes.Node; import org.openide.nodes.Node;
/** /**
* Interface for the different viewers that show a set of nodes in the * An interface for result viewers. A result viewer uses a Swing Component to
* DataResult area. AbstractDataResultViewer has default implementations for the * provide a view of the application data represented by a NetBeans Node passed
* action handlers. * 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 { public interface DataResultViewer {
/** /**
* Set the root node to display in this viewer. When called with null, must * Creates a new instance of this result viewer, which allows the
* clear all references to previous nodes. * 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
public void setNode(Node selectedNode); * result viewer as a "factory" for creating other instances.
/**
* Gets the title of this viewer
*/
public String getTitle();
/**
* Get a new instance of DataResultViewer
*/ */
public DataResultViewer createInstance(); 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(); 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 * Frees any resources tha have been allocated by this result viewer, in
* for permanently disposing of it. * 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 * @param node The node.
*/
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 selectedNode the selected node * @deprecated This API is not used by the application.
*
* @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 @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 * Autopsy Forensic Browser
* *
* Copyright 2011-17 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,65 +23,64 @@ import java.beans.PropertyVetoException;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JPanel; import javax.swing.JPanel;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerManager.Provider;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
/** /**
* This class provides a default implementation of selected methods of the * An abstract base class for an implementation of the result viewer interface
* DataResultViewer interface. Derived classes will be Swing JPanel objects. * that is a JPanel that displays the child nodes of a given node using a
* Additionally, the ExplorerManager.Provider interface is implemented to supply * NetBeans explorer view as a child component. Such a result viewer should use
* an ExplorerManager to derived classes and their child components. * 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()); 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 * Constructs an abstract base class for an implementation of the result
* an ExplorerManager provided by a TopComponent, allowing Node selections * viewer interface that is a JPanel that displays the child nodes of the
* to be available to Actions via the action global context lookup when the * given node using a NetBeans explorer view as a child component.
* 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.
* *
* @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) { public AbstractDataResultViewer(ExplorerManager explorerManager) {
this.em = explorerManager; this.explorerManager = 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());
} }
@Override @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 @Override
public void expandNode(Node n) { public void setSelectedNodes(Node[] selected) {
} try {
this.getExplorerManager().setSelectedNodes(selected);
@Override } catch (PropertyVetoException ex) {
public void resetComponent() { logger.log(Level.SEVERE, "Couldn't set selected nodes", ex); //NON-NLS
}
} }
@Override @Override
@ -89,22 +88,4 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView
return this; 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.goToPageLabel.text=Go to Page:
DataResultViewerThumbnail.goToPageField.text= DataResultViewerThumbnail.goToPageField.text=
AdvancedConfigurationDialog.cancelButton.text=Cancel 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.waitText=Retrieving and preparing data, please wait...
DataContentViewerArtifact.errorText=Error retrieving result DataContentViewerArtifact.errorText=Error retrieving result
DataContentViewerArtifact.title=Results DataContentViewerArtifact.title=Results
@ -181,3 +178,6 @@ AutopsyOptionsPanel.agencyLogoPreview.text=<html><div style='text-align: center;
AutopsyOptionsPanel.logoPanel.border.title=Logo AutopsyOptionsPanel.logoPanel.border.title=Logo
AutopsyOptionsPanel.runtimePanel.border.title=Runtime AutopsyOptionsPanel.runtimePanel.border.title=Runtime
AutopsyOptionsPanel.viewPanel.border.title=View 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.pageNumLabel.text=-
DataResultViewerThumbnail.goToPageLabel.text=\u6b21\u306e\u30da\u30fc\u30b8\u306b\u79fb\u52d5\uff1a DataResultViewerThumbnail.goToPageLabel.text=\u6b21\u306e\u30da\u30fc\u30b8\u306b\u79fb\u52d5\uff1a
AdvancedConfigurationDialog.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb 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.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.errorText=\u7d50\u679c\u3092\u53d6\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
DataContentViewerArtifact.title=\u7d50\u679c 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.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.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 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 // file and not more details about the artifact
if ((artifact == null) if ((artifact == null)
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) || (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; return 3;
} else { } else {
return 6; return 6;

View File

@ -25,13 +25,13 @@
<DimensionLayout dim="0"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" 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"/> <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"/> <EmptySpace max="-2" attributes="0"/>
<Component id="matchLabel" min="-2" max="-2" attributes="0"/> <Component id="matchLabel" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Component id="dataResultTabbedPanel" max="32767" attributes="0"/> <Component id="resultViewerTabs" max="32767" attributes="0"/>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
<DimensionLayout dim="1"> <DimensionLayout dim="1">
@ -39,32 +39,32 @@
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="103" alignment="0" groupAlignment="3" 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"/> <Component id="matchLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Component id="directoryTablePath" alignment="0" min="-2" max="-2" attributes="0"/> <Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/> <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>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
<SubComponents> <SubComponents>
<Component class="javax.swing.JLabel" name="directoryTablePath"> <Component class="javax.swing.JLabel" name="descriptionLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[5, 14]"/> <Dimension value="[5, 14]"/>
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JLabel" name="numberMatchLabel"> <Component class="javax.swing.JLabel" name="numberOfChildNodesLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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> </Property>
</Properties> </Properties>
</Component> </Component>
@ -75,7 +75,7 @@
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
<Container class="javax.swing.JTabbedPane" name="dataResultTabbedPanel"> <Container class="javax.swing.JTabbedPane" name="resultViewerTabs">
<Properties> <Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 5]"/> <Dimension value="[0, 5]"/>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,6 +22,7 @@ import java.awt.Cursor;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
@ -44,153 +45,204 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
/** /**
* A Swing JPanel with a JTabbedPane child component. The tabbed pane contains * A result view panel is a JPanel with a JTabbedPane child component that
* result viewers. * 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 * A result view panel provides an implementation of the setNode API of the the
* (DataResultViewerTable) and a thumbnail viewer (DataResultViewerThumbnail), * DataResult interface that pushes a given NetBeans Node into its child result
* plus zero to many additional DataResultViewers, since the DataResultViewer * viewers via the DataResultViewer.setNode API. The result viewers are
* interface is an extension point. * 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 * A result panel should be child components of top components that are explorer
* (DataResultTopComponent) that is normally docked into the upper right hand * manager providers. The parent top component is expected to expose a lookup
* side of the main window of the desktop application. * 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 * All result view panels push single node selections in the child result
* node selected in the tree view (DirectoryTreeTopComponent) that is normally * viewers to either the "main" content view (DataContentTopComponent) that is
* docked into the left hand side of the main window of the desktop application. * normally docked into the lower right hand side of the main application
* * window, or to a supplied custom content view (implements DataContent).
* 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.
*/ */
public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final int NO_TAB_SELECTED = -1; private static final int NO_TAB_SELECTED = -1;
private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName"); private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName");
private final List<DataResultViewer> resultViewers = new ArrayList<>(); private final boolean isMain;
private boolean isMain; private final List<DataResultViewer> resultViewers;
private ExplorerManager explorerManager; private final ExplorerManagerListener explorerManagerListener;
private ExplorerManagerNodeSelectionListener emNodeSelectionListener; private final RootNodeListener rootNodeListener;
private Node rootNode;
private final RootNodeListener rootNodeListener = new RootNodeListener();
private boolean listeningToTabbedPane;
private DataContent contentView; private DataContent contentView;
private ExplorerManager explorerManager;
private Node currentRootNode;
private boolean listeningToTabbedPane;
/** /**
* Constructs and opens a DataResultPanel with the given initial data, and * Creates and opens a Swing JPanel with a JTabbedPane child component that
* the default DataContent. * 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 title The title for the result view panel.
* @param pathText Descriptive text about the source of the nodes * @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
*
* @return A DataResultPanel instance.
*/
public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches) {
DataResultPanel resultPanel = new DataResultPanel(title, false);
createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel);
resultPanel.open();
return resultPanel;
}
/**
* Constructs and opens a DataResultPanel with the given initial data, and a
* custom DataContent.
*
* @param title The title for the panel.
* @param pathText 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.
*
* @return A DataResultPanel instance.
*/
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);
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.
*
* @param title The title for the panel.
* @param pathText 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.
*
* @return A DataResultPanel instance.
*/
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);
return resultPanel;
}
/**
* Executes code common to all of the DataSreultPanel factory methods.
*
* @param title The title for the panel.
* @param pathText Descriptive text about the source of the nodes
* displayed. * displayed.
* @param rootNode The new root node. * @param currentRootNode The current root (parent) node for the nodes
* @param totalMatches Cardinality of root node's children * displayed. May be changed by calling setNode.
* @param resultViewPanel A content view to use in place of the default * @param childNodeCount The cardinality of the root node's children.
* content view. *
* @return A result view panel.
*/ */
private static void createInstanceCommon(String title, String pathText, Node rootNode, int totalMatches, DataResultPanel resultViewPanel) { 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;
}
/**
* 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 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 viewers A collection of result viewers to use instead of
* the result viewers provided by the results viewer
* extension point.
*
* @return A result view panel.
*/
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;
}
/**
* 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 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 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 result view panel.
*/
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;
}
/**
* 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 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 customContentView A content view to use in place of the default
* content view.
*
* @return A result view panel.
*/
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.setTitle(title);
resultViewPanel.setName(title); resultViewPanel.setName(title);
resultViewPanel.setNumMatches(totalMatches); resultViewPanel.setNumberOfChildNodes(childNodeCount);
resultViewPanel.setNode(rootNode); resultViewPanel.setNode(currentRootNode);
resultViewPanel.setPath(pathText); 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 title The title of the result view panel.
* @param isMain True if the DataResultPanel being constructed is the "main" * @param isMain Whether or not the result view panel is the
* DataResultPanel. * "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) { DataResultPanel(String title, boolean isMain, Collection<DataResultViewer> viewers, DataContent customContentView) {
this(isMain, Lookup.getDefault().lookup(DataContent.class)); this.setTitle(title);
setTitle(title);
}
private DataResultPanel(boolean isMain, DataContent contentView) {
this.isMain = isMain; 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(); initComponents();
} }
/** /**
* Constructs a DataResultPanel with the a custom DataContent. * Gets the preferred identifier for this result view panel in the window
* * system.
* @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.
* *
* @return The preferred identifier. * @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 * Sets the title of this result view panel.
* 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.
* *
* @param title The title. * @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 * 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 @Override
public void setPath(String pathText) { public void setPath(String description) {
this.directoryTablePath.setText(pathText); 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) { public void addResultViewer(DataResultViewer resultViewer) {
resultViewers.add(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. * @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 * Sets the content view for this result view panel. Needs to be called
* call to open. * before the first call to open.
* *
* @param customContentView A content view to use in place of the default * @param customContentView A content view to use in place of the default
* content view. * 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. * when the top component is opened.
*/ */
public void open() { public void open() {
if (null == explorerManager) { /*
/* * The parent top component is expected to be an explorer manager
* Get an explorer manager to pass to the child result viewers. If * provider that exposes a lookup maintained by its explorer manager to
* the application components are put together as expected, this * the actions global context. The child result view panel will then
* will be an explorer manager owned by a parent top component, and * find the parent top component's explorer manager at runtime, so that
* placed by the top component in the look up that is proxied as the * it can act as an explorer manager provider for its child result
* action global context when the top component has focus. The * viewers. This connects the nodes displayed in the result viewers to
* sharing of this explorer manager enables the same child node * the actions global context.
* selections to be made in all of the result viewers. */
*/ if (this.explorerManager == null) {
explorerManager = ExplorerManager.find(this); this.explorerManager = ExplorerManager.find(this);
emNodeSelectionListener = new ExplorerManagerNodeSelectionListener(); this.explorerManager.addPropertyChangeListener(this.explorerManagerListener);
explorerManager.addPropertyChangeListener(emNodeSelectionListener);
} }
/* /*
* 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()) { if (this.resultViewerTabs.getTabCount() == 0) {
/* if (this.resultViewers.isEmpty()) {
* TODO (JIRA-2658): Fix the DataResultViewer extension point. When for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
* this is done, restore the implementation of DataResultViewerTable if (this.isMain) {
* and DataREsultViewerThumbnail as DataResultViewer service this.resultViewers.add(resultViewer);
* providers. } else {
*/ this.resultViewers.add(resultViewer.createInstance());
addResultViewer(new DataResultViewerTable(this.explorerManager)); }
//addResultViewer(new DataResultViewerThumbnail(this.explorerManager));
for (DataResultViewer factory : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
DataResultViewer resultViewer;
if (isMain) {
resultViewer = factory;
} else {
resultViewer = factory.createInstance();
} }
addResultViewer(resultViewer);
} }
} this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()));
if (isMain && null == rootNode) {
setNode(rootNode);
} }
this.setVisible(true); this.setVisible(true);
} }
/** /**
* Sets the root node for this panel. The child nodes of the root node will * Sets the current root node for this result view panel. The child nodes of
* be displayed in the result viewers. For the "main" panel, the root node * the current root node will be displayed in the child result viewers. For
* is the currently selected node in the tree view docked into the left side * the "main" panel, the root node is the currently selected node in the
* of the main application window. * 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 @Override
public void setNode(Node rootNode) { public void setNode(Node rootNode) {
if (this.rootNode != null) { if (this.currentRootNode != null) {
this.rootNode.removeNodeListener(rootNodeListener); this.currentRootNode.removeNodeListener(rootNodeListener);
} }
/* /*
@ -333,53 +366,54 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* construction. * construction.
*/ */
if (listeningToTabbedPane == false) { if (listeningToTabbedPane == false) {
dataResultTabbedPanel.addChangeListener(this); resultViewerTabs.addChangeListener(this);
listeningToTabbedPane = true; listeningToTabbedPane = true;
} }
this.rootNode = rootNode; this.currentRootNode = rootNode;
if (this.rootNode != null) { if (this.currentRootNode != null) {
rootNodeListener.reset(); rootNodeListener.reset();
this.rootNode.addNodeListener(rootNodeListener); this.currentRootNode.addNodeListener(rootNodeListener);
} }
resetTabs(this.rootNode); this.resultViewers.forEach((viewer) -> {
setupTabs(this.rootNode); viewer.resetComponent();
});
setupTabs(this.currentRootNode);
if (null != this.rootNode) { if (this.currentRootNode != null) {
int childrenCount = this.rootNode.getChildren().getNodesCount(); int childrenCount = this.currentRootNode.getChildren().getNodesCount();
this.numberMatchLabel.setText(Integer.toString(childrenCount)); 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 * Gets the root node of this result view panel. For the "main" panel, the
* the currently selected node in the tree view docked into the left side of * root node is the currently selected node in the application tree view
* the main application window. * docked into the left side of the main application window.
* *
* @return The root node. * @return The root node.
*/ */
public Node getRootNode() { 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) { public void setNumberOfChildNodes(Integer numberOfChildNodes) {
if (this.numberMatchLabel != null) { this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes));
this.numberMatchLabel.setText(Integer.toString(numberOfChildNodes));
}
} }
/** /**
* Sets the children of the root node that should be currently selected in * Selects the given child nodes of the root node in this panel's result
* this panel's result viewers. * viewers.
* *
* @param selectedNodes The nodes to be selected. * @param selectedNodes The child nodes to be selected.
*/ */
public void setSelectedNodes(Node[] selectedNodes) { public void setSelectedNodes(Node[] selectedNodes) {
this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(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 * Enable or disable the result viewer tabs based on whether or not the
* corresponding results viewer supports display of the selected node. * 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)) { if (resultViewers.get(i).isSupported(selectedNode)) {
dataResultTabbedPanel.setEnabledAt(i, true); resultViewerTabs.setEnabledAt(i, true);
} else { } 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(); NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) { if (null != selectedChildInfo) {
for (int i = 0; i < resultViewers.size(); ++i) { 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; tabToSelect = i;
} }
} }
} }
}; };
if (NO_TAB_SELECTED == tabToSelect) { if (tabToSelect == NO_TAB_SELECTED) {
tabToSelect = dataResultTabbedPanel.getSelectedIndex(); tabToSelect = resultViewerTabs.getSelectedIndex();
if ((NO_TAB_SELECTED == tabToSelect) || (!dataResultTabbedPanel.isEnabledAt(tabToSelect))) { if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) {
for (int i = 0; i < dataResultTabbedPanel.getTabCount(); ++i) { for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) {
if (dataResultTabbedPanel.isEnabledAt(i)) { if (resultViewerTabs.isEnabledAt(i)) {
tabToSelect = i; tabToSelect = i;
break; 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 * If there is a tab to select, do so, and push the selected node to the
* the corresponding result viewer. * corresponding result viewer.
*/ */
if (NO_TAB_SELECTED != tabToSelect) { if (tabToSelect != NO_TAB_SELECTED) {
dataResultTabbedPanel.setSelectedIndex(tabToSelect); resultViewerTabs.setSelectedIndex(tabToSelect);
resultViewers.get(tabToSelect).setNode(selectedNode); 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 * Responds to a tab selection changed event by setting the root node of the
* corresponding result viewer. * corresponding result viewer.
@ -465,57 +487,34 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
public void stateChanged(ChangeEvent event) { public void stateChanged(ChangeEvent event) {
JTabbedPane pane = (JTabbedPane) event.getSource(); JTabbedPane pane = (JTabbedPane) event.getSource();
int currentTab = pane.getSelectedIndex(); int currentTab = pane.getSelectedIndex();
if (-1 != currentTab) { if (currentTab != DataResultPanel.NO_TAB_SELECTED) {
DataResultViewer currentViewer = this.resultViewers.get(currentTab); DataResultViewer currentViewer = this.resultViewers.get(currentTab);
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
currentViewer.setNode(rootNode); currentViewer.setNode(currentRootNode);
} finally { } 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 * Closes this reult view panel. Intended to be called by the parent top
* 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
* component when it is closed. * component when it is closed.
*/ */
void close() { void close() {
if (null != explorerManager && null != emNodeSelectionListener) { if (explorerManager != null && explorerManagerListener != null) {
explorerManager.removePropertyChangeListener(emNodeSelectionListener); explorerManager.removePropertyChangeListener(explorerManagerListener);
explorerManager = null; explorerManager = null;
} }
this.resultViewers.forEach((viewer) -> viewer.setNode(null)); this.resultViewers.forEach((viewer) -> viewer.setNode(null));
if (!this.isMain) { if (!this.isMain) { // RJCTODO: What?
this.resultViewers.forEach(DataResultViewer::clearComponent); this.resultViewers.forEach(DataResultViewer::clearComponent);
this.directoryTablePath.removeAll(); this.descriptionLabel.removeAll();
this.directoryTablePath = null; this.numberOfChildNodesLabel.removeAll();
this.numberMatchLabel.removeAll();
this.numberMatchLabel = null;
this.matchLabel.removeAll(); this.matchLabel.removeAll();
this.matchLabel = null;
this.setLayout(null); this.setLayout(null);
this.removeAll(); this.removeAll();
this.setVisible(false); this.setVisible(false);
@ -525,50 +524,37 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
@Override @Override
public ExplorerManager getExplorerManager() { public ExplorerManager getExplorerManager() {
return explorerManager; 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 @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
try { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) {
Case.getOpenCase(); /*
} catch (NoCurrentCaseException ex) { * Pass a single node selection in a result viewer to the
return; * 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
* Only interested in node selection events. * single node.
*/ */
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { Node[] selectedNodes = explorerManager.getSelectedNodes();
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (1 == selectedNodes.length) {
try { contentView.setNode(selectedNodes[0]);
if (contentView != null) { } else {
Node[] selectedNodes = explorerManager.getSelectedNodes(); contentView.setNode(null);
/*
* 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 * Responds to changes in the root node due to asynchronous child node
* creation. * creation.
*/ */
// RJCTODO: Why do we need this?
private class RootNodeListener implements NodeListener { private class RootNodeListener implements NodeListener {
private volatile boolean waitingForData = true; private volatile boolean waitingForData = true;
@ -638,8 +625,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* *
*/ */
private void updateMatches() { private void updateMatches() {
if (rootNode != null && rootNode.getChildren() != null) { if (currentRootNode != null && currentRootNode.getChildren() != null) {
setNumMatches(rootNode.getChildren().getNodesCount()); 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 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
directoryTablePath = new javax.swing.JLabel(); descriptionLabel = new javax.swing.JLabel();
numberMatchLabel = new javax.swing.JLabel(); numberOfChildNodesLabel = new javax.swing.JLabel();
matchLabel = 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)); setMinimumSize(new java.awt.Dimension(0, 5));
setPreferredSize(new java.awt.Dimension(5, 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 org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N
directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14)); 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 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); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(numberMatchLabel) .addComponent(numberOfChildNodesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(matchLabel)) .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.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(numberMatchLabel) .addComponent(numberOfChildNodesLabel)
.addComponent(matchLabel)) .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) .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 }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTabbedPane dataResultTabbedPanel; private javax.swing.JLabel descriptionLabel;
private javax.swing.JLabel directoryTablePath;
private javax.swing.JLabel matchLabel; 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 // 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,10 +28,10 @@
<SubComponents> <SubComponents>
<Component class="org.sleuthkit.autopsy.corecomponents.DataResultPanel" name="dataResultPanelLocal"> <Component class="org.sleuthkit.autopsy.corecomponents.DataResultPanel" name="dataResultPanelLocal">
<AuxValues> <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_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues> </AuxValues>
</Component> </Component>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
@ -39,161 +40,220 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger; 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. * Result view top components are typically docked into the upper right hand
* Others can also create an additional result viewer tc using one of the * side of the main application window (editor mode), and are linked to the
* factory methods, that can be: * 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 * The "main" result view top component receives its current node as a selection
* different, custom mode, - linked to a custom content viewer that responds to * from the case tree view in the top component (DirectoryTreeYopComponent)
* selections from this top component. * docked into the left hand side of the main application window.
* *
* For embedding custom data result in other top components window, use * Result view top components are explorer manager providers to connect the
* DataResultPanel component instead, since we cannot nest top components. * lookups of the nodes displayed in the NetBeans explorer views of the result
* * viewers to the actions global context.
* Encapsulates the internal DataResultPanel and delegates to it.
*
* Implements DataResult interface by delegating to the encapsulated
* DataResultPanel.
*/ */
@RetainLocation("editor") @RetainLocation("editor")
public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider {
private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName()); 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 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, * @param title The title for the top component, appears on the top
* there can be only 1 main result viewer * component's tab.
* @param title title of the data result window * @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) { public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount) {
associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null);
this.dataResultPanel = new DataResultPanel(title, isMain); initInstance(description, node, childNodeCount, resultViewTopComponent);
initComponents(); return resultViewTopComponent;
customizeComponent(isMain, title);
} }
/** /**
* Create a new, custom data result top component, in addition to the * Creates a result view top component that provides multiple views of the
* application main one * 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 * @param title The title for the top component, appears on the top
* used as title * component's tab.
* @param mode custom mode to dock into * @param description Descriptive text about the node displayed, appears
* @param customContentViewer custom content viewer to send selection events * on the top component's tab
* to * @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) {
associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, viewers, null);
this.customModeName = mode; initInstance(description, node, childNodeCount, resultViewTopComponent);
dataResultPanel = new DataResultPanel(name, customContentViewer); return resultViewTopComponent;
initComponents();
customizeComponent(isMain, name);
} }
private void customizeComponent(boolean isMain, String title) { /**
this.isMain = isMain; * Creates a partially initialized result view top component that provides
this.customModeName = null; * 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
setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); * application window (editor mode) and will be linked to the content view
* in the lower right hand side of the main application window (output
setTitle(title); // set the title * mode). Its result viewers are provided by the result viewer extension
setName(title); * point (service providers that implement DataResultViewer).
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS *
getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS * IMPORTANT: Initialization MUST be completed by calling initInstance.
*
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI * @param title The title for the result view top component, appears on the
putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true); * top component's tab.
putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true); *
* @return The partially initialized result view top component.
activeComponentIds.add(title); */
public static DataResultTopComponent createInstance(String title) {
DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null);
return resultViewTopComponent;
} }
/** /**
* Initialize previously created tc instance with additional data * Initializes a partially initialized result view top component.
* *
* @param pathText * @param description Descriptive text about the node displayed,
* @param givenNode * appears on the top component's tab
* @param totalMatches * @param node The node to display.
* @param newDataResult previously created with createInstance() * @param childNodeCount The cardinality of the node's children.
* uninitialized instance * @param resultViewTopComponent The partially initialized result view top
* component.
*/ */
public static void initInstance(String pathText, Node givenNode, int totalMatches, DataResultTopComponent newDataResult) { public static void initInstance(String description, Node node, int childNodeCount, DataResultTopComponent resultViewTopComponent) {
newDataResult.setNumMatches(totalMatches); resultViewTopComponent.setNumberOfChildNodes(childNodeCount);
resultViewTopComponent.open();
newDataResult.open(); // open it first so the component can be initialized resultViewTopComponent.setNode(node);
resultViewTopComponent.setPath(description);
// set the tree table view resultViewTopComponent.requestActive();
newDataResult.setNode(givenNode);
newDataResult.setPath(pathText);
newDataResult.requestActive();
} }
/** /**
* Creates a new non-default DataResult component and initializes it * 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 Title of the component window * @param title The title for the top component, appears
* @param pathText Descriptive text about the source of the nodes * on the top component's tab.
* displayed * @param mode The NetBeans Window system mode into which
* @param givenNode The new root node * this top component should be docked.
* @param totalMatches Cardinality of root node's children * @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 a new, not default, initialized DataResultTopComponent instance * @return The result view top component.
*/ */
public static DataResultTopComponent createInstance(String title, String pathText, Node givenNode, int totalMatches) { public static DataResultTopComponent createInstance(String title, String mode, String description, Node node, int childNodeCount, DataContentTopComponent contentViewTopComponent) {
DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); DataResultTopComponent newDataResult = new DataResultTopComponent(false, title, mode, Collections.emptyList(), contentViewTopComponent);
initInstance(description, node, childNodeCount, newDataResult);
initInstance(pathText, givenNode, totalMatches, newDataResult);
return newDataResult; return newDataResult;
} }
/** /**
* Creates a new non-default DataResult component linked with a custom data * Creates a result view top component that provides multiple views of the
* content, and initializes it. * 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 Title of the component window * @param title The title for the top component, appears on the top
* @param mode custom mode to dock this custom TopComponent to * component's tab.
* @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) { public DataResultTopComponent(String title) {
DataResultTopComponent newDataResult = new DataResultTopComponent(title, mode, dataContentWindow); this(true, title, null, Collections.emptyList(), null);
initInstance(pathText, givenNode, totalMatches, newDataResult);
return newDataResult;
} }
/** /**
* Creates a new non-default DataResult component. You probably want to use * Constructs a result view top component that provides multiple views of
* initInstance after it * the application data represented by a NetBeans Node.
* *
* @param title * @param isMain Whether or not this is the "main" result
* * view top component.
* @return a new, not default, not fully initialized DataResultTopComponent * @param title The title for the top component, appears
* instance * 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,
*/ */
public static DataResultTopComponent createInstance(String title) { private DataResultTopComponent(boolean isMain, String title, String mode, Collection<DataResultViewer> viewers, DataContentTopComponent contentViewTopComponent) {
final DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); this.isMain = isMain;
this.explorerManager = new ExplorerManager();
associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
this.customModeName = mode;
this.dataResultPanel = new DataResultPanel(title, isMain, viewers, contentViewTopComponent);
initComponents();
customizeComponent(title);
}
return newDataResult; 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);
putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true);
putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true);
activeComponentIds.add(title);
} }
@Override @Override
@ -202,38 +262,15 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
} }
/** /**
* 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() { public static List<String> getActiveComponentIds() {
return new ArrayList<>(activeComponentIds); 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 @Override
public int getPersistenceType() { public int getPersistenceType() {
if (customModeName == null) { if (customModeName == null) {
@ -245,16 +282,6 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override @Override
public void open() { 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) { if (customModeName != null) {
Mode mode = WindowManager.getDefault().findMode(customModeName); Mode mode = WindowManager.getDefault().findMode(customModeName);
if (mode != null) { 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 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 @Override
@ -290,7 +323,7 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
} else { } else {
selectedNode = null; selectedNode = null;
} }
/* /*
* If the selected node of the content viewer is different than that of * If the selected node of the content viewer is different than that of
* the result viewer, the content viewer needs to be updated. Otherwise, * the result viewer, the content viewer needs to be updated. Otherwise,
@ -343,31 +376,15 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override @Override
public boolean canClose() { 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; Case openCase;
try { try {
openCase = Case.getOpenCase(); openCase = Case.getOpenCase();
} catch (NoCurrentCaseException ex) { } catch (NoCurrentCaseException unused) {
return true; return true;
} }
return (!this.isMain) || openCase.hasData() == false; 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) { public void setSelectedNodes(Node[] selected) {
dataResultPanel.setSelectedNodes(selected); dataResultPanel.setSelectedNodes(selected);
} }
@ -376,7 +393,74 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
return dataResultPanel.getRootNode(); 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 * Autopsy Forensic Browser
* *
* Copyright 2011-2017 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -60,155 +60,153 @@ import org.openide.nodes.Node;
import org.openide.nodes.Node.Property; import org.openide.nodes.Node.Property;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbPreferences; import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; 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, * Instances of this class should use the explorer manager of an ancestor top
* restore implementation of DataResultViewerTable as a DataResultViewer service * component to connect the lookups of the nodes displayed in the OutlineView to
* provider. * 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) @ServiceProvider(service = DataResultViewer.class)
public class DataResultViewerTable extends AbstractDataResultViewer { public final class DataResultViewerTable extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName());
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
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 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 * @param explorerManager The explorer manager of the ancestor top
* * component.
* 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
*/ */
public DataResultViewerTable(ExplorerManager explorerManager) { public DataResultViewerTable(ExplorerManager explorerManager) {
this(explorerManager, Bundle.DataResultViewerTable_title()); this(explorerManager, Bundle.DataResultViewerTable_title());
} }
/** /**
* Creates a DataResultViewerTable object that is compatible with node * Constructs a tabular result viewer that displays the children of a given
* multiple selection actions, and a custom title. * 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 explorerManager The explorer manager of the ancestor top
* @param title The custom title. * component.
* @param title The title.
*/ */
public DataResultViewerTable(ExplorerManager explorerManager, String title) { public DataResultViewerTable(ExplorerManager explorerManager, String title) {
super(explorerManager); super(explorerManager);
this.title = title; this.title = title;
this.columnMap = new HashMap<>();
this.propertiesMap = new TreeMap<>();
/*
* Execute the code generated by the GUI builder.
*/
initComponents(); initComponents();
/*
* Configure the child OutlineView (explorer view) component.
*/
outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
outline = outlineView.getOutline(); outline = outlineView.getOutline();
outline.setRowSelectionAllowed(true); outline.setRowSelectionAllowed(true);
outline.setColumnSelectionAllowed(true); outline.setColumnSelectionAllowed(true);
outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
outline.setRootVisible(false); // don't show the root node outline.setRootVisible(false);
outline.setDragEnabled(false); outline.setDragEnabled(false);
outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer());
// add a listener so that when columns are moved, the new order is stored /*
tableListener = new TableListener(); * Add a table listener to the child OutlineView (explorer view) to
outline.getColumnModel().addColumnModelListener(tableListener); * persist the order of the table columns when a column is moved.
*/
// the listener also moves columns back if user tries to move the first column out of place outlineViewListener = new TableListener();
outline.getTableHeader().addMouseListener(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);
} }
/** /**
* Creates a DataResultViewerTable object that is NOT compatible with node * Creates a new instance of a tabular result viewer that displays the
* multiple selection actions. * 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
public DataResultViewerTable() { * class in the "main" results view of the application, while using distinct
this(new ExplorerManager(),Bundle.DataResultViewerTable_title()); * instances in other places in the UI.
}
/**
* Expand node
* *
* @param n Node to expand * @return A new instance of a tabular result viewer,
*/ */
@Override @Override
public void expandNode(Node n) { public DataResultViewer createInstance() {
super.expandNode(n); return new DataResultViewerTable();
}
outlineView.expandNode(n); /**
* Gets the title of this tabular result viewer.
*/
@Override
@NbBundle.Messages("DataResultViewerTable.title=Table")
public String getTitle() {
return title;
} }
/** /**
* This method is called from within the constructor to initialize the form. * Indicates whether a given node is supported as a root node for this
* WARNING: Do NOT modify this code. The content of this method is always * tabular viewer.
* regenerated by the Form Editor. *
* @param candidateRootNode The candidate root node.
*
* @return
*/ */
@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 @Override
public boolean isSupported(Node selectedNode) { public boolean isSupported(Node candidateRootNode) {
return true; 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 @Override
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @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, * The quick filter must be reset because when determining column width,
* ETable.getRowCount is called, and the documentation states that quick * ETable.getRowCount is called, and the documentation states that quick
@ -217,30 +215,30 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* model." * model."
*/ */
outline.unsetQuickFilter(); outline.unsetQuickFilter();
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
boolean hasChildren = false; /*
if (selectedNode != null) { * If the given node is not null and has children, set it as the
// @@@ This just did a DB round trip to get the count and the results were not saved... * root context of the child OutlineView, otherwise make an
hasChildren = selectedNode.getChildren().getNodesCount() > 0; * "empty"node the root context.
} *
* IMPORTANT NOTE: This is the first of many times where a
if (hasChildren) { * getChildren call on the current root node causes all of the
currentRoot = selectedNode; * children of the root node to be created and defeats lazy child
em.setRootContext(currentRoot); * 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(); setupTable();
} else { } else {
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node this.getExplorerManager().setRootContext(emptyNode);
outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
outlineViewListener.listenToVisibilityChanges(false);
/* outlineView.setPropertyColumns();
* 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
} }
} finally { } finally {
this.setCursor(null); this.setCursor(null);
@ -248,16 +246,17 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
} }
/** /**
* Create Column Headers based on the Content represented by the Nodes in * Sets up the Outline view of this tabular result viewer by creating
* the table. Load persisted column order, sorting and visibility. * column headers based on the children of the current root node. The
* persisted column order, sorting and visibility is used.
*/ */
private void setupTable() { private void setupTable() {
/* /*
* Since we are modifying the columns, we don't want to listen to * 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. * 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 * OutlineView makes the first column be the result of
* node.getDisplayName with the icon. This duplicates our first column, * node.getDisplayName with the icon. This duplicates our first column,
* which is the file name, etc. So, pop that property off the list, but * which is the file name, etc. So, pop that property off the list, but
@ -289,7 +288,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
setColumnWidths(); 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(); loadColumnSorting();
/* /*
@ -301,7 +303,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/ */
populateColumnMap(); 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(); loadColumnVisibility();
/* /*
@ -309,32 +314,36 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* it. * it.
*/ */
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo(); NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) { if (null != selectedChildInfo) {
Node[] childNodes = currentRoot.getChildren().getNodes(true); Node[] childNodes = rootNode.getChildren().getNodes(true);
for (int i = 0; i < childNodes.length; ++i) { for (int i = 0; i < childNodes.length; ++i) {
Node childNode = childNodes[i]; Node childNode = childNodes[i];
if (selectedChildInfo.matches(childNode)) { if (selectedChildInfo.matches(childNode)) {
try { try {
em.setSelectedNodes(new Node[]{childNode}); this.getExplorerManager().setSelectedNodes(new Node[]{childNode});
} catch (PropertyVetoException ex) { } catch (PropertyVetoException ex) {
LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex); LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
} }
break; 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. * loading/storing the visibility info.
*/ */
private void populateColumnMap() { 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() { private void setColumnWidths() {
if (currentRoot.getChildren().getNodesCount() != 0) { if (rootNode.getChildren().getNodesCount() != 0) {
final Graphics graphics = outlineView.getGraphics(); final Graphics graphics = outlineView.getGraphics();
if (graphics != null) { if (graphics != null) {
final FontMetrics metrics = graphics.getFontMetrics(); 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) { synchronized private void assignColumns(List<Property<?>> props) {
// Get the columns setup with respect to names and sortability
String[] propStrings = new String[props.size() * 2]; String[] propStrings = new String[props.size() * 2];
for (int i = 0; i < props.size(); i++) { for (int i = 0; i < props.size(); i++) {
final Property<?> prop = props.get(i); final Property<?> prop = props.get(i);
@ -402,29 +418,25 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
propStrings[2 * i] = prop.getName(); propStrings[2 * i] = prop.getName();
propStrings[2 * i + 1] = prop.getDisplayName(); propStrings[2 * i + 1] = prop.getDisplayName();
} }
outlineView.setPropertyColumns(propStrings); 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() { private synchronized void storeColumnVisibility() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) currentRoot; TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
//store hidden state
for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
String columnName = entry.getKey(); String columnName = entry.getKey();
final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName); final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
final TableColumn column = entry.getValue(); final TableColumn column = entry.getValue();
boolean columnHidden = columnModel.isColumnHidden(column); boolean columnHidden = columnModel.isColumnHidden(column);
if (columnHidden) { if (columnHidden) {
preferences.putBoolean(columnHiddenKey, true); 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() { private synchronized void storeColumnOrder() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) currentRoot; TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
// Store the current order of the columns into settings // Store the current order of the columns into settings
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey()); 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() { private synchronized void storeColumnSorting() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
final TableFilterNode tfn = ((TableFilterNode) currentRoot); final TableFilterNode tfn = ((TableFilterNode) rootNode);
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
ETableColumn etc = entry.getValue(); ETableColumn etc = entry.getValue();
String columnName = entry.getKey(); String columnName = entry.getKey();
//store sort rank and order //store sort rank and order
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName); final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(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 * Reads and applies the column sorting information persisted to the
* preferences file. Must be called after loadColumnOrder() since it depends * preferences file. Must be called after loadColumnOrder, since it depends
* on propertiesMap being initialized, and after assignColumns since it * on the properties map being initialized, and after assignColumns, since
* cannot set the sort on columns that have not been added to the table. * it cannot set the sort on columns that have not been added to the table.
*/ */
private synchronized void loadColumnSorting() { private synchronized void loadColumnSorting() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (rootNode instanceof TableFilterNode) {
if (currentRoot instanceof TableFilterNode) { final TableFilterNode tfn = (TableFilterNode) rootNode;
final TableFilterNode tfn = (TableFilterNode) currentRoot;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
//organize property sorting information, sorted by rank //organize property sorting information, sorted by rank
TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
propertiesMap.entrySet().stream().forEach(entry -> { propertiesMap.entrySet().stream().forEach(entry -> {
final String propName = entry.getValue().getName(); final String propName = entry.getValue().getName();
//if the sort rank is undefined, it will be defaulted to 0 => unsorted. //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0); Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
//default to true => ascending //default to true => ascending
Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true); Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
}); });
//apply sort information in rank order. //apply sort information in rank order.
sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); 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() { private synchronized void loadColumnVisibility() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (rootNode instanceof TableFilterNode) {
if (currentRoot instanceof TableFilterNode) {
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final TableFilterNode tfn = ((TableFilterNode) rootNode);
final TableFilterNode tfn = ((TableFilterNode) currentRoot);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
final String propName = entry.getValue().getName(); 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 * 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. * order.
* *
* @return a List<Node.Property<?>> of the properties in the persisted * @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() { 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 node is not table filter node, use default order for columns
if (!(currentRoot instanceof TableFilterNode)) { if (!(rootNode instanceof TableFilterNode)) {
return props; return props;
} }
final TableFilterNode tfn = ((TableFilterNode) currentRoot); final TableFilterNode tfn = ((TableFilterNode) rootNode);
propertiesMap.clear(); propertiesMap.clear();
/* /*
@ -590,24 +597,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
return new ArrayList<>(propertiesMap.values()); return new ArrayList<>(propertiesMap.values());
} }
@Override /**
@NbBundle.Messages("DataResultViewerTable.title=Table") * Frees the resources that have been allocated by this tabular results
public String getTitle() { * viewer, in preparation for permanently disposing of it.
return title; */
}
@Override
public DataResultViewer createInstance() {
return new DataResultViewerTable();
}
@Override @Override
public void clearComponent() { public void clearComponent() {
this.outlineView.removeAll(); this.outlineView.removeAll();
this.outlineView = null; this.outlineView = null;
super.clearComponent(); super.clearComponent();
} }
/** /**
@ -778,8 +776,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
// only override the color if a node is not selected // only override the color if a node is not selected
if (currentRoot != null && !isSelected) { if (rootNode != null && !isSelected) {
Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row));
boolean tagFound = false; boolean tagFound = false;
if (node != null) { if (node != null) {
Node.PropertySet[] propSets = node.getPropertySets(); 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 the node does have associated tags, set its background color
if (tagFound) { if (tagFound) {
component.setBackground(TAGGED_COLOR); component.setBackground(TAGGED_ROW_COLOR);
} }
} }
return component; 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 * Autopsy Forensic Browser
* *
* Copyright 2011-2017 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -50,6 +50,7 @@ import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent; import org.openide.nodes.NodeReorderEvent;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbPreferences; import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import static org.sleuthkit.autopsy.corecomponents.Bundle.*;
import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion;
@ -59,64 +60,67 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; 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 * Instances of this class should use the explorer manager of an ancestor top
* (currently) 200 images at a time. This works whether or not the underlying * component to connect the lookups of the nodes displayed in the IconView to
* content nodes are being lazy loaded or not. * 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
* TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, * ancestor top component's explorer manager at runtime.
* restore implementation of DataResultViewerTable as a DataResultViewer service
* provider.
*/ */
//@ServiceProvider(service = DataResultViewer.class) @ServiceProvider(service = DataResultViewer.class)
final class DataResultViewerThumbnail extends AbstractDataResultViewer { public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
private int curPage;
private int totalPages;
private int curPageImages;
private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
private final PageUpdater pageUpdater = new PageUpdater(); private final PageUpdater pageUpdater = new PageUpdater();
private TableFilterNode tfn; private TableFilterNode rootNode;
private ThumbnailViewChildren tvc; 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, * Constructs a thumbnail result viewer, with paging support, that displays
* that is compatible with node multiple selection actions. * 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) { @NbBundle.Messages({
super(explorerManager); "DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
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",
"DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails",
"DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails" "DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails"
}) })
private void initialize() { public DataResultViewerThumbnail(ExplorerManager explorerManager) {
super(explorerManager);
initComponents(); initComponents();
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(),
Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()}));
Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()}));
thumbnailSizeComboBox.setSelectedIndex(1); thumbnailSizeComboBox.setSelectedIndex(1);
curPage = -1; currentPage = -1;
totalPages = 0; totalPages = 0;
curPageImages = 0; currentPageImages = 0;
} }
/** /**
@ -297,24 +301,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
if (thumbSize != newIconSize) { if (thumbSize != newIconSize) {
thumbSize = newIconSize; thumbSize = newIconSize;
Node root = em.getRootContext(); Node root = this.getExplorerManager().getRootContext();
((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize);
// Temporarily set the explored context to the root, instead of a child node. // 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 // 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 // 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 // will cause the entire view to update completely. After this we
// immediately set the node back to the current child by calling switchPage(). // immediately set the node back to the current child by calling switchPage().
em.setExploredContext(root); this.getExplorerManager().setExploredContext(root);
switchPage(); switchPage();
} }
}//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed }//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed
private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed
List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100); List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100);
SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn)); SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(rootNode));
DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle()); DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle());
Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
createDialog.setVisible(true); createDialog.setVisible(true);
@ -335,8 +337,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
Node.Property<?> prop = childProperties.get(i); Node.Property<?> prop = childProperties.get(i);
String propName = prop.getName(); String propName = prop.getName();
SortCriterion criterion = criteriaMap.get(prop); SortCriterion criterion = criteriaMap.get(prop);
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName); final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(rootNode, propName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName); final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(rootNode, propName);
if (criterion != null) { if (criterion != null) {
preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING); preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING);
@ -346,7 +348,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
preferences.remove(columnSortRankKey); 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 }//GEN-LAST:event_sortButtonActionPerformed
@ -379,28 +381,31 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override @Override
public void setNode(Node givenNode) { public void setNode(Node givenNode) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (tvc != null) { if (selectionListener == null) {
tvc.cancelLoadingThumbnails(); this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup
}
if (rootNodeChildren != null) {
rootNodeChildren.cancelLoadingThumbnails();
} }
try { try {
if (givenNode != null) { if (givenNode != null) {
tfn = (TableFilterNode) givenNode; rootNode = (TableFilterNode) givenNode;
/* /*
* Wrap the given node in a ThumbnailViewChildren that will * Wrap the given node in a ThumbnailViewChildren that will
* produce ThumbnailPageNodes with ThumbnailViewNode children * produce ThumbnailPageNodes with ThumbnailViewNode children
* from the child nodes of the given node. * from the child nodes of the given node.
*/ */
tvc = new ThumbnailViewChildren(givenNode,thumbSize); rootNodeChildren = new ThumbnailViewChildren(givenNode, thumbSize);
final Node root = new AbstractNode(tvc); final Node root = new AbstractNode(rootNodeChildren);
pageUpdater.setRoot(root); pageUpdater.setRoot(root);
root.addNodeListener(pageUpdater); root.addNodeListener(pageUpdater);
em.setRootContext(root); this.getExplorerManager().setRootContext(root);
} else { } else {
tfn = null; rootNode = null;
tvc = null; rootNodeChildren = null;
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); this.getExplorerManager().setRootContext(emptyNode);
iconView.setBackground(Color.BLACK); iconView.setBackground(Color.BLACK);
} }
} finally { } finally {
@ -422,8 +427,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
public void resetComponent() { public void resetComponent() {
super.resetComponent(); super.resetComponent();
this.totalPages = 0; this.totalPages = 0;
this.curPage = -1; this.currentPage = -1;
curPageImages = 0; currentPageImages = 0;
updateControls(); updateControls();
} }
@ -435,15 +440,15 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
} }
private void nextPage() { private void nextPage() {
if (curPage < totalPages) { if (currentPage < totalPages) {
curPage++; currentPage++;
switchPage(); switchPage();
} }
} }
private void previousPage() { private void previousPage() {
if (curPage > 1) { if (currentPage > 1) {
curPage--; currentPage--;
switchPage(); switchPage();
} }
} }
@ -465,7 +470,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
return; return;
} }
curPage = newPage; currentPage = newPage;
switchPage(); switchPage();
} }
@ -488,10 +493,11 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs")); NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs"));
progress.start(); progress.start();
progress.switchToIndeterminate(); progress.switchToIndeterminate();
Node root = em.getRootContext(); ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager();
Node pageNode = root.getChildren().getNodeAt(curPage - 1); Node root = explorerManager.getRootContext();
em.setExploredContext(pageNode); Node pageNode = root.getChildren().getNodeAt(currentPage - 1);
curPageImages = pageNode.getChildren().getNodesCount(); explorerManager.setExploredContext(pageNode);
currentPageImages = pageNode.getChildren().getNodesCount();
return null; return null;
} }
@ -504,8 +510,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
try { try {
get(); get();
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
NotifyDescriptor d = NotifyDescriptor d
new NotifyDescriptor.Message( = new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg",
ex.getMessage()), ex.getMessage()),
NotifyDescriptor.ERROR_MESSAGE); NotifyDescriptor.ERROR_MESSAGE);
@ -535,20 +541,19 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
sortLabel.setText(DataResultViewerThumbnail_sortLabel_text()); sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
} else { } else {
pageNumLabel.setText( pageNumLabel.setText(NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal",
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", Integer.toString(currentPage), Integer.toString(totalPages)));
Integer.toString(curPage), Integer.toString(totalPages))); final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
imagesRangeLabel.setText(imagesFrom + "-" + imagesTo); imagesRangeLabel.setText(imagesFrom + "-" + imagesTo);
pageNextButton.setEnabled(!(curPage == totalPages)); pageNextButton.setEnabled(!(currentPage == totalPages));
pagePrevButton.setEnabled(!(curPage == 1)); pagePrevButton.setEnabled(!(currentPage == 1));
goToPageField.setEnabled(totalPages > 1); goToPageField.setEnabled(totalPages > 1);
sortButton.setEnabled(true); sortButton.setEnabled(true);
thumbnailSizeComboBox.setEnabled(true); thumbnailSizeComboBox.setEnabled(true);
if (tfn != null) { if (rootNode != null) {
String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream() String sortString = ResultViewerPersistence.loadSortCriteria(rootNode).stream()
.map(SortCriterion::toString) .map(SortCriterion::toString)
.collect(Collectors.joining(" ")); .collect(Collectors.joining(" "));
sortString = StringUtils.defaultIfBlank(sortString, "---"); sortString = StringUtils.defaultIfBlank(sortString, "---");
@ -579,30 +584,30 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
totalPages = root.getChildren().getNodesCount(); totalPages = root.getChildren().getNodesCount();
if (totalPages == 0) { if (totalPages == 0) {
curPage = -1; currentPage = -1;
updateControls(); updateControls();
return; return;
} }
if (curPage == -1 || curPage > totalPages) { if (currentPage == -1 || currentPage > totalPages) {
curPage = 1; currentPage = 1;
} }
//force load the curPage node //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}); //em.setSelectedNodes(new Node[]{pageNode});
if (pageNode != null) { if (pageNode != null) {
pageNode.addNodeListener(new NodeListener() { pageNode.addNodeListener(new NodeListener() {
@Override @Override
public void childrenAdded(NodeMemberEvent nme) { public void childrenAdded(NodeMemberEvent nme) {
curPageImages = pageNode.getChildren().getNodesCount(); currentPageImages = pageNode.getChildren().getNodesCount();
updateControls(); updateControls();
} }
@Override @Override
public void childrenRemoved(NodeMemberEvent nme) { public void childrenRemoved(NodeMemberEvent nme) {
curPageImages = 0; currentPageImages = 0;
updateControls(); updateControls();
} }
@ -619,7 +624,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
} }
}); });
em.setExploredContext(pageNode); DataResultViewerThumbnail.this.getExplorerManager().setExploredContext(pageNode);
} }
updateControls(); updateControls();
@ -628,7 +633,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override @Override
public void childrenRemoved(NodeMemberEvent nme) { public void childrenRemoved(NodeMemberEvent nme) {
totalPages = 0; totalPages = 0;
curPage = -1; currentPage = -1;
updateControls(); updateControls();
} }
@ -641,14 +646,14 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
} }
} }
private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { private class NodeSelectionListener implements PropertyChangeListener {
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
Node[] selectedNodes = em.getSelectedNodes(); Node[] selectedNodes = DataResultViewerThumbnail.this.getExplorerManager().getSelectedNodes();
if (selectedNodes.length == 1) { if (selectedNodes.length == 1) {
AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class); AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
if (af == null) { if (af == null) {

View File

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

View File

@ -358,7 +358,7 @@ public class PlatformUtil {
File[] files = dev.listFiles(); File[] files = dev.listFiles();
for (File f : files) { for (File f : files) {
String name = f.getName(); 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 String path = "/dev/" + name; //NON-NLS
if (canReadDrive(path)) { if (canReadDrive(path)) {
try { try {
@ -401,7 +401,7 @@ public class PlatformUtil {
File[] files = dev.listFiles(); File[] files = dev.listFiles();
for (File f : files) { for (File f : files) {
String name = f.getName(); 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 String path = "/dev/" + name; //NON-NLS
if (canReadDrive(path)) { if (canReadDrive(path)) {
drives.add(new LocalDisk(path, path, f.getTotalSpace())); drives.add(new LocalDisk(path, path, f.getTotalSpace()));

View File

@ -386,8 +386,15 @@ public class DataResultFilterNode extends FilterNode {
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); 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
actionsList.add(new ViewContextAction( // 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)); NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban));
}
} }
Content c = ban.getLookup().lookup(File.class); Content c = ban.getLookup().lookup(File.class);
Node n = null; Node n = null;

View File

@ -101,7 +101,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private final transient ExplorerManager em = new ExplorerManager(); private final transient ExplorerManager em = new ExplorerManager();
private static DirectoryTreeTopComponent instance; 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[]> backList;
private final LinkedList<String[]> forwardList; private final LinkedList<String[]> forwardList;
private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS

View File

@ -18,7 +18,6 @@
*/ */
package org.sleuthkit.autopsy.guiutils; package org.sleuthkit.autopsy.guiutils;
import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.time.Duration; import java.time.Duration;
import javax.swing.JTable; import javax.swing.JTable;
@ -41,36 +40,47 @@ public class DurationCellRenderer extends GrayableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Long) { if (value instanceof Long) {
{ {
Duration d = Duration.ofMillis((long) value); setText(DurationCellRenderer.longToDurationString((long) value));
if (d.isNegative()) {
d = Duration.ofMillis(-(long) value);
}
String result;
long days = d.toDays();
long hours = d.minusDays(days).toHours();
long minutes = d.minusDays(days).minusHours(hours).toMinutes();
long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds();
if (minutes > 0) {
if (hours > 0) {
if (days > 0) {
result = days + " d " + hours + " h " + minutes + " m " + seconds + " s";
} else {
result = hours + " h " + minutes + " m " + seconds + " s";
}
} else {
result = minutes + " m " + seconds + " s";
}
} else {
result = seconds + " s";
}
setText(result);
} }
} }
grayCellIfTableNotEnabled(table, isSelected); grayCellIfTableNotEnabled(table, isSelected);
return this; 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(-duration);
}
String result;
long days = d.toDays();
long hours = d.minusDays(days).toHours();
long minutes = d.minusDays(days).minusHours(hours).toMinutes();
long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds();
if (minutes > 0) {
if (hours > 0) {
if (days > 0) {
result = days + " d " + hours + " h " + minutes + " m " + seconds + " s";
} else {
result = hours + " h " + minutes + " m " + seconds + " s";
}
} else {
result = minutes + " m " + seconds + " s";
}
} else {
result = seconds + " s";
}
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); this.warnings.add(warning);
} }
} else { } else {
try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { // @@@ BC Jython serialization is currently broken and this
settings = (IngestModuleIngestJobSettings) in.readObject(); // throws an exception. (-2323). Commenting out so that
} catch (IOException | ClassNotFoundException exception) { // Python modules will at least load with default settings.
String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS // try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) {
logger.log(Level.WARNING, warning, exception); // settings = (IngestModuleIngestJobSettings) in.readObject();
this.warnings.add(warning); // } 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) { 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; package org.sleuthkit.autopsy.modules.encryptiondetection;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.logging.Level; 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.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard; 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.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; 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 { 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 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 IngestServices services = IngestServices.getInstance();
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
private FileTypeDetector fileTypeDetector; private FileTypeDetector fileTypeDetector;
@ -73,9 +70,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/** /**
* Create a EncryptionDetectionFileIngestModule object that will detect * Create a EncryptionDetectionFileIngestModule object that will detect
* files that are encrypted and create blackboard artifacts as appropriate. * files that are either encrypted or password protected and create
* The supplied EncryptionDetectionIngestJobSettings object is used to * blackboard artifacts as appropriate. The supplied
* configure the module. * EncryptionDetectionIngestJobSettings object is used to configure the
* module.
*/ */
EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) { EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
minimumEntropy = settings.getMinimumEntropy(); minimumEntropy = settings.getMinimumEntropy();
@ -101,13 +99,37 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
public IngestModule.ProcessResult process(AbstractFile file) { public IngestModule.ProcessResult process(AbstractFile file) {
try { try {
if (isFileEncrypted(file)) { /*
return flagFile(file); * Qualify the file type.
*/
if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
&& (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) {
/*
* Qualify the file against hash databases.
*/
if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) {
/*
* Qualify the MIME type.
*/
String mimeType = fileTypeDetector.getMIMEType(file);
if (mimeType.equals("application/octet-stream")) {
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 ex) { } catch (ReadContentInputStreamException | SAXException | TikaException ex) {
logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex); logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR; return IngestModule.ProcessResult.ERROR;
} catch (IOException | TskCoreException ex) { } catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex); logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR; return IngestModule.ProcessResult.ERROR;
} }
@ -121,31 +143,23 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
* @throws IngestModule.IngestModuleException If the input is empty, * @throws IngestModule.IngestModuleException If the input is empty,
* invalid, or out of range. * 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 { private void validateSettings() throws IngestModule.IngestModuleException {
if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumEntropyInput()); EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
}
if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) {
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumFileSizeInput());
}
} }
/** /**
* Create a blackboard artifact. * Create a blackboard artifact.
* *
* @param The file to be processed. * @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 * @return 'OK' if the file was processed successfully, or 'ERROR' if there
* was a problem. * was a problem.
*/ */
private IngestModule.ProcessResult flagFile(AbstractFile file) { private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) {
try { try {
BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); BlackboardArtifact artifact = file.newArtifact(artifactType);
try { try {
/* /*
@ -159,17 +173,19 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/* /*
* Send an event to update the view with the new result. * 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))); services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact)));
/* /*
* Make an ingest inbox message. * Make an ingest inbox message.
*/ */
StringBuilder detailsSb = new StringBuilder(); StringBuilder detailsSb = new StringBuilder();
detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append("<br/>\n"); detailsSb.append("File: ").append(file.getParentPath()).append(file.getName());
detailsSb.append("Entropy: ").append(calculatedEntropy); if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
detailsSb.append("<br/>\n").append("Entropy: ").append(calculatedEntropy);
}
services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
"Encryption Detected Match: " + file.getName(), artifactType.getDisplayName() + " Match: " + file.getName(),
detailsSb.toString(), detailsSb.toString(),
file.getName(), file.getName(),
artifact)); artifact));
@ -182,16 +198,87 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
} }
/** /**
* This method checks if the AbstractFile input is encrypted. Initial * This method checks if the AbstractFile input is password protected.
* 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. * @param file AbstractFile to be checked.
* *
* @return True if the AbstractFile is encrypted. * @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 isFileEncrypted(AbstractFile file) throws ReadContentInputStreamException, IOException, TskCoreException { 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) {
/*
* File is determined to be password protected.
*/
passwordProtected = true;
} finally {
if (in != null) {
in.close();
}
if (bin != null) {
bin.close();
}
}
}
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 * Criteria for the checks in this method are partially based on
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection * http://www.forensicswiki.org/wiki/TrueCrypt#Detection
@ -200,99 +287,20 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
boolean possiblyEncrypted = false; boolean possiblyEncrypted = false;
/* /*
* Qualify the file type. * Qualify the size.
*/ */
if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) long contentSize = file.getSize();
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) if (contentSize >= minimumFileSize) {
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
&& (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) {
/*
* Qualify the file against hash databases.
*/
if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) {
/* /*
* Qualify the size. * Qualify the entropy.
*/ */
long contentSize = file.getSize(); calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
if (contentSize >= minimumFileSize) { if (calculatedEntropy >= minimumEntropy) {
if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) { possiblyEncrypted = true;
/*
* Qualify the MIME type.
*/
String mimeType = fileTypeDetector.getMIMEType(file);
if (mimeType.equals("application/octet-stream")) {
possiblyEncrypted = true;
}
}
} }
} }
} }
return possiblyEncrypted;
if (possiblyEncrypted) {
calculatedEntropy = calculateEntropy(file);
if (calculatedEntropy >= minimumEntropy) {
return true;
}
}
return false;
}
/**
* Calculate the entropy of the file. The result is used to qualify the file
* as an encrypted file.
*
* @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
*/
InputStream in = null;
BufferedInputStream bin = null;
try {
in = new ReadContentInputStream(file);
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 = 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;
} finally {
if (in != null) {
in.close();
}
if (bin != null) {
bin.close();
}
}
} }
} }

View File

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

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2017 Basis Technology Corp. * Copyright 2017-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -30,7 +30,8 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; 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) @ServiceProvider(service = IngestModuleFactory.class)
@Messages({ @Messages({
@ -106,11 +107,14 @@ public class EncryptionDetectionModuleFactory implements IngestModuleFactory {
@Override @Override
public boolean isDataSourceIngestModuleFactory() { public boolean isDataSourceIngestModuleFactory() {
return false; return true;
} }
@Override @Override
public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { 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.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; 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.FileIngestModule;
import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
@ -182,8 +184,20 @@ public class HashDbIngestModule implements FileIngestModule {
String md5Hash = file.getMd5Hash(); String md5Hash = file.getMd5Hash();
if (md5Hash == null || md5Hash.isEmpty()) { if (md5Hash == null || md5Hash.isEmpty()) {
try { try {
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
long calcstart = System.currentTimeMillis(); long calcstart = System.currentTimeMillis();
md5Hash = HashUtility.calculateMd5Hash(file); 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); file.setMd5Hash(md5Hash);
long delta = (System.currentTimeMillis() - calcstart); long delta = (System.currentTimeMillis() - calcstart);
totals.totalCalctime.addAndGet(delta); totals.totalCalctime.addAndGet(delta);

View File

@ -225,7 +225,7 @@ public final class PromptDialogManager {
@NbBundle.Messages({ @NbBundle.Messages({
"PromptDialogManager.showTimeLineDisabledMessage.contentText=" "PromptDialogManager.showTimeLineDisabledMessage.contentText="
+ "Timeline functionality is not available for Linux yet." + "Timeline functionality is not available yet."
+ " Timeline will be disabled. ", + " Timeline will be disabled. ",
"PromptDialogManager.showTimeLineDisabledMessage.headerText="}) "PromptDialogManager.showTimeLineDisabledMessage.headerText="})
static void showTimeLineDisabledMessage() { 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; package org.sleuthkit.autopsy.ingest;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import static junit.framework.Assert.assertFalse;
import org.netbeans.junit.NbModuleSuite; import org.netbeans.junit.NbModuleSuite;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import org.sleuthkit.autopsy.casemodule.CaseDetails;
import junit.framework.Test; import junit.framework.Test;
import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbTestCase; import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions; import org.openide.util.Exceptions;
import org.python.icu.impl.Assert; 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.LocalFilesDSProcessor;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType;
import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory; import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; 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.MetaTypeCondition;
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition;
import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory; import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory;
import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner; import org.sleuthkit.autopsy.testutils.CaseUtils;
import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner.ProcessorCallback;
import org.sleuthkit.autopsy.testutils.IngestJobRunner; import org.sleuthkit.autopsy.testutils.IngestJobRunner;
import org.sleuthkit.autopsy.testutils.IngestUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
@ -77,26 +69,14 @@ public class IngestFileFiltersTest extends NbTestCase {
@Override @Override
public void tearDown() { public void tearDown() {
try { CaseUtils.closeCase();
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);
}
} }
public void testBasicDir() { public void testBasicDir() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testBasicDir"); Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testBasicDir");
createCase(casePath); CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rule = new HashMap<>(); HashMap<String, Rule> rule = new HashMap<>();
rule.put("Rule", new Rule("testFileType", null, new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null)); 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 { try {
Case openCase = Case.getOpenCase(); Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>(); ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, dirFilter); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, dirFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = openCase.getServices().getFileManager(); FileManager fileManager = openCase.getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("file.jpg", "dir1"); List<AbstractFile> results = fileManager.findFiles("file.jpg", "dir1");
String mimeType = results.get(0).getMIMEType(); String mimeType = results.get(0).getMIMEType();
@ -136,9 +117,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testExtAndDirWithOneRule() { public void testExtAndDirWithOneRule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithOneRule"); Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithOneRule");
createCase(casePath); CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>(); 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)); 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 { try {
Case openCase = Case.getOpenCase(); Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>(); ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%"); List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(62, results.size()); assertEquals(62, results.size());
@ -171,9 +153,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testExtAndDirWithTwoRules() { public void testExtAndDirWithTwoRules() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithTwoRules"); Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithTwoRules");
createCase(casePath); CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>(); HashMap<String, Rule> rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); 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 { try {
Case openCase = Case.getOpenCase(); Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>(); ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%"); List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(62, results.size()); assertEquals(62, results.size());
@ -215,9 +198,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testFullFileNameRule() { public void testFullFileNameRule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testFullFileNameRule"); Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testFullFileNameRule");
createCase(casePath); CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>(); 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)); 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 { try {
Case openCase = Case.getOpenCase(); Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>(); ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, fullNameFilter);
runIngestJob(openCase.getDataSources(), templates, fullNameFilter); IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%"); List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(62, results.size()); assertEquals(62, results.size());
@ -251,9 +234,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testCarvingWithExtRuleAndUnallocSpace() { public void testCarvingWithExtRuleAndUnallocSpace() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingWithExtRuleAndUnallocSpace"); Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingWithExtRuleAndUnallocSpace");
createCase(casePath); CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>(); HashMap<String, Rule> rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); 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 { try {
Case openCase = Case.getOpenCase(); Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>(); ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, extensionFilter); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, extensionFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%"); List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(70, results.size()); assertEquals(70, results.size());
@ -299,9 +283,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testCarvingNoUnallocatedSpace() { public void testCarvingNoUnallocatedSpace() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingNoUnallocatedSpace"); Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingNoUnallocatedSpace");
createCase(casePath); CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap<String, Rule> rules = new HashMap<>(); HashMap<String, Rule> rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); 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 { try {
Case openCase = Case.getOpenCase(); Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>(); ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, extensionFilter); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, extensionFilter);
try { try {
List<IngestModuleError> errs = IngestJobRunner.runIngestJob(openCase.getDataSources(), ingestJobSettings); List<IngestModuleError> errs = IngestJobRunner.runIngestJob(openCase.getDataSources(), ingestJobSettings);
@ -333,9 +317,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testEmbeddedModule() { public void testEmbeddedModule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testEmbeddedModule"); Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testEmbeddedModule");
createCase(casePath); CaseUtils.createCase(casePath);
LocalFilesDSProcessor dataSourceProcessor = new LocalFilesDSProcessor(); LocalFilesDSProcessor dataSourceProcessor = new LocalFilesDSProcessor();
addDataSourceToCase(dataSourceProcessor, ZIPFILE_PATH); IngestUtils.addDataSource(dataSourceProcessor, ZIPFILE_PATH);
//Build the filter to find jpg files //Build the filter to find jpg files
HashMap<String, Rule> rules = new HashMap<>(); HashMap<String, Rule> rules = new HashMap<>();
@ -348,9 +332,10 @@ public class IngestFileFiltersTest extends NbTestCase {
try { try {
Case openCase = Case.getOpenCase(); Case openCase = Case.getOpenCase();
ArrayList<IngestModuleTemplate> templates = new ArrayList<>(); ArrayList<IngestModuleTemplate> templates = new ArrayList<>();
templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
templates.add(getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory())); templates.add(IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()));
runIngestJob(openCase.getDataSources(), templates, embeddedFilter); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, embeddedFilter);
IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
//get all .jpg files in zip file //get all .jpg files in zip file
List<AbstractFile> results = fileManager.findFiles("%%"); List<AbstractFile> results = fileManager.findFiles("%%");
@ -376,64 +361,4 @@ public class IngestFileFiltersTest extends NbTestCase {
Assert.fail(ex); 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> <specification-version>1.49.1</specification-version>
</run-dependency> </run-dependency>
</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> <dependency>
<code-name-base>org.openide.awt</code-name-base> <code-name-base>org.openide.awt</code-name-base>
<build-prerequisite/> <build-prerequisite/>
@ -49,6 +57,14 @@
<specification-version>7.41.1</specification-version> <specification-version>7.41.1</specification-version>
</run-dependency> </run-dependency>
</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> <dependency>
<code-name-base>org.openide.filesystems</code-name-base> <code-name-base>org.openide.filesystems</code-name-base>
<build-prerequisite/> <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"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" 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"> <Group type="103" groupAlignment="1" attributes="0">
<Component id="pendingScrollPane" max="32767" attributes="0"/> <Component id="pendingScrollPane" max="32767" attributes="0"/>
<Component id="runningScrollPane" alignment="0" max="32767" attributes="0"/> <Component id="runningScrollPane" alignment="0" max="32767" attributes="0"/>
<Component id="completedScrollPane" alignment="0" max="32767" attributes="0"/> <Component id="completedScrollPane" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="0" 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="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="1" 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"/>
</Group>
<Component id="lbPending" alignment="0" min="-2" max="-2" 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="lbCompleted" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lbRunning" 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> </Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/> <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group> </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> </Group>
<EmptySpace min="-2" max="-2" attributes="0"/> <EmptySpace min="-2" max="-2" attributes="0"/>
</Group> </Group>
@ -65,60 +57,48 @@
<DimensionLayout dim="1"> <DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" 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"> <Group type="103" groupAlignment="3" attributes="0">
<Component id="lbServicesStatus" alignment="3" min="-2" pref="23" max="-2" 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"/> <Component id="tbServicesStatusMessage" alignment="3" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="lbPending" min="-2" pref="23" max="-2" attributes="0"/> <Component id="lbPending" min="-2" pref="23" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="1" max="-2" attributes="0"/> <EmptySpace min="-2" pref="1" max="-2" attributes="0"/>
<Component id="pendingScrollPane" min="-2" pref="215" max="-2" attributes="0"/> <Component id="pendingScrollPane" max="32767" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="lbRunning" min="-2" max="-2" attributes="0"/> <Component id="lbRunning" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="1" max="-2" attributes="0"/> <EmptySpace min="-2" pref="1" max="-2" attributes="0"/>
<Component id="runningScrollPane" min="-2" pref="133" max="-2" attributes="0"/> <Component id="runningScrollPane" pref="133" max="32767" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="lbCompleted" min="-2" max="-2" attributes="0"/> <Component id="lbCompleted" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="completedScrollPane" min="-2" pref="179" max="-2" attributes="0"/> <Component id="completedScrollPane" pref="179" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0"> <Group type="103" groupAlignment="3" attributes="0">
<Component id="refreshButton" alignment="3" min="-2" max="-2" 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="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> </Group>
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
<SubComponents> <SubComponents>
<Container class="javax.swing.JScrollPane" name="pendingScrollPane"> <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> <AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/> <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <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>
<Container class="javax.swing.JScrollPane" name="runningScrollPane"> <Container class="javax.swing.JScrollPane" name="runningScrollPane">
<AuxValues> <AuxValues>
@ -126,22 +106,6 @@
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <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>
<Container class="javax.swing.JScrollPane" name="completedScrollPane"> <Container class="javax.swing.JScrollPane" name="completedScrollPane">
<AuxValues> <AuxValues>
@ -149,22 +113,6 @@
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <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> </Container>
<Component class="javax.swing.JLabel" name="lbPending"> <Component class="javax.swing.JLabel" name="lbPending">
<Properties> <Properties>
@ -233,34 +181,6 @@
</Property> </Property>
</Properties> </Properties>
</Component> </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"> <Component class="javax.swing.JButton" name="clusterMetricsButton">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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"/> <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="clusterMetricsButtonActionPerformed"/>
</Events> </Events>
</Component> </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> </SubComponents>
</Form> </Form>

View File

@ -19,38 +19,23 @@
package org.sleuthkit.autopsy.experimental.autoingest; package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Cursor; 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.Observable;
import java.util.Observer; import java.util.Observer;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.DefaultListSelectionModel;
import java.awt.Color; import java.awt.Color;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import javax.swing.UIManager; 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;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.core.ServicesMonitor;
import org.sleuthkit.autopsy.coreutils.Logger; 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. * A dashboard for monitoring an automated ingest cluster.
@ -58,35 +43,12 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer;
final class AutoIngestDashboard extends JPanel implements Observer { final class AutoIngestDashboard extends JPanel implements Observer {
private static final long serialVersionUID = 1L; 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 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 AutoIngestMonitor autoIngestMonitor;
private AutoIngestJobsPanel pendingJobsPanel;
private AutoIngestJobsPanel runningJobsPanel;
private AutoIngestJobsPanel completedJobsPanel;
/** /**
* Maintain a mapping of each service to it's last status update. * Maintain a mapping of each service to it's last status update.
*/ */
@ -113,38 +75,56 @@ final class AutoIngestDashboard extends JPanel implements Observer {
/** /**
* Constructs a panel for monitoring an automated ingest cluster. * 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() { private AutoIngestDashboard() {
this.statusByService = new ConcurrentHashMap<>(); this.statusByService = new ConcurrentHashMap<>();
pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
initComponents(); initComponents();
statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); 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.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")); statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"));
setServicesStatusMessage(); setServicesStatusMessage();
initPendingJobsTable(); pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.PENDING_JOB);
initRunningJobsTable(); pendingJobsPanel.setSize(pendingScrollPane.getSize());
initCompletedJobsTable(); 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. * Must set this flag, otherwise pop up menus don't close properly.
*/ */
UIManager.put("PopupMenu.consumeEventOnClose", false); UIManager.put("PopupMenu.consumeEventOnClose", false);
} }
AutoIngestMonitor getMonitor() {
return autoIngestMonitor;
}
AutoIngestJobsPanel getPendingJobsPanel() {
return pendingJobsPanel;
}
/** /**
* Update status of the services on the dashboard * Update status of the services on the dashboard
*/ */
private void displayServicesStatus() { private void displayServicesStatus() {
tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message",
statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()),
statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()),
statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()),
statusByService.get(ServicesMonitor.Service.MESSAGING.toString()))); statusByService.get(ServicesMonitor.Service.MESSAGING.toString())));
String upStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); String upStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up");
if (statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).compareTo(upStatus) != 0 if (statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).compareTo(upStatus) != 0
@ -162,7 +142,7 @@ final class AutoIngestDashboard extends JPanel implements Observer {
*/ */
private void setServicesStatusMessage() { private void setServicesStatusMessage() {
new SwingWorker<Void, Void>() { new SwingWorker<Void, Void>() {
@Override @Override
protected Void doInBackground() throws Exception { protected Void doInBackground() throws Exception {
statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE)); statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE));
@ -202,233 +182,6 @@ final class AutoIngestDashboard extends JPanel implements Observer {
}.execute(); }.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, * 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 * subscribes to services monitor events and starts a task to populate the
@ -437,10 +190,10 @@ final class AutoIngestDashboard extends JPanel implements Observer {
private void startUp() throws AutoIngestMonitor.AutoIngestMonitorException { private void startUp() throws AutoIngestMonitor.AutoIngestMonitorException {
PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> { PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> {
String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString(); String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString();
String status = evt.getNewValue().toString(); String status = evt.getNewValue().toString();
if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) { if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) {
status = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); status = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up");
LOGGER.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS LOGGER.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS
@ -450,31 +203,48 @@ final class AutoIngestDashboard extends JPanel implements Observer {
} else { } else {
LOGGER.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS LOGGER.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS
} }
// if the status update is for an existing service who's status hasn't changed - do nothing. // if the status update is for an existing service who's status hasn't changed - do nothing.
if (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) { if (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) {
return; return;
} }
statusByService.put(serviceDisplayName, status); statusByService.put(serviceDisplayName, status);
displayServicesStatus(); displayServicesStatus();
}; };
// Subscribe to all multi-user services in order to display their status // Subscribe to all multi-user services in order to display their status
Set<String> servicesList = new HashSet<>(); Set<String> servicesList = new HashSet<>();
servicesList.add(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()); servicesList.add(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString());
servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString());
servicesList.add(ServicesMonitor.Service.MESSAGING.toString()); servicesList.add(ServicesMonitor.Service.MESSAGING.toString());
ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener); ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener);
autoIngestMonitor = new AutoIngestMonitor(); autoIngestMonitor = new AutoIngestMonitor();
autoIngestMonitor.addObserver(this); autoIngestMonitor.addObserver(this);
autoIngestMonitor.startUp(); 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 @Override
public void update(Observable observable, Object arg) { 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. * @param jobsSnapshot The jobs snapshot.
*/ */
private void refreshTables(JobsSnapshot jobsSnapshot) { void refreshTables() {
List<AutoIngestJob> pendingJobs = jobsSnapshot.getPendingJobs(); pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
List<AutoIngestJob> runningJobs = jobsSnapshot.getRunningJobs(); runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
List<AutoIngestJob> completedJobs = jobsSnapshot.getCompletedJobs(); completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
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);
}
} }
/** /**
@ -698,75 +301,22 @@ final class AutoIngestDashboard extends JPanel implements Observer {
jButton1 = new javax.swing.JButton(); jButton1 = new javax.swing.JButton();
pendingScrollPane = new javax.swing.JScrollPane(); pendingScrollPane = new javax.swing.JScrollPane();
pendingTable = new javax.swing.JTable();
runningScrollPane = new javax.swing.JScrollPane(); runningScrollPane = new javax.swing.JScrollPane();
runningTable = new javax.swing.JTable();
completedScrollPane = new javax.swing.JScrollPane(); completedScrollPane = new javax.swing.JScrollPane();
completedTable = new javax.swing.JTable();
lbPending = new javax.swing.JLabel(); lbPending = new javax.swing.JLabel();
lbRunning = new javax.swing.JLabel(); lbRunning = new javax.swing.JLabel();
lbCompleted = new javax.swing.JLabel(); lbCompleted = new javax.swing.JLabel();
refreshButton = new javax.swing.JButton(); refreshButton = new javax.swing.JButton();
lbServicesStatus = new javax.swing.JLabel(); lbServicesStatus = new javax.swing.JLabel();
tbServicesStatusMessage = new javax.swing.JTextField(); tbServicesStatusMessage = new javax.swing.JTextField();
prioritizeJobButton = new javax.swing.JButton();
prioritizeCaseButton = new javax.swing.JButton();
clusterMetricsButton = 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 org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N
pendingTable.setModel(pendingTableModel); pendingScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N pendingScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
pendingTable.setRowHeight(20); pendingScrollPane.setOpaque(false);
pendingTable.setSelectionModel(new DefaultListSelectionModel() { pendingScrollPane.setPreferredSize(new java.awt.Dimension(2, 215));
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);
lbPending.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N 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 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.setText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.text")); // NOI18N
tbServicesStatusMessage.setBorder(null); 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 org.openide.awt.Mnemonics.setLocalizedText(clusterMetricsButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.clusterMetricsButton.text")); // NOI18N
clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() { clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { 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); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
@ -843,35 +357,27 @@ final class AutoIngestDashboard extends JPanel implements Observer {
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap() .addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .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(runningScrollPane, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(completedScrollPane, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(completedScrollPane, 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.DEFAULT_SIZE, 861, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .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(lbPending, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE))
.addComponent(lbServicesStatus) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(0, 0, Short.MAX_VALUE))) .addComponent(clusterMetricsButton)))
.addContainerGap()) .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.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -883,23 +389,19 @@ final class AutoIngestDashboard extends JPanel implements Observer {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lbPending, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(lbPending, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(1, 1, 1) .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(lbRunning) .addComponent(lbRunning)
.addGap(1, 1, 1) .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(lbCompleted) .addComponent(lbCompleted)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(refreshButton) .addComponent(refreshButton)
.addComponent(prioritizeJobButton) .addComponent(clusterMetricsButton))
.addComponent(prioritizeCaseButton)
.addComponent(clusterMetricsButton)
.addComponent(deprioritizeJobButton)
.addComponent(deprioritizeCaseButton))
.addContainerGap()) .addContainerGap())
); );
}// </editor-fold>//GEN-END:initComponents }// </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 private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
JobsSnapshot jobsSnapshot = autoIngestMonitor.refreshJobsSnapshot(); refreshTables();
refreshTables(jobsSnapshot);
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}//GEN-LAST:event_refreshButtonActionPerformed }//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 private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed
new AutoIngestMetricsDialog(this.getTopLevelAncestor()); new AutoIngestMetricsDialog(this.getTopLevelAncestor());
}//GEN-LAST:event_clusterMetricsButtonActionPerformed }//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 // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton clusterMetricsButton; private javax.swing.JButton clusterMetricsButton;
private javax.swing.JScrollPane completedScrollPane; 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.JButton jButton1;
private javax.swing.JLabel lbCompleted; private javax.swing.JLabel lbCompleted;
private javax.swing.JLabel lbPending; private javax.swing.JLabel lbPending;
private javax.swing.JLabel lbRunning; private javax.swing.JLabel lbRunning;
private javax.swing.JLabel lbServicesStatus; private javax.swing.JLabel lbServicesStatus;
private javax.swing.JScrollPane pendingScrollPane; 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.JButton refreshButton;
private javax.swing.JScrollPane runningScrollPane; private javax.swing.JScrollPane runningScrollPane;
private javax.swing.JTable runningTable;
private javax.swing.JTextField tbServicesStatusMessage; private javax.swing.JTextField tbServicesStatusMessage;
// End of variables declaration//GEN-END:variables // 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

@ -25,4 +25,4 @@
</Group> </Group>
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
</Form> </Form>

View File

@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.experimental.autoingest; package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Component;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -74,7 +75,7 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
AutoIngestDashboard dashboard = AutoIngestDashboard.createDashboard(); AutoIngestDashboard dashboard = AutoIngestDashboard.createDashboard();
tc.add(dashboard); tc.add(dashboard);
dashboard.setSize(dashboard.getPreferredSize()); dashboard.setSize(dashboard.getPreferredSize());
tc.open(); tc.open();
} }
tc.toFront(); tc.toFront();
@ -86,17 +87,24 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
} }
} }
public static void closeTopComponent() { @Override
protected void componentClosed() {
if (topComponentInitialized) { if (topComponentInitialized) {
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) { if (tc != null) {
try { try {
for (Component comp : getComponents()) {
if (comp instanceof AutoIngestDashboard) {
((AutoIngestDashboard) comp).shutDown();
}
}
tc.close(); tc.close();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "Failed to close " + PREFERRED_ID, e); // NON-NLS logger.log(Level.SEVERE, "Failed to close " + PREFERRED_ID, e); // NON-NLS
} }
} }
} }
super.componentClosed();
} }
public AutoIngestDashboardTopComponent() { public AutoIngestDashboardTopComponent() {
@ -104,6 +112,20 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
setName(Bundle.CTL_AutoIngestDashboardTopComponent()); 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 @Override
public List<Mode> availableModes(List<Mode> modes) { 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.Stage=Stage
AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status
AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path 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.StageTime=Time in Stage
AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case
AutoIngestDashboard.JobsTableModel.ColumnHeader.Job=Job AutoIngestDashboard.JobsTableModel.ColumnHeader.Job=Job
@ -222,10 +219,6 @@ FileExporterSettingsPanel.SaveTooltip_1=Save the current rule
AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables
AutoIngestDashboard.refreshButton.text=&Refresh AutoIngestDashboard.refreshButton.text=&Refresh
AutoIngestDashboard.jButton1.text=jButton1 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= AutoIngestMetricsDialog.reportTextArea.text=
AutoIngestDashboard.clusterMetricsButton.text=Auto Ingest &Metrics AutoIngestDashboard.clusterMetricsButton.text=Auto Ingest &Metrics
AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report
@ -236,10 +229,6 @@ ArchiveFilePanel.browseButton.text=Browse
ArchiveFilePanel.pathTextField.text= ArchiveFilePanel.pathTextField.text=
ArchiveFilePanel.errorLabel.text=Error Label ArchiveFilePanel.errorLabel.text=Error Label
AutoIngestMetricsDialog.startingDataLabel.text=Starting Date: 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.bnDeprioritizeCase.text=Deprioritize Case
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job
AutoIngestControlPanel.bnPrioritizeCase.text=Prioritize Case 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 final DataSourceProcessorCallback callback;
private volatile VolatilityProcessor volatilityProcessor; private volatile VolatilityProcessor volatilityProcessor;
private volatile boolean isCancelled; private volatile boolean isCancelled;
private final String profile; // empty for autodetect
/** /**
* Constructs a runnable that adds a memory image to a case database. * 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 * associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID). * to be unique across multiple cases (e.g., a UUID).
* @param memoryImagePath Path to the memory image file. * @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 pluginsToRun The Volatility plugins to run.
* @param timeZone The time zone to use when processing dates and * @param timeZone The time zone to use when processing dates and
* times for the image, obtained from * times for the image, obtained from
@ -65,9 +67,10 @@ final class AddMemoryImageTask implements Runnable {
* during processing. * during processing.
* @param callback Callback to call when processing is done. * @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.deviceId = deviceId;
this.memoryImagePath = memoryImagePath; this.memoryImagePath = memoryImagePath;
this.profile = profile;
this.pluginsToRun = pluginsToRun; this.pluginsToRun = pluginsToRun;
this.timeZone = timeZone; this.timeZone = timeZone;
this.callback = callback; this.callback = callback;
@ -94,7 +97,7 @@ final class AddMemoryImageTask implements Runnable {
try { try {
Image dataSource = addImageToCase(); Image dataSource = addImageToCase();
dataSources.add(dataSource); dataSources.add(dataSource);
volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, pluginsToRun, progressMonitor); volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, profile, pluginsToRun, progressMonitor);
volatilityProcessor.run(); volatilityProcessor.run();
} catch (NoCurrentCaseException | TskCoreException | VolatilityProcessor.VolatilityProcessorException ex) { } catch (NoCurrentCaseException | TskCoreException | VolatilityProcessor.VolatilityProcessorException ex) {
criticalErrorOccurred = true; criticalErrorOccurred = true;

View File

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

View File

@ -28,13 +28,15 @@
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Component id="pathLabel" min="-2" pref="218" max="-2" attributes="0"/> <Component id="pathLabel" min="-2" pref="218" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0"> <Group type="102" attributes="0">
<Component id="timeZoneLabel" min="-2" pref="168" max="-2" attributes="0"/> <Component id="timeZoneLabel" min="-2" pref="134" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" 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"/> <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> </Group>
</Group> </Group>
@ -43,8 +45,8 @@
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Component id="errorLabel" min="-2" max="-2" 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="PluginsToRunLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="profileLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace max="32767" attributes="0"/> <EmptySpace max="32767" attributes="0"/>
</Group> </Group>
@ -66,17 +68,17 @@
</Group> </Group>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="errorLabel" min="-2" 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"> <Group type="103" groupAlignment="3" attributes="0">
<Component id="volExecutableLabel" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="profileLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="volExecutableComboBox" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="profileComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Component id="PluginsToRunLabel" min="-2" max="-2" 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> </Group>
<EmptySpace pref="30" max="32767" attributes="0"/> <EmptySpace pref="73" max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -85,7 +87,7 @@
<Component class="javax.swing.JLabel" name="pathLabel"> <Component class="javax.swing.JLabel" name="pathLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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> </Property>
</Properties> </Properties>
<AccessibilityProperties> <AccessibilityProperties>
@ -97,14 +99,17 @@
<Component class="javax.swing.JTextField" name="pathTextField"> <Component class="javax.swing.JTextField" name="pathTextField">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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> </Property>
</Properties> </Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pathTextFieldActionPerformed"/>
</Events>
</Component> </Component>
<Component class="javax.swing.JButton" name="browseButton"> <Component class="javax.swing.JButton" name="browseButton">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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> </Property>
</Properties> </Properties>
<Events> <Events>
@ -117,14 +122,14 @@
<Color blue="0" green="0" red="ff" type="rgb"/> <Color blue="0" green="0" red="ff" type="rgb"/>
</Property> </Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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> </Property>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JLabel" name="timeZoneLabel"> <Component class="javax.swing.JLabel" name="timeZoneLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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> </Property>
</Properties> </Properties>
</Component> </Component>
@ -139,27 +144,6 @@
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/> <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues> </AuxValues>
</Component> </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"> <Component class="javax.swing.JLabel" name="PluginsToRunLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents> <SubComponents>
<Component class="javax.swing.JTable" name="PluginList"> <Component class="javax.swing.JTable" name="pluginTable">
<Properties> <Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor"> <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
<Table columnCount="0" rowCount="4"/> <Table columnCount="0" rowCount="4"/>
@ -189,5 +173,20 @@
</Component> </Component>
</SubComponents> </SubComponents>
</Container> </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> </SubComponents>
</Form> </Form>

View File

@ -26,7 +26,9 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.SimpleTimeZone; import java.util.SimpleTimeZone;
import java.util.SortedSet;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.TreeSet;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JTable; 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.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PathValidator; import org.sleuthkit.autopsy.coreutils.PathValidator;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class MemoryDSInputPanel extends JPanel implements DocumentListener { final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private static final long serialVersionUID = 1L; //default 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 PluginListTableModel tableModel = new PluginListTableModel();
private final List<String> PluginListNames = new ArrayList<>(); 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 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 * Creates new MemoryDSInputPanel panel for user input
*/ */
private MemoryDSInputPanel(String context) { 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); Arrays.sort(this.pluginList);
initComponents(); initComponents();
@ -82,7 +98,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
instance.postInit(); instance.postInit();
instance.customizePluginListTable(); instance.customizePluginListTable();
instance.createTimeZoneList(); instance.createTimeZoneList();
instance.createVolatilityVersionList(); instance.populateProfileCombobox();
instance.createPluginList(); instance.createPluginList();
return instance; return instance;
@ -95,14 +111,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
} }
private void customizePluginListTable() { private void customizePluginListTable() {
PluginList.setModel(tableModel); pluginTable.setModel(tableModel);
PluginList.setTableHeader(null); pluginTable.setTableHeader(null);
PluginList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); pluginTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
final int width = listsScrollPane.getPreferredSize().width; final int width = listsScrollPane.getPreferredSize().width;
PluginList.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); pluginTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
TableColumn column; TableColumn column;
for (int i = 0; i < PluginList.getColumnCount(); i++) { for (int i = 0; i < pluginTable.getColumnCount(); i++) {
column = PluginList.getColumnModel().getColumn(i); column = pluginTable.getColumnModel().getColumn(i);
if (i == 0) { if (i == 0) {
column.setPreferredWidth(((int) (width * 0.07))); column.setPreferredWidth(((int) (width * 0.07)));
} else { } else {
@ -138,11 +154,12 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
timeZoneComboBox.setSelectedItem(formatted); timeZoneComboBox.setSelectedItem(formatted);
} }
private void createVolatilityVersionList() {
private void populateProfileCombobox() {
volExecutableComboBox.addItem("2.6"); profileComboBox.addItem(AUTODETECT_PROFILE);
volExecutableComboBox.addItem("2.5"); profileList.forEach((profile) -> {
profileComboBox.addItem(profile);
});
} }
private void createPluginList() { private void createPluginList() {
@ -157,8 +174,10 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
PluginListNames.add(plugin); PluginListNames.add(plugin);
if (allEnabled) { if (allEnabled) {
pluginListStates.put(plugin, true); pluginListStates.put(plugin, true);
} else if ((pluginMap.containsKey(plugin) && pluginMap.get(plugin).equals("false"))) {
pluginListStates.put(plugin, false);
} else { } else {
pluginListStates.put(plugin, pluginMap.containsKey(plugin)); pluginListStates.put(plugin, true);
} }
} }
tableModel.fireTableDataChanged(); tableModel.fireTableDataChanged();
@ -181,15 +200,20 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
errorLabel = new javax.swing.JLabel(); errorLabel = new javax.swing.JLabel();
timeZoneLabel = new javax.swing.JLabel(); timeZoneLabel = new javax.swing.JLabel();
timeZoneComboBox = new javax.swing.JComboBox<>(); timeZoneComboBox = new javax.swing.JComboBox<>();
volExecutableLabel = new javax.swing.JLabel();
volExecutableComboBox = new javax.swing.JComboBox<>();
PluginsToRunLabel = new javax.swing.JLabel(); PluginsToRunLabel = new javax.swing.JLabel();
listsScrollPane = new javax.swing.JScrollPane(); 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 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.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 org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.browseButton.text")); // NOI18N
browseButton.addActionListener(new java.awt.event.ActionListener() { browseButton.addActionListener(new java.awt.event.ActionListener() {
@ -205,18 +229,9 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
timeZoneComboBox.setMaximumRowCount(30); 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 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 [][] { 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); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
@ -241,18 +264,19 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 218, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(pathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 218, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup() .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .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(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 199, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE)))) .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)) .addGap(0, 163, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(errorLabel) .addComponent(errorLabel)
.addComponent(volExecutableLabel) .addComponent(PluginsToRunLabel)
.addComponent(PluginsToRunLabel)) .addComponent(profileLabel))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
); );
layout.setVerticalGroup( 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)) .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(errorLabel) .addComponent(errorLabel)
.addGap(18, 18, 18) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(volExecutableLabel) .addComponent(profileLabel)
.addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(PluginsToRunLabel) .addComponent(PluginsToRunLabel)
.addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(30, Short.MAX_VALUE)) .addContainerGap(73, Short.MAX_VALUE))
); );
pathLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName")); // NOI18N 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 }//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: // 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 // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTable PluginList;
private javax.swing.JLabel PluginsToRunLabel; private javax.swing.JLabel PluginsToRunLabel;
private javax.swing.JButton browseButton; private javax.swing.JButton browseButton;
private javax.swing.JLabel errorLabel; private javax.swing.JLabel errorLabel;
@ -311,10 +338,11 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private javax.swing.JScrollPane listsScrollPane; private javax.swing.JScrollPane listsScrollPane;
private javax.swing.JLabel pathLabel; private javax.swing.JLabel pathLabel;
private javax.swing.JTextField pathTextField; 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.JComboBox<String> timeZoneComboBox;
private javax.swing.JLabel timeZoneLabel; private javax.swing.JLabel timeZoneLabel;
private javax.swing.JComboBox<String> volExecutableComboBox;
private javax.swing.JLabel volExecutableLabel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
/** /**
* Get the path of the user selected image. * Get the path of the user selected image.
@ -325,17 +353,28 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
return pathTextField.getText(); 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> getPluginsToRun() {
List<String> enabledPlugins = new ArrayList<>(); List<String> enabledPlugins = new ArrayList<>();
Map<String, String> pluginMap = new HashMap<>(); Map<String, String> pluginSettingsToSave = new HashMap<>();
for (String plugin : PluginListNames) { for (String plugin : PluginListNames) {
if (pluginListStates.get(plugin)) { if (pluginListStates.get(plugin)) {
enabledPlugins.add(plugin); enabledPlugins.add(plugin);
pluginMap.put(plugin, "");
} }
pluginSettingsToSave.put(plugin, pluginListStates.get(plugin).toString());
} }
ModuleSettings.setConfigSettings(this.contextName, pluginSettingsToSave);
ModuleSettings.setConfigSettings(this.contextName, pluginMap);
// @@ Could return keys of set // @@ Could return keys of set
return enabledPlugins; return enabledPlugins;
} }

View File

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

View File

@ -29,7 +29,6 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import org.openide.modules.InstalledFileLocator; import org.openide.modules.InstalledFileLocator;
import org.openide.util.Lookup;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; 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.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData.EncodingType;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
/** /**
@ -70,6 +69,8 @@ class VolatilityProcessor {
private String moduleOutputPath; private String moduleOutputPath;
private FileManager fileManager; private FileManager fileManager;
private volatile boolean isCancelled; private volatile boolean isCancelled;
private Content outputVirtDir;
private String profile;
/** /**
* Constructs a processor that runs Volatility on a given memory image file * 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 memoryImagePath Path to memory image file.
* @param dataSource The memory image data source. * @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 plugInToRuns Volatility plugins to run.
* @param progressMonitor Progress monitor for reporting progress during * @param progressMonitor Progress monitor for reporting progress during
* processing. * 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.memoryImagePath = memoryImagePath;
this.pluginsToRun = plugInToRun; this.pluginsToRun = plugInToRun;
this.dataSource = dataSource; this.dataSource = dataSource;
@ -117,6 +120,13 @@ class VolatilityProcessor {
fileManager = currentCase.getServices().getFileManager(); 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. * Make an output folder unique to this data source.
*/ */
@ -124,9 +134,14 @@ class VolatilityProcessor {
moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), VOLATILITY, dataSourceId.toString()).toString(); moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), VOLATILITY, dataSourceId.toString()).toString();
File directory = new File(String.valueOf(moduleOutputPath)); File directory = new File(String.valueOf(moduleOutputPath));
if (!directory.exists()) { if (!directory.exists()) {
directory.mkdirs(); 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 progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo("imageinfo")); //NON-NLS
runVolatilityPlugin("imageinfo"); //NON-NLS runVolatilityPlugin("imageinfo"); //NON-NLS
profile = getProfileFromImageInfoOutput();
} }
progressMonitor.setIndeterminate(false); progressMonitor.setIndeterminate(false);
@ -177,24 +192,38 @@ class VolatilityProcessor {
commandLine.add("\"" + executableFile + "\""); //NON-NLS commandLine.add("\"" + executableFile + "\""); //NON-NLS
File memoryImage = new File(memoryImagePath); File memoryImage = new File(memoryImagePath);
commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS
if (!profile.isEmpty()) {
File imageInfoOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS commandLine.add("--profile=" + profile); //NON-NLS
if (imageInfoOutputFile.exists()) {
String memoryProfile = parseImageInfoOutput(imageInfoOutputFile);
commandLine.add("--profile=" + memoryProfile); //NON-NLS
} }
commandLine.add(pluginToRun); 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); ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
/* /*
* Add an environment variable to force Volatility to run with the same * Add an environment variable to force Volatility to run with the same
* permissions Autopsy uses. * permissions Autopsy uses.
*/ */
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
processBuilder.redirectOutput(new File(outputFile)); File outputFile = new File(outputFileAsString);
processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); //NON-NLS processBuilder.redirectOutput(outputFile);
processBuilder.redirectError(new File(moduleOutputPath + File.separator + "Volatility_err.txt")); //NON-NLS
processBuilder.directory(new File(memoryImage.getParent())); processBuilder.directory(new File(memoryImage.getParent()));
try { try {
@ -210,32 +239,16 @@ class VolatilityProcessor {
if (isCancelled) { if (isCancelled) {
return; return;
} }
/*
* Add the plugin output file to the case as a report.
*/
try { try {
Report report = currentCase.getSleuthkitCase().addReport(outputFile, VOLATILITY, VOLATILITY + " " + pluginToRun + " Plugin"); //NON-NLS String relativePath = new File(currentCase.getCaseDirectory()).toURI().relativize(new File(outputFileAsString).toURI()).getPath();
try { fileManager.addDerivedFile(pluginToRun, relativePath, outputFile.length(), 0, 0, 0, 0, true, outputVirtDir, null, null, null, null, EncodingType.NONE);
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));
}
} catch (TskCoreException ex) {
throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun), ex);
}
} catch (TskCoreException 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({ @NbBundle.Messages({
"VolatilityProcessor_exceptionMessage_failedToParseImageInfo=Could not parse image info" "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))) { try (BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) {
String fileRead = br.readLine(); String fileRead = br.readLine();
String[] profileLine = fileRead.split(":"); //NON-NLS if (fileRead != null) {
String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS String[] profileLine = fileRead.split(":"); //NON-NLS
return memProfile[0].replaceAll("\\s+", ""); //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) { } catch (IOException ex) {
throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo(), 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. * Stores data about a search before it is done.
*/ */
final class QueryRequest { final class AdHocQueryRequest {
private final KeywordSearchQuery query; private final KeywordSearchQuery query;
private final String queryString; private final String queryString;
@ -36,7 +36,7 @@ final class QueryRequest {
* @param id ID that callers simply increment from 0 * @param id ID that callers simply increment from 0
* @param query Query that will be performed. * @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.queryString = query.getEscapedQueryString();
this.queryProperties = map; this.queryProperties = map;
this.query = query; 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.EmptyNode;
import org.sleuthkit.autopsy.datamodel.KeyValue; import org.sleuthkit.autopsy.datamodel.KeyValue;
import org.sleuthkit.autopsy.datamodel.KeyValueNode; 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.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; 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 * Responsible for assembling nodes and columns in the right way and performing
* lazy queries as needed. * 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 //common properties (superset of all Node properties) to be displayed as columns
static final List<String> COMMON_PROPERTIES static final List<String> COMMON_PROPERTIES
@ -82,9 +82,9 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
.map(Object::toString)) .map(Object::toString))
.collect(Collectors.toList()); .collect(Collectors.toList());
private final Collection<QueryRequest> queryRequests; private final Collection<AdHocQueryRequest> queryRequests;
KeywordSearchResultFactory(Collection<QueryRequest> queryRequests) { AdHocSearchChildFactory(Collection<AdHocQueryRequest> queryRequests) {
this.queryRequests = queryRequests; this.queryRequests = queryRequests;
} }
@ -98,7 +98,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
@Override @Override
protected boolean createKeys(List<KeyValue> toPopulate) { protected boolean createKeys(List<KeyValue> toPopulate) {
for (QueryRequest queryRequest : queryRequests) { for (AdHocQueryRequest queryRequest : queryRequests) {
/** /**
* Check the validity of the requested query. * Check the validity of the requested query.
*/ */
@ -155,7 +155,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
} }
int hitNumber = 0; int hitNumber = 0;
List<KeyValueQueryContent> tempList = new ArrayList<>(); List<KeywordHitKey> tempList = new ArrayList<>();
for (KeywordHit hit : getOneHitPerObject(queryResults)) { for (KeywordHit hit : getOneHitPerObject(queryResults)) {
/** /**
@ -203,7 +203,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
hitName = contentName; hitName = contentName;
} }
hitNumber++; 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) { protected Node createNodeForKey(KeyValue key) {
Node resultNode; Node resultNode;
if (key instanceof KeyValueQueryContent) { if (key instanceof KeywordHitKey) {
AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key); AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeywordHitKey) key);
/** /**
* Place the Content, Artifact and hit results into the lookup for * 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<>(); ArrayList<Object> lookups = new ArrayList<>();
lookups.add(adHocQueryResult); lookups.add(adHocQueryResult);
if (((KeyValueQueryContent) key).getContent() != null) { if (((KeywordHitKey) key).getContent() != null) {
lookups.add(((KeyValueQueryContent) key).getContent()); lookups.add(((KeywordHitKey) key).getContent());
} }
if (((KeyValueQueryContent) key).getArtifact() != null) { if (((KeywordHitKey) key).getArtifact() != null) {
lookups.add(((KeyValueQueryContent) key).getArtifact()); lookups.add(((KeywordHitKey) key).getArtifact());
} }
Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.fixed(lookups.toArray())); 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 //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
resultNode = new KeywordSearchFilterNode(kvNode); resultNode = new AdHocSearchFilterNode(kvNode);
} else { } else {
resultNode = new EmptyNode("This Node Is Empty"); resultNode = new EmptyNode("This Node Is Empty");
resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text")); resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text"));
@ -298,7 +298,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
* which the hit was found. * which the hit was found.
* @param results The query results. * @param results The query results.
*/ */
AdHocQueryResult(KeyValueQueryContent key) { AdHocQueryResult(KeywordHitKey key) {
this.solrObjectId = key.getSolrObjectId(); this.solrObjectId = key.getSolrObjectId();
this.results = key.getHits(); 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 * Used to display keyword search results in table. Eventually turned into a
* node. * node.
*/ */
class KeyValueQueryContent extends KeyValue { class KeywordHitKey extends KeyValue {
private final long solrObjectId; private final long solrObjectId;
@ -350,7 +350,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
* @param query Query used in search * @param query Query used in search
* @param hits Full set of search results (for all files! @@@) * @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); super(name, map, id);
this.solrObjectId = solrObjectId; this.solrObjectId = solrObjectId;
this.content = content; this.content = content;

View File

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

View File

@ -53,15 +53,17 @@ import org.sleuthkit.datamodel.VirtualDirectory;
/** /**
* FilterNode containing properties and actions for keyword search. * 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. * Instantiate a KeywordSearchFilterNode.
* *
* @param original The original source node. * @param original The original source node.
*/ */
KeywordSearchFilterNode(Node original) { AdHocSearchFilterNode(Node original) {
super(original, null, new ProxyLookup(original.getLookup())); super(original, null, new ProxyLookup(original.getLookup()));
} }
@ -116,7 +118,7 @@ class KeywordSearchFilterNode extends FilterNode {
@Override @Override
public List<Action> visit(Report r) { public List<Action> visit(Report r) {
List<Action> actionsList = new ArrayList<>(); 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()); actionsList.addAll(ContextMenuExtensionPoint.getActions());
return actionsList; return actionsList;
@ -161,7 +163,7 @@ class KeywordSearchFilterNode extends FilterNode {
private List<Action> getFileActions(boolean enableHashSearch) { private List<Action> getFileActions(boolean enableHashSearch) {
List<Action> actionsList = new ArrayList<>(); 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(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal()));
actionsList.add(null); actionsList.add(null);
actionsList.add(ExtractAction.getInstance()); 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 * class and extended classes model the user's intentions, not necessarily how
* the search manager and 3rd party tools actually perform the search. * 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"); private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader");
protected int filesIndexed; protected int filesIndexed;
KeywordSearchPanel() { AdHocSearchPanel() {
initListeners(); initListeners();
} }
@ -110,7 +110,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel {
} }
} }
KeywordSearchQueryDelegator man = null; AdHocSearchDelegator man = null;
final List<KeywordList> keywordLists = getKeywordLists(); final List<KeywordList> keywordLists = getKeywordLists();
if (keywordLists.isEmpty()) { if (keywordLists.isEmpty()) {
@ -119,7 +119,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel {
KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
return; return;
} }
man = new KeywordSearchQueryDelegator(keywordLists); man = new AdHocSearchDelegator(keywordLists);
if (man.validate()) { if (man.validate()) {
man.execute(); 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 * Viewer panel widget for keyword lists that is used in the ingest config and
* options area. * 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 final Logger logger = Logger.getLogger(DropdownListSearchPanel.class.getName());
private static DropdownListSearchPanel instance; private static DropdownListSearchPanel instance;
@ -131,7 +132,7 @@ class DropdownListSearchPanel extends KeywordSearchPanel {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
if (ingestRunning) { if (ingestRunning) {
SearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists()); IngestSearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists());
logger.log(Level.INFO, "Submitted enqueued lists to ingest"); //NON-NLS logger.log(Level.INFO, "Submitted enqueued lists to ingest"); //NON-NLS
} else { } else {
searchAction(e); searchAction(e);

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger; 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 * perform this task at the desired size, and neither could numerous other
* fonts. * 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 long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(DropdownSingleTermSearchPanel.class.getName()); private static final Logger LOGGER = Logger.getLogger(DropdownSingleTermSearchPanel.class.getName());
@ -135,8 +137,22 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel {
* *
* @return The keyword list. * @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 @Override
List<KeywordList> getKeywordLists() { 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<>(); List<Keyword> keywords = new ArrayList<>();
keywords.add(new Keyword(keywordTextField.getText(), !regexRadioButton.isSelected(), exactRadioButton.isSelected())); keywords.add(new Keyword(keywordTextField.getText(), !regexRadioButton.isSelected(), exactRadioButton.isSelected()));
List<KeywordList> keywordLists = new ArrayList<>(); 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.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.Logger; 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.AbstractFile;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact; 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 * Adds keywords to a keyword list, returns true if at least one keyword was
* successfully added and no duplicates were found. * successfully added and no duplicates were found.
@ -151,6 +154,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
dupeCount = 0; dupeCount = 0;
badCount = 0; badCount = 0;
keywordsToRedisplay = ""; keywordsToRedisplay = "";
boolean displayedBoundaryWarning = false;
if (!dialog.getKeywords().isEmpty()) { if (!dialog.getKeywords().isEmpty()) {
@ -164,6 +168,19 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
dupeCount++; dupeCount++;
continue; 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 //check if valid
boolean valid = true; boolean valid = true;

View File

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

View File

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

View File

@ -288,7 +288,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
return ProcessResult.OK; return ProcessResult.OK;
} }
List<String> keywordListNames = settings.getNamesOfEnabledKeyWordLists(); List<String> keywordListNames = settings.getNamesOfEnabledKeyWordLists();
SearchRunner.getInstance().startJob(context, keywordListNames); IngestSearchRunner.getInstance().startJob(context, keywordListNames);
startedSearching = true; startedSearching = true;
} }
@ -309,13 +309,13 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
if (context.fileIngestIsCancelled()) { if (context.fileIngestIsCancelled()) {
logger.log(Level.INFO, "Keyword search ingest module instance {0} stopping search job due to ingest cancellation", instanceNum); //NON-NLS 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(); cleanup();
return; return;
} }
// Remove from the search list and trigger final commit and final search // 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 // We only need to post the summary msg from the last module per job
if (refCounter.decrementAndGet(jobId) == 0) { 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 // 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 // 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) + ":/" solrQuery.setQuery((field == null ? Server.Schema.CONTENT_STR.toString() : field) + ":/"
+ (queryStringContainsWildcardPrefix ? "" : ".*") + getQueryString() + (skipWildcardPrefix ? "" : ".*") + getQueryString()
+ (queryStringContainsWildcardSuffix ? "" : ".*") + "/"); + (skipWildcardSuffix ? "" : ".*") + "/");
// Set the fields we want to have returned by the query. // 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()); 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.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; 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.autopsy.keywordsearchservice.KeywordSearchServiceException;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
@ -708,7 +710,9 @@ public class Server {
if (null == currentCore) { if (null == currentCore) {
throw new NoOpenCoreException(); throw new NoOpenCoreException();
} }
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk");
currentCore.addDocument(doc); currentCore.addDocument(doc);
EnterpriseHealthMonitor.submitTimingMetric(metric);
} finally { } finally {
currentCoreLock.readLock().unlock(); currentCoreLock.readLock().unlock();
} }
@ -773,7 +777,9 @@ public class Server {
IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory());
currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS
} }
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check");
connectToSolrServer(currentSolrServer); connectToSolrServer(currentSolrServer);
EnterpriseHealthMonitor.submitTimingMetric(metric);
} catch (SolrServerException | IOException ex) { } catch (SolrServerException | IOException ex) {
throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
@ -1315,11 +1321,13 @@ public class Server {
* @throws IOException * @throws IOException
*/ */
void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException { void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException {
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check");
CoreAdminRequest statusRequest = new CoreAdminRequest(); CoreAdminRequest statusRequest = new CoreAdminRequest();
statusRequest.setCoreName( null ); statusRequest.setCoreName( null );
statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS ); statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS );
statusRequest.setIndexInfoNeeded(false); statusRequest.setIndexInfoNeeded(false);
statusRequest.process(solrServer); 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 * in less than roughly two seconds. This stuff should be reworked using
* an ExecutorService and tasks with Futures. * an ExecutorService and tasks with Futures.
*/ */
KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters(); AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters();
try { try {
Thread.sleep(2000); Thread.sleep(2000);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {

View File

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

View File

@ -30,14 +30,14 @@ install:
- ps: pushd C:\ - ps: pushd C:\
- git clone https://github.com/sleuthkit/sleuthkit - git clone https://github.com/sleuthkit/sleuthkit
- ps: popd - ps: popd
services:
- postgresql95
services:
- postgresql95
build_script: build_script:
- cd %TSK_HOME% - cd %TSK_HOME%
- python setupLibs.py - python setupDevRepos.py
- python win32\updateBuildLibs.py -m - python win32\updateAndBuildAll.py -m
- ps: pushd bindings/java - ps: pushd bindings/java
- ps: ant -version - ps: ant -version
- cmd: ant dist-PostgreSQL - 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}/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}/NEWS.txt" tofile="${zip-tmp}/${app.name}/NEWS.txt"/>
<copy file="${basedir}/KNOWN_ISSUES.txt" tofile="${zip-tmp}/${app.name}/KNOWN_ISSUES.txt"/> <copy file="${basedir}/KNOWN_ISSUES.txt" tofile="${zip-tmp}/${app.name}/KNOWN_ISSUES.txt"/>
<if> <copy file="${basedir}/unix_setup.sh" tofile="${zip-tmp}/${app.name}/unix_setup.sh"/>
<equals arg1="${os.family}" arg2="unix"/> <replaceregexp file="${zip-tmp}/${app.name}/unix_setup.sh" match="TSK_VERSION=(.*)" replace="TSK_VERSION=${TSK_VERSION}" byline="true"/>
<then> <unzip src="${thirdparty.dir}/gstreamer/windows/i386/0.10.7/gstreamer.zip" dest="${zip-tmp}/${app.name}/gstreamer"/>
<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>
<copy file="${basedir}/icons/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/> <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. 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. 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. > 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; "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 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 \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). 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 \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 tree_viewer_page
- \subpage result_viewer_page - \subpage result_viewer_page
- \subpage content_viewer_page - \subpage content_viewer_page
- \subpage quick_search
- \subpage image_gallery_page - \subpage image_gallery_page
- \subpage file_search_page - \subpage file_search_page
- \subpage ad_hoc_keyword_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. 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. 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 \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 __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 Visualization settings
---------------------- ----------------------

View File

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

View File

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