diff --git a/.travis.yml b/.travis.yml
index d8d478ed13..8cde465f7d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,7 +13,7 @@ before_install:
install:
- sudo apt-get install testdisk
- cd sleuthkit/sleuthkit
- - sh install-sleuthkit.sh
+ - sh travis_build.sh
script:
- set -e
- echo "building autopsy..." && echo -en 'travis_fold:start:script.build\\r'
diff --git a/Core/build.xml b/Core/build.xml
index 2a3bf41ee6..401d0dfb6b 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -86,7 +86,10 @@
-
+
+
+
+
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 02df4b01bb..0e683d15fd 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -18,7 +18,17 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi
file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
-file.reference.sleuthkit-postgresql-4.6.0.jar=release/modules/ext/sleuthkit-postgresql-4.6.0.jar
+file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar
+file.reference.jempbox-1.8.13.jar=release/modules/ext/jempbox-1.8.13.jar
+file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0.1.jar
+file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar
+file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar
+file.reference.cxf-rt-rs-client-3.0.16.jar=release/modules/ext/cxf-rt-rs-client-3.0.16.jar
+file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar
+file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar
+file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar
+file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar
+file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar
file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar
file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar
file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 0159ec9880..26676f0054 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -327,6 +327,7 @@
org.sleuthkit.autopsy.eventsorg.sleuthkit.autopsy.filesearchorg.sleuthkit.autopsy.guiutils
+ org.sleuthkit.autopsy.healthmonitororg.sleuthkit.autopsy.ingestorg.sleuthkit.autopsy.keywordsearchserviceorg.sleuthkit.autopsy.menuactions
@@ -347,8 +348,8 @@
release/modules/ext/jdom-2.0.5.jar
- ext/sleuthkit-postgresql-4.6.0.jar
- release/modules/ext/sleuthkit-postgresql-4.6.0.jar
+ ext/sleuthkit-postgresql-4.6.1.jar
+ release/modules/ext/sleuthkit-postgresql-4.6.1.jarext/opencv-248.jar
@@ -410,10 +411,46 @@
ext/curator-client-2.8.0.jarrelease/modules/ext/curator-client-2.8.0.jar
+
+ ext/jempbox-1.8.13.jar
+ release/modules/ext/jempbox-1.8.13.jar
+
+
+ ext/javax.ws.rs-api-2.0.1.jar
+ release/modules/ext/javax.ws.rs-api-2.0.1.jar
+
+
+ ext/cxf-rt-rs-client-3.0.16.jar
+ release/modules/ext/cxf-rt-rs-client-3.0.16.jar
+
+
+ ext/cxf-rt-transports-http-3.0.16.jar
+ release/modules/ext/cxf-rt-transports-http-3.0.16.jar
+
+
+ ext/cxf-core-3.0.16.jar
+ release/modules/ext/cxf-core-3.0.16.jar
+
+
+ ext/cxf-rt-frontend-jaxrs-3.0.16.jar
+ release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar
+ ext/tika-parsers-1.17.jarrelease/modules/ext/tika-parsers-1.17.jar
+
+ ext/fontbox-2.0.8.jar
+ release/modules/ext/fontbox-2.0.8.jar
+
+
+ ext/pdfbox-2.0.8.jar
+ release/modules/ext/pdfbox-2.0.8.jar
+
+
+ ext/pdfbox-tools-2.0.8.jar
+ release/modules/ext/pdfbox-tools-2.0.8.jar
+ ext/sqlite-jdbc-3.8.11.jarrelease/modules/ext/sqlite-jdbc-3.8.11.jar
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 65e6c5ab42..11f3fa90fd 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -1543,8 +1543,8 @@ public class Case {
* @throws TskCoreException if there is a problem adding the report to the
* case database.
*/
- public Report addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
- return addReport(localPath, srcModuleName, reportName, null);
+ public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
+ addReport(localPath, srcModuleName, reportName, null);
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java
index 6ef03ae00d..2f732ac60f 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java
@@ -68,7 +68,17 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter {
@Override
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
diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java
index 7b90357993..dca1770463 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java
@@ -128,7 +128,7 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov
public void propertyChange(PropertyChangeEvent pce) {
if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes();
- messagesResultPanel.setNumMatches(0);
+ messagesResultPanel.setNumberOfChildNodes(0);
messagesResultPanel.setNode(null);
messagesResultPanel.setPath("");
if (selectedNodes.length > 0) {
diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
index 4f6b53ae39..f3f77fbef4 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
@@ -208,11 +208,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
applyLayout(fastOrganicLayout);
}
- /**
- *
- * @param layoutButton the value of layoutButton
- * @param layout the value of layout
- */
@Override
public Lookup getLookup() {
return proxyLookup;
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
index 960f90f39e..8726cfedb6 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
@@ -98,7 +98,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
@NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments")
public MessageContentViewer() {
initComponents();
- drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", Node.EMPTY, 0, null);
+ drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null);
attachmentsScrollPane.setViewportView(drp);
msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true);
diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java
index f52ed088ae..6272cc9ab7 100644
--- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java
+++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java
@@ -455,7 +455,8 @@ public final class CoordinationService {
CASES("cases"),
MANIFESTS("manifests"),
CONFIG("config"),
- CENTRAL_REPO("centralRepository");
+ CENTRAL_REPO("centralRepository"),
+ HEALTH_MONITOR("healthMonitor");
private final String displayName;
diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java
index 83250f719d..63ac880171 100644
--- a/Core/src/org/sleuthkit/autopsy/core/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java
@@ -216,6 +216,7 @@ public class Installer extends ModuleInstall {
packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault());
+ packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault());
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java
index 191a851b9a..6af30b8729 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,41 +22,58 @@ import java.util.List;
import org.openide.nodes.Node;
/**
- * The interface for the "top right component" window.
+ * An interface for result view components. A result view component provides
+ * multiple views of the application data represented by a given NetBeans Node.
+ * The differing views of the node are supplied by a collection of result
+ * viewers (implementations of the DataResultViewer interface).
*
+ * A typical implementation of this interface are the NetBeans TopComponents
+ * (DataResultTopComponents) that use a child result view component
+ * (DataResultPanel) for displaying their result viewers, and are docked into
+ * the upper right hand side (editor mode) of the main application window.
*/
public interface DataResult {
/**
- * Sets the "selected" node in this class.
+ * Sets the node for which this result view component should provide
+ * multiple views of the underlying application data.
+ *
+ * @param node The node, may be null. If null, the call to this method is
+ * equivalent to a call to resetComponent on this result view
+ * component's result viewers.
*/
- public void setNode(Node selectedNode);
+ public void setNode(Node node);
/**
- * Gets the unique TopComponent ID of this class.
+ * Gets the preferred identifier for this result view panel in the window
+ * system.
*
- * @return preferredID the unique ID
+ * @return The preferred identifier.
*/
public String getPreferredID();
/**
- * Sets the title of this TopComponent
+ * Sets the title of this result view component.
*
- * @param title the given title (String)
+ * @param title The title.
*/
public void setTitle(String title);
/**
- * Sets the descriptive context text at the top of the pane.
+ * Sets the descriptive text about the source of the nodes displayed in this
+ * result view component.
*
- * @param pathText Descriptive text giving context for the current results
+ * @param description The text to display.
*/
public void setPath(String pathText);
/**
- * Checks if this is the main (uncloseable) instance of DataResult
+ * Gets whether or not this result view panel is the "main" result view
+ * panel used to view the child nodes of a node selected in the application
+ * tree view (DirectoryTreeTopComponent) that is normally docked into the
+ * left hand side of the main window.
*
- * @return true if it is the main instance, otherwise false
+ * @return True or false.
*/
public boolean isMain();
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java
index d88c979844..c9d305f53e 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-17 Basis Technology Corp.
+ * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,76 +22,120 @@ import java.awt.Component;
import org.openide.nodes.Node;
/**
- * Interface for the different viewers that show a set of nodes in the
- * DataResult area. AbstractDataResultViewer has default implementations for the
- * action handlers.
+ * An interface for result viewers. A result viewer uses a Swing Component to
+ * provide a view of the application data represented by a NetBeans Node passed
+ * to it via its setNode method.
*
+ * Result viewers are most commonly presented as a tab in a result view panel
+ * (DataResultPanel) inside a result view top component (DataResultTopComponent)
+ * that is docked into the upper right hand side (editor mode) of the main
+ * application window.
+ *
+ * A result viewer is typically a JPanel that displays the child nodes of the
+ * given node using a NetBeans explorer view child component. Such a result
+ * viewer should use the explorer manager of the ancestor top component to
+ * connect the lookups of the nodes displayed in the NetBeans explorer view to
+ * the actions global context. It is strongly recommended that this type of
+ * result viewer is implemented by extending the abstract base class
+ * AbstractDataResultViewer, which will handle some key aspects of working with
+ * the ancestor top component's explorer manager.
+ *
+ * This interface is an extension point, so classes that implement it should
+ * provide an appropriate ServiceProvider annotation.
*/
public interface DataResultViewer {
/**
- * Set the root node to display in this viewer. When called with null, must
- * clear all references to previous nodes.
- */
- public void setNode(Node selectedNode);
-
- /**
- * Gets the title of this viewer
- */
- public String getTitle();
-
- /**
- * Get a new instance of DataResultViewer
+ * Creates a new instance of this result viewer, which allows the
+ * application to use the capability provided by this result viewer in more
+ * than one result view. This is done by using the default instance of this
+ * result viewer as a "factory" for creating other instances.
*/
public DataResultViewer createInstance();
/**
- * Get the Swing component (i.e. JPanel) for this viewer
+ * Indicates whether this result viewer is able to provide a meaningful view
+ * of the application data represented by a given node. Typically, indicates
+ * whether or not this result viewer can use the given node as the root node
+ * of its child explorer view component.
+ *
+ * @param node The node.
+ *
+ * @return True or false.
+ */
+ public boolean isSupported(Node node);
+
+ /**
+ * Sets the node for which this result viewer should provide a view of the
+ * underlying application data. Typically, this means using the given node
+ * as the root node of this result viewer's child explorer view component.
+ *
+ * @param node The node, may be null. If null, the call to this method is
+ * equivalent to a call to resetComponent.
+ */
+ public void setNode(Node node);
+
+ /**
+ * Requests selection of the given child nodes of the node passed to
+ * setNode. This method should be implemented as a no-op for result viewers
+ * that do not display the child nodes of a given root node using a NetBeans
+ * explorer view set up to use a given explorer manager.
+ *
+ * @param selectedNodes The child nodes to select.
+ */
+ default public void setSelectedNodes(Node[] selectedNodes) {
+ }
+
+ /**
+ * Gets the title of this result viewer.
+ *
+ * @return The title.
+ */
+ public String getTitle();
+
+ /**
+ * Gets the Swing component for this viewer.
+ *
+ * @return The component.
*/
public Component getComponent();
/**
- * Resets the viewer.
+ * Resets the state of the Swing component for this viewer to its default
+ * state.
*/
- public void resetComponent();
+ default public void resetComponent() {
+ }
/**
- * Frees the objects that have been allocated by this viewer, in preparation
- * for permanently disposing of it.
+ * Frees any resources tha have been allocated by this result viewer, in
+ * preparation for permanently disposing of it.
*/
- public void clearComponent();
+ default public void clearComponent() {
+ }
/**
- * Expand node, if supported by the viewed
+ * Sets the node for which this result viewer should provide a view of the
+ * underlying application data model object, and expands the node.
*
- * @param n Node to expand
- */
- public void expandNode(Node n);
-
- /**
- * Select the given node array
- */
- public void setSelectedNodes(Node[] selected);
-
- /**
- * Checks whether the currently selected root node is supported by this
- * viewer
+ * @param node The node.
*
- * @param selectedNode the selected node
- *
- * @return True if supported, else false
- */
- public boolean isSupported(Node selectedNode);
-
- /**
- * Set a custom content viewer to respond to selection events from this
- * result viewer. If not set, the default content viewer is used
- *
- * @param contentViewer content viewer to respond to selection events from
- * this viewer
- *
- * @deprecated All implementations of this in the standard DataResultViewers are now no-ops.
+ * @deprecated This API is not used by the application.
*/
@Deprecated
- public void setContentViewer(DataContent contentViewer);
+ default public void expandNode(Node node) {
+ }
+
+ /**
+ * Sets a custom content viewer to which nodes selected in this result
+ * viewer should be pushed via DataContent.setNode.
+ *
+ * @param contentViewer The content viewer.
+ *
+ * @deprecated This API is not used by the application.
+ */
+ @Deprecated
+ default public void setContentViewer(DataContent contentViewer) {
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java
index 26d58d6eff..fdabf16ac0 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-17 Basis Technology Corp.
+ * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,65 +23,64 @@ import java.beans.PropertyVetoException;
import java.util.logging.Level;
import javax.swing.JPanel;
import org.openide.explorer.ExplorerManager;
-import org.openide.explorer.ExplorerManager.Provider;
import org.openide.nodes.Node;
-import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
- * This class provides a default implementation of selected methods of the
- * DataResultViewer interface. Derived classes will be Swing JPanel objects.
- * Additionally, the ExplorerManager.Provider interface is implemented to supply
- * an ExplorerManager to derived classes and their child components.
+ * An abstract base class for an implementation of the result viewer interface
+ * that is a JPanel that displays the child nodes of a given node using a
+ * NetBeans explorer view as a child component. Such a result viewer should use
+ * the explorer manager of an ancestor top component to connect the lookups of
+ * the nodes displayed in the NetBeans explorer view to the actions global
+ * context. This class handles some key aspects of working with the ancestor top
+ * component's explorer manager.
+ *
+ * Instances of this class can be supplied with the top component's explorer
+ * manager during construction, but the typical use case is for the result
+ * viewer to find the ancestor top component's explorer manager at runtime.
+ *
+ * IMPORTANT: If the result viewer is going to find the ancestor top component's
+ * explorer manager at runtime, the first call to the getExplorerManager method
+ * of this class must be made AFTER the component hierarchy is fully
+ * constructed.
+ *
*/
-abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider {
+public abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, ExplorerManager.Provider {
private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName());
- protected transient ExplorerManager em;
+ private transient ExplorerManager explorerManager;
/**
- * This constructor is intended to allow an AbstractDataResultViewer to use
- * an ExplorerManager provided by a TopComponent, allowing Node selections
- * to be available to Actions via the action global context lookup when the
- * TopComponent has focus. The ExplorerManager must be present when the
- * object is constructed so that its child components can discover it using
- * the ExplorerManager.find() method.
+ * Constructs an abstract base class for an implementation of the result
+ * viewer interface that is a JPanel that displays the child nodes of the
+ * given node using a NetBeans explorer view as a child component.
*
- * @param explorerManager
+ * @param explorerManager The explorer manager to use in the NetBeans
+ * explorer view child component of this result
+ * viewer, may be null. If null, the explorer manager
+ * will be discovered the first time
+ * getExplorerManager is called.
*/
- AbstractDataResultViewer(ExplorerManager explorerManager) {
- this.em = explorerManager;
- }
-
- /**
- * This constructor can be used by AbstractDataResultViewers that do not
- * need to make Node selections available to Actions via the action global
- * context lookup.
- */
- public AbstractDataResultViewer() {
- this(new ExplorerManager());
+ public AbstractDataResultViewer(ExplorerManager explorerManager) {
+ this.explorerManager = explorerManager;
}
@Override
- public void clearComponent() {
- }
-
- public Node getSelectedNode() {
- Node result = null;
- Node[] selectedNodes = this.getExplorerManager().getSelectedNodes();
- if (selectedNodes.length > 0) {
- result = selectedNodes[0];
+ public ExplorerManager getExplorerManager() {
+ if (this.explorerManager == null) {
+ this.explorerManager = ExplorerManager.find(this);
}
- return result;
+ return this.explorerManager;
}
@Override
- public void expandNode(Node n) {
- }
-
- @Override
- public void resetComponent() {
+ public void setSelectedNodes(Node[] selected) {
+ try {
+ this.getExplorerManager().setSelectedNodes(selected);
+ } catch (PropertyVetoException ex) {
+ logger.log(Level.SEVERE, "Couldn't set selected nodes", ex); //NON-NLS
+ }
}
@Override
@@ -89,22 +88,4 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView
return this;
}
- @Override
- public ExplorerManager getExplorerManager() {
- return this.em;
- }
-
- @Override
- public void setSelectedNodes(Node[] selected) {
- try {
- this.em.setSelectedNodes(selected);
- } catch (PropertyVetoException ex) {
- logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS
- }
- }
-
- @Deprecated
- @Override
- public void setContentViewer(DataContent contentViewer) {
- }
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 245fe1744d..c2a9d9845f 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -62,9 +62,6 @@ DataResultViewerThumbnail.filePathLabel.text=\ \ \
DataResultViewerThumbnail.goToPageLabel.text=Go to Page:
DataResultViewerThumbnail.goToPageField.text=
AdvancedConfigurationDialog.cancelButton.text=Cancel
-DataResultPanel.directoryTablePath.text=directoryPath
-DataResultPanel.numberMatchLabel.text=0
-DataResultPanel.matchLabel.text=Results
DataContentViewerArtifact.waitText=Retrieving and preparing data, please wait...
DataContentViewerArtifact.errorText=Error retrieving result
DataContentViewerArtifact.title=Results
@@ -181,3 +178,6 @@ AutopsyOptionsPanel.agencyLogoPreview.text=
resultViewers = new ArrayList<>();
- private boolean isMain;
- private ExplorerManager explorerManager;
- private ExplorerManagerNodeSelectionListener emNodeSelectionListener;
- private Node rootNode;
- private final RootNodeListener rootNodeListener = new RootNodeListener();
- private boolean listeningToTabbedPane;
+ private final boolean isMain;
+ private final List resultViewers;
+ private final ExplorerManagerListener explorerManagerListener;
+ private final RootNodeListener rootNodeListener;
private DataContent contentView;
+ private ExplorerManager explorerManager;
+ private Node currentRootNode;
+ private boolean listeningToTabbedPane;
/**
- * Constructs and opens a DataResultPanel with the given initial data, and
- * the default DataContent.
+ * Creates and opens a Swing JPanel with a JTabbedPane child component that
+ * contains instances of the result viewers (DataResultViewer) provided by
+ * the result viewer extension point (service providers that implement
+ * DataResultViewer). The result view panel will push single node selections
+ * from its child result viewers to the "main" content view that is normally
+ * docked into the lower right hand side of the main application window.
*
- * @param title The title for the panel.
- * @param pathText Descriptive text about the source of the nodes
- * 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
+ * @param title The title for the result view panel.
+ * @param description Descriptive text about the source of the nodes
* displayed.
- * @param rootNode The new root node.
- * @param totalMatches Cardinality of root node's children
- * @param resultViewPanel A content view to use in place of the default
- * content view.
+ * @param currentRootNode The current root (parent) node for the nodes
+ * displayed. May be changed by calling setNode.
+ * @param childNodeCount The cardinality of the root node's children.
+ *
+ * @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 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.setName(title);
- resultViewPanel.setNumMatches(totalMatches);
- resultViewPanel.setNode(rootNode);
- resultViewPanel.setPath(pathText);
+ resultViewPanel.setNumberOfChildNodes(childNodeCount);
+ resultViewPanel.setNode(currentRootNode);
+ resultViewPanel.setPath(description);
}
/**
- * Constructs a DataResultPanel with the default DataContent
+ * Constructs a Swing JPanel with a JTabbedPane child component that
+ * contains a collection of result viewers that is either supplied or
+ * provided by the result viewer extension point.
*
- * @param title The title for the panel.
- * @param isMain True if the DataResultPanel being constructed is the "main"
- * DataResultPanel.
+ * @param title The title of the result view panel.
+ * @param isMain Whether or not the result view panel is the
+ * "main" instance of the panel that resides in the
+ * "main" results view (DataResultTopComponent)
+ * that is normally docked into the upper right
+ * hand side of the main application window.
+ * @param viewers A collection of result viewers to use instead of
+ * the result viewers provided by the results
+ * viewer extension point, may be empty.
+ * @param customContentView A custom content view to use instead of the
+ * "main" content view that is normally docked into
+ * the lower right hand side of the main
+ * application window, may be null.
*/
- DataResultPanel(String title, boolean isMain) {
- this(isMain, Lookup.getDefault().lookup(DataContent.class));
- setTitle(title);
- }
-
- private DataResultPanel(boolean isMain, DataContent contentView) {
+ DataResultPanel(String title, boolean isMain, Collection viewers, DataContent customContentView) {
+ this.setTitle(title);
this.isMain = isMain;
- this.contentView = contentView;
+ if (customContentView == null) {
+ this.contentView = Lookup.getDefault().lookup(DataContent.class);
+ } else {
+ this.contentView = customContentView;
+ }
+ this.resultViewers = new ArrayList<>(viewers);
+ this.explorerManagerListener = new ExplorerManagerListener();
+ this.rootNodeListener = new RootNodeListener();
initComponents();
}
/**
- * Constructs a DataResultPanel with the a custom DataContent.
- *
- * @param title The title for the panel.
- * @param customContentView A content view to use in place of the default
- * content view.
- */
- DataResultPanel(String title, DataContent customContentView) {
- this(false, customContentView);
- }
-
- /**
- * Gets the preferred identifier for this panel in the window system.
+ * Gets the preferred identifier for this result view panel in the window
+ * system.
*
* @return The preferred identifier.
*/
@@ -200,19 +252,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/**
- * Gets whether or not this panel is the "main" panel used to view the child
- * nodes of a node selected in the tree view (DirectoryTreeTopComponent)
- * that is normally docked into the left hand side of the main window.
- *
- * @return True or false.
- */
- @Override
- public boolean isMain() {
- return this.isMain;
- }
-
- /**
- * Sets the title of this panel.
+ * Sets the title of this result view panel.
*
* @param title The title.
*/
@@ -223,27 +263,27 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
/**
* Sets the descriptive text about the source of the nodes displayed in this
- * panel.
+ * result view panel.
*
- * @param pathText The text to display.
+ * @param description The text to display.
*/
@Override
- public void setPath(String pathText) {
- this.directoryTablePath.setText(pathText);
+ public void setPath(String description) {
+ this.descriptionLabel.setText(description);
}
/**
- * Adds a result viewer to this panel.
+ * Adds a results viewer to this result view panel.
*
- * @param resultViewer The result viewer.
+ * @param resultViewer The results viewer.
*/
public void addResultViewer(DataResultViewer resultViewer) {
resultViewers.add(resultViewer);
- dataResultTabbedPanel.addTab(resultViewer.getTitle(), resultViewer.getComponent());
+ resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent());
}
/**
- * Gets the result viewers for this panel.
+ * Gets the result viewers for this result view panel.
*
* @return A list of result viewers.
*/
@@ -253,8 +293,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/**
- * Sets the content view for this panel. Needs to be called before the first
- * call to open.
+ * Sets the content view for this result view panel. Needs to be called
+ * before the first call to open.
*
* @param customContentView A content view to use in place of the default
* content view.
@@ -264,67 +304,60 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/**
- * Initializes this panel. Intended to be called by a parent top component
+ * Opens this result view panel. Should be called by a parent top component
* when the top component is opened.
*/
public void open() {
- if (null == explorerManager) {
- /*
- * Get an explorer manager to pass to the child result viewers. If
- * the application components are put together as expected, this
- * will be an explorer manager owned by a parent top component, and
- * placed by the top component in the look up that is proxied as the
- * action global context when the top component has focus. The
- * sharing of this explorer manager enables the same child node
- * selections to be made in all of the result viewers.
- */
- explorerManager = ExplorerManager.find(this);
- emNodeSelectionListener = new ExplorerManagerNodeSelectionListener();
- explorerManager.addPropertyChangeListener(emNodeSelectionListener);
+ /*
+ * The parent top component is expected to be an explorer manager
+ * provider that exposes a lookup maintained by its explorer manager to
+ * the actions global context. The child result view panel will then
+ * find the parent top component's explorer manager at runtime, so that
+ * it can act as an explorer manager provider for its child result
+ * viewers. This connects the nodes displayed in the result viewers to
+ * the actions global context.
+ */
+ if (this.explorerManager == null) {
+ this.explorerManager = ExplorerManager.find(this);
+ this.explorerManager.addPropertyChangeListener(this.explorerManagerListener);
}
/*
- * Load the child result viewers into the tabbed pane.
+ * Load either the supplied result viewers or the result viewers
+ * provided by the result viewer extension point into the tabbed pane.
+ * If loading from the extension point and distinct result viewer
+ * instances MUST be created if this is not the "main" result view.
*/
- if (0 == dataResultTabbedPanel.getTabCount()) {
- /*
- * TODO (JIRA-2658): Fix the DataResultViewer extension point. When
- * this is done, restore the implementation of DataResultViewerTable
- * and DataREsultViewerThumbnail as DataResultViewer service
- * providers.
- */
- addResultViewer(new DataResultViewerTable(this.explorerManager));
- //addResultViewer(new DataResultViewerThumbnail(this.explorerManager));
- for (DataResultViewer factory : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
- DataResultViewer resultViewer;
- if (isMain) {
- resultViewer = factory;
- } else {
- resultViewer = factory.createInstance();
+ if (this.resultViewerTabs.getTabCount() == 0) {
+ if (this.resultViewers.isEmpty()) {
+ for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
+ if (this.isMain) {
+ this.resultViewers.add(resultViewer);
+ } else {
+ this.resultViewers.add(resultViewer.createInstance());
+ }
}
- addResultViewer(resultViewer);
}
- }
-
- if (isMain && null == rootNode) {
- setNode(rootNode);
+ this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()));
}
this.setVisible(true);
}
/**
- * Sets the root node for this panel. The child nodes of the root node will
- * be displayed in the result viewers. For the "main" panel, the root node
- * is the currently selected node in the tree view docked into the left side
- * of the main application window.
+ * Sets the current root node for this result view panel. The child nodes of
+ * the current root node will be displayed in the child result viewers. For
+ * the "main" panel, the root node is the currently selected node in the
+ * application tree view docked into the left side of the main application
+ * window.
*
- * @param rootNode The root node for this panel.
+ * @param rootNode The root node for this panel, may be null if the panel is
+ * to be reset.
*/
@Override
public void setNode(Node rootNode) {
- if (this.rootNode != null) {
- this.rootNode.removeNodeListener(rootNodeListener);
+ if (this.currentRootNode != null) {
+ this.currentRootNode.removeNodeListener(rootNodeListener);
}
/*
@@ -333,53 +366,54 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* construction.
*/
if (listeningToTabbedPane == false) {
- dataResultTabbedPanel.addChangeListener(this);
+ resultViewerTabs.addChangeListener(this);
listeningToTabbedPane = true;
}
- this.rootNode = rootNode;
- if (this.rootNode != null) {
+ this.currentRootNode = rootNode;
+ if (this.currentRootNode != null) {
rootNodeListener.reset();
- this.rootNode.addNodeListener(rootNodeListener);
+ this.currentRootNode.addNodeListener(rootNodeListener);
}
- resetTabs(this.rootNode);
- setupTabs(this.rootNode);
+ this.resultViewers.forEach((viewer) -> {
+ viewer.resetComponent();
+ });
+ setupTabs(this.currentRootNode);
- if (null != this.rootNode) {
- int childrenCount = this.rootNode.getChildren().getNodesCount();
- this.numberMatchLabel.setText(Integer.toString(childrenCount));
+ if (this.currentRootNode != null) {
+ int childrenCount = this.currentRootNode.getChildren().getNodesCount();
+ this.numberOfChildNodesLabel.setText(Integer.toString(childrenCount));
}
- this.numberMatchLabel.setVisible(true);
+ this.numberOfChildNodesLabel.setVisible(true);
}
/**
- * Gets the root node of this panel. For the "main" panel, the root node is
- * the currently selected node in the tree view docked into the left side of
- * the main application window.
+ * Gets the root node of this result view panel. For the "main" panel, the
+ * root node is the currently selected node in the application tree view
+ * docked into the left side of the main application window.
*
* @return The root node.
*/
public Node getRootNode() {
- return rootNode;
+ return currentRootNode;
}
/**
- * Set number of child nodes displayed for the current root node.
+ * Sets the label text that displays the number of the child nodes displayed
+ * by this result view panel's result viewers.
*
- * @param numberOfChildNodes
+ * @param numberOfChildNodes The number of child nodes.
*/
- public void setNumMatches(Integer numberOfChildNodes) {
- if (this.numberMatchLabel != null) {
- this.numberMatchLabel.setText(Integer.toString(numberOfChildNodes));
- }
+ public void setNumberOfChildNodes(Integer numberOfChildNodes) {
+ this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes));
}
/**
- * Sets the children of the root node that should be currently selected in
- * this panel's result viewers.
+ * Selects the given child nodes of the root node in this panel's result
+ * viewers.
*
- * @param selectedNodes The nodes to be selected.
+ * @param selectedNodes The child nodes to be selected.
*/
public void setSelectedNodes(Node[] selectedNodes) {
this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes));
@@ -396,11 +430,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* Enable or disable the result viewer tabs based on whether or not the
* corresponding results viewer supports display of the selected node.
*/
- for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) {
+ for (int i = 0; i < resultViewerTabs.getTabCount(); i++) {
if (resultViewers.get(i).isSupported(selectedNode)) {
- dataResultTabbedPanel.setEnabledAt(i, true);
+ resultViewerTabs.setEnabledAt(i, true);
} else {
- dataResultTabbedPanel.setEnabledAt(i, false);
+ resultViewerTabs.setEnabledAt(i, false);
}
}
@@ -415,17 +449,17 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) {
for (int i = 0; i < resultViewers.size(); ++i) {
- if (resultViewers.get(i) instanceof DataResultViewerTable && dataResultTabbedPanel.isEnabledAt(i)) {
+ if (resultViewers.get(i) instanceof DataResultViewerTable && resultViewerTabs.isEnabledAt(i)) {
tabToSelect = i;
}
}
}
};
- if (NO_TAB_SELECTED == tabToSelect) {
- tabToSelect = dataResultTabbedPanel.getSelectedIndex();
- if ((NO_TAB_SELECTED == tabToSelect) || (!dataResultTabbedPanel.isEnabledAt(tabToSelect))) {
- for (int i = 0; i < dataResultTabbedPanel.getTabCount(); ++i) {
- if (dataResultTabbedPanel.isEnabledAt(i)) {
+ if (tabToSelect == NO_TAB_SELECTED) {
+ tabToSelect = resultViewerTabs.getSelectedIndex();
+ if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) {
+ for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) {
+ if (resultViewerTabs.isEnabledAt(i)) {
tabToSelect = i;
break;
}
@@ -434,27 +468,15 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
/*
- * If there is a tab to sele3ct, do so, and push the selected node to
- * the corresponding result viewer.
+ * If there is a tab to select, do so, and push the selected node to the
+ * corresponding result viewer.
*/
- if (NO_TAB_SELECTED != tabToSelect) {
- dataResultTabbedPanel.setSelectedIndex(tabToSelect);
+ if (tabToSelect != NO_TAB_SELECTED) {
+ resultViewerTabs.setSelectedIndex(tabToSelect);
resultViewers.get(tabToSelect).setNode(selectedNode);
}
}
- /**
- * Resets the state of the child result viewers, based on a selected root
- * node.
- *
- * @param unusedSelectedNode The selected node.
- */
- public void resetTabs(Node unusedSelectedNode) {
- this.resultViewers.forEach((viewer) -> {
- viewer.resetComponent();
- });
- }
-
/**
* Responds to a tab selection changed event by setting the root node of the
* corresponding result viewer.
@@ -465,57 +487,34 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
public void stateChanged(ChangeEvent event) {
JTabbedPane pane = (JTabbedPane) event.getSource();
int currentTab = pane.getSelectedIndex();
- if (-1 != currentTab) {
+ if (currentTab != DataResultPanel.NO_TAB_SELECTED) {
DataResultViewer currentViewer = this.resultViewers.get(currentTab);
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
- currentViewer.setNode(rootNode);
+ currentViewer.setNode(currentRootNode);
} finally {
- this.setCursor(null);
+ this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
}
/**
- * Indicates whether or not this panel can be closed at the time of the
- * call.
- *
- * @return True or false.
- */
- public boolean canClose() {
- /*
- * If this is the "main" panel, only allow it to be closed when no case
- * is open or no there are no data sources in the current case.
- */
- Case openCase;
- try {
- openCase = Case.getOpenCase();
- } catch (NoCurrentCaseException ex) {
- return true;
- }
- return (!this.isMain) || openCase.hasData() == false;
- }
-
- /**
- * Closes down the component. Intended to be called by the parent top
+ * Closes this reult view panel. Intended to be called by the parent top
* component when it is closed.
*/
void close() {
- if (null != explorerManager && null != emNodeSelectionListener) {
- explorerManager.removePropertyChangeListener(emNodeSelectionListener);
+ if (explorerManager != null && explorerManagerListener != null) {
+ explorerManager.removePropertyChangeListener(explorerManagerListener);
explorerManager = null;
}
this.resultViewers.forEach((viewer) -> viewer.setNode(null));
- if (!this.isMain) {
+ if (!this.isMain) { // RJCTODO: What?
this.resultViewers.forEach(DataResultViewer::clearComponent);
- this.directoryTablePath.removeAll();
- this.directoryTablePath = null;
- this.numberMatchLabel.removeAll();
- this.numberMatchLabel = null;
+ this.descriptionLabel.removeAll();
+ this.numberOfChildNodesLabel.removeAll();
this.matchLabel.removeAll();
- this.matchLabel = null;
this.setLayout(null);
this.removeAll();
this.setVisible(false);
@@ -525,50 +524,37 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
@Override
public ExplorerManager getExplorerManager() {
return explorerManager;
+
}
/**
- * Responds to node selection change events from the explorer manager.
+ * Responds to node selection change events from the explorer manager of
+ * this panel's parent top component. The selected nodes are passed to the
+ * content view. This is how the results view and the content view are kept
+ * in sync. It is therefore required that all of the result viewers in this
+ * panel use the explorer manager of the parent top component. This supports
+ * this way of passing the selection to the content view, plus the exposure
+ * of the selection to through the actions global context, which is needed
+ * for multiple selection.
*/
- private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener {
+ private class ExplorerManagerListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
- try {
- Case.getOpenCase();
- } catch (NoCurrentCaseException ex) {
- return;
- }
-
- /*
- * Only interested in node selection events.
- */
- if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
- setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- try {
- if (contentView != null) {
- Node[] selectedNodes = explorerManager.getSelectedNodes();
-
- /*
- * Pass the selected nodes to all of the result viewers
- * sharing this explorer manager.
- */
- resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes));
-
- /*
- * Passing null signals that either multiple nodes are
- * selected, or no nodes are selected. This is important
- * to the content view, since content views only work
- * for a single node..
- */
- if (1 == selectedNodes.length) {
- contentView.setNode(selectedNodes[0]);
- } else {
- contentView.setNode(null);
- }
- }
- } finally {
- setCursor(null);
+ if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) {
+ /*
+ * Pass a single node selection in a result viewer to the
+ * content view. Note that passing null to the content view
+ * signals that either multiple nodes are selected, or a
+ * previous selection has been cleared. This is important to the
+ * content view, since its child content viewers only work for a
+ * single node.
+ */
+ Node[] selectedNodes = explorerManager.getSelectedNodes();
+ if (1 == selectedNodes.length) {
+ contentView.setNode(selectedNodes[0]);
+ } else {
+ contentView.setNode(null);
}
}
}
@@ -598,6 +584,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* Responds to changes in the root node due to asynchronous child node
* creation.
*/
+ // RJCTODO: Why do we need this?
private class RootNodeListener implements NodeListener {
private volatile boolean waitingForData = true;
@@ -638,8 +625,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
*
*/
private void updateMatches() {
- if (rootNode != null && rootNode.getChildren() != null) {
- setNumMatches(rootNode.getChildren().getNodesCount());
+ if (currentRootNode != null && currentRootNode.getChildren() != null) {
+ setNumMatches(currentRootNode.getChildren().getNodesCount());
}
}
@@ -670,52 +657,93 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
// //GEN-BEGIN:initComponents
private void initComponents() {
- directoryTablePath = new javax.swing.JLabel();
- numberMatchLabel = new javax.swing.JLabel();
+ descriptionLabel = new javax.swing.JLabel();
+ numberOfChildNodesLabel = new javax.swing.JLabel();
matchLabel = new javax.swing.JLabel();
- dataResultTabbedPanel = new javax.swing.JTabbedPane();
+ resultViewerTabs = new javax.swing.JTabbedPane();
setMinimumSize(new java.awt.Dimension(0, 5));
setPreferredSize(new java.awt.Dimension(5, 5));
- org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N
- directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14));
+ org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N
+ descriptionLabel.setMinimumSize(new java.awt.Dimension(5, 14));
- org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N
- dataResultTabbedPanel.setMinimumSize(new java.awt.Dimension(0, 5));
+ resultViewerTabs.setMinimumSize(new java.awt.Dimension(0, 5));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(numberMatchLabel)
+ .addComponent(numberOfChildNodesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(matchLabel))
- .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(numberMatchLabel)
+ .addComponent(numberOfChildNodesLabel)
.addComponent(matchLabel))
- .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(0, 0, 0)
- .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
- private javax.swing.JTabbedPane dataResultTabbedPanel;
- private javax.swing.JLabel directoryTablePath;
+ private javax.swing.JLabel descriptionLabel;
private javax.swing.JLabel matchLabel;
- private javax.swing.JLabel numberMatchLabel;
+ private javax.swing.JLabel numberOfChildNodesLabel;
+ private javax.swing.JTabbedPane resultViewerTabs;
// End of variables declaration//GEN-END:variables
+ /**
+ * Gets whether or not this result view panel is the "main" result view
+ * panel used to view the child nodes of a node selected in the application
+ * tree view (DirectoryTreeTopComponent) that is normally docked into the
+ * left hand side of the main window.
+ *
+ * @return True or false.
+ *
+ * @Deprecated This method has no valid use case.
+ */
+ @Deprecated
+ @Override
+ public boolean isMain() {
+ return this.isMain;
+ }
+
+ /**
+ * Sets the label text that displays the number of the child nodes displayed
+ * by this result view panel's result viewers.
+ *
+ * @param numberOfChildNodes The number of child nodes.
+ *
+ * @deprecated Use setNumberOfChildNodes instead.
+ */
+ @Deprecated
+ public void setNumMatches(Integer numberOfChildNodes) {
+ this.setNumberOfChildNodes(numberOfChildNodes);
+ }
+
+ /**
+ * Resets the state of this results panel.
+ *
+ * @param unusedSelectedNode Unused.
+ *
+ * @deprecated Use setNode(null) instead.
+ */
+ @Deprecated
+ public void resetTabs(Node unusedSelectedNode) {
+ this.setNode(null);
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form
index cb4669a51e..755df0fcf0 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form
@@ -28,10 +28,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
index 88ad19fa25..47982bb059 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.corecomponents;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
@@ -39,161 +40,220 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
- * Top component which displays results (top-right editor mode by default).
+ * A DataResultTopComponent object is a NetBeans top component that provides
+ * multiple views of the application data represented by a NetBeans Node. It is
+ * a result view component (implements DataResult) that contains a result view
+ * panel (DataResultPanel), which is also a result view component. The result
+ * view panel is a JPanel with a JTabbedPane child component that contains a
+ * collection of result viewers. Each result viewer (implements
+ * DataResultViewer) presents a different view of the current node. Result
+ * viewers are usually JPanels that display the child nodes of the current node
+ * using a NetBeans explorer view child component. The result viewers are either
+ * supplied during construction of the result view top component or provided by
+ * the result viewer extension point (service providers that implement
+ * DataResultViewer).
*
- * There is a main tc instance that responds to directory tree selections.
- * Others can also create an additional result viewer tc using one of the
- * factory methods, that can be:
+ * Result view top components are typically docked into the upper right hand
+ * side of the main application window (editor mode), and are linked to the
+ * content view in the lower right hand side of the main application window
+ * (output mode) by the result view panel. The panel passes single node
+ * selections in the active result viewer to the content view.
*
- * - added to top-right corner as an additional, closeable viewer - added to a
- * different, custom mode, - linked to a custom content viewer that responds to
- * selections from this top component.
+ * The "main" result view top component receives its current node as a selection
+ * from the case tree view in the top component (DirectoryTreeYopComponent)
+ * docked into the left hand side of the main application window.
*
- * For embedding custom data result in other top components window, use
- * DataResultPanel component instead, since we cannot nest top components.
- *
- * Encapsulates the internal DataResultPanel and delegates to it.
- *
- * Implements DataResult interface by delegating to the encapsulated
- * DataResultPanel.
+ * Result view top components are explorer manager providers to connect the
+ * lookups of the nodes displayed in the NetBeans explorer views of the result
+ * viewers to the actions global context.
*/
@RetainLocation("editor")
public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider {
private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName());
- private final ExplorerManager explorerManager = new ExplorerManager();
- private final DataResultPanel dataResultPanel; //embedded component with all the logic
- private boolean isMain;
- private String customModeName;
-
- //keep track of tcs opened for menu presenters
private static final List activeComponentIds = Collections.synchronizedList(new ArrayList());
+ private final boolean isMain;
+ private final String customModeName;
+ private final ExplorerManager explorerManager;
+ private final DataResultPanel dataResultPanel;
/**
- * Create a new data result top component
+ * Creates a result view top component that provides multiple views of the
+ * application data represented by a NetBeans Node. The result view will be
+ * docked into the upper right hand side of the main application window
+ * (editor mode) and will be linked to the content view in the lower right
+ * hand side of the main application window (output mode). Its result
+ * viewers are provided by the result viewer extension point (service
+ * providers that implement DataResultViewer).
*
- * @param isMain whether it is the main, application default result viewer,
- * there can be only 1 main result viewer
- * @param title title of the data result window
+ * @param title The title for the top component, appears on the top
+ * component's tab.
+ * @param description Descriptive text about the node displayed, appears
+ * on the top component's tab
+ * @param node The node to display.
+ * @param childNodeCount The cardinality of the node's children.
+ *
+ * @return The result view top component.
*/
- public DataResultTopComponent(boolean isMain, String title) {
- associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
- this.dataResultPanel = new DataResultPanel(title, isMain);
- initComponents();
- customizeComponent(isMain, title);
+ public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount) {
+ DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null);
+ initInstance(description, node, childNodeCount, resultViewTopComponent);
+ return resultViewTopComponent;
}
/**
- * Create a new, custom data result top component, in addition to the
- * application main one
+ * Creates a result view top component that provides multiple views of the
+ * application data represented by a NetBeans Node. The result view will be
+ * docked into the upper right hand side of the main application window
+ * (editor mode) and will be linked to the content view in the lower right
+ * hand side of the main application window (output mode).
*
- * @param name unique name of the data result window, also
- * used as title
- * @param mode custom mode to dock into
- * @param customContentViewer custom content viewer to send selection events
- * to
+ * @param title The title for the top component, appears on the top
+ * component's tab.
+ * @param description Descriptive text about the node displayed, appears
+ * on the top component's tab
+ * @param node The node to display.
+ * @param childNodeCount The cardinality of the node's children.
+ * @param viewers A collection of result viewers to use instead of
+ * the result viewers provided by the results viewer
+ * extension point.
+ *
+ * @return The result view top component.
*/
- DataResultTopComponent(String name, String mode, DataContentTopComponent customContentViewer) {
- associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
- this.customModeName = mode;
- dataResultPanel = new DataResultPanel(name, customContentViewer);
- initComponents();
- customizeComponent(isMain, name);
+ public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount, Collection viewers) {
+ DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, viewers, null);
+ initInstance(description, node, childNodeCount, resultViewTopComponent);
+ return resultViewTopComponent;
}
- private void customizeComponent(boolean isMain, String title) {
- this.isMain = isMain;
- this.customModeName = null;
-
- setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent"));
-
- setTitle(title); // set the title
- setName(title);
- getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS
- getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS
-
- putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI
- putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true);
- putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true);
-
- activeComponentIds.add(title);
+ /**
+ * Creates a partially initialized result view top component that provides
+ * multiple views of the application data represented by a NetBeans Node.
+ * The result view will be docked into the upper right hand side of the main
+ * application window (editor mode) and will be linked to the content view
+ * in the lower right hand side of the main application window (output
+ * mode). Its result viewers are provided by the result viewer extension
+ * point (service providers that implement DataResultViewer).
+ *
+ * IMPORTANT: Initialization MUST be completed by calling initInstance.
+ *
+ * @param title The title for the result view top component, appears on the
+ * top component's tab.
+ *
+ * @return The partially initialized result view top component.
+ */
+ public static DataResultTopComponent createInstance(String title) {
+ DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null);
+ return resultViewTopComponent;
}
/**
- * Initialize previously created tc instance with additional data
+ * Initializes a partially initialized result view top component.
*
- * @param pathText
- * @param givenNode
- * @param totalMatches
- * @param newDataResult previously created with createInstance()
- * uninitialized instance
+ * @param description Descriptive text about the node displayed,
+ * appears on the top component's tab
+ * @param node The node to display.
+ * @param childNodeCount The cardinality of the node's children.
+ * @param resultViewTopComponent The partially initialized result view top
+ * component.
*/
- public static void initInstance(String pathText, Node givenNode, int totalMatches, DataResultTopComponent newDataResult) {
- newDataResult.setNumMatches(totalMatches);
-
- newDataResult.open(); // open it first so the component can be initialized
-
- // set the tree table view
- newDataResult.setNode(givenNode);
- newDataResult.setPath(pathText);
-
- newDataResult.requestActive();
+ public static void initInstance(String description, Node node, int childNodeCount, DataResultTopComponent resultViewTopComponent) {
+ resultViewTopComponent.setNumberOfChildNodes(childNodeCount);
+ resultViewTopComponent.open();
+ resultViewTopComponent.setNode(node);
+ resultViewTopComponent.setPath(description);
+ resultViewTopComponent.requestActive();
}
/**
- * Creates a 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 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 title The title for the top component, appears
+ * on the top component's tab.
+ * @param mode The NetBeans Window system mode into which
+ * this top component should be docked.
+ * @param description Descriptive text about the node displayed,
+ * appears on the top component's tab
+ * @param node The node to display.
+ * @param childNodeCount The cardinality of the node's children.
+ * @param contentViewTopComponent A content view to which this result view
+ * will be linked.
*
- * @return 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) {
- DataResultTopComponent newDataResult = new DataResultTopComponent(false, title);
-
- initInstance(pathText, givenNode, totalMatches, newDataResult);
-
+ public static DataResultTopComponent createInstance(String title, String mode, String description, Node node, int childNodeCount, DataContentTopComponent contentViewTopComponent) {
+ DataResultTopComponent newDataResult = new DataResultTopComponent(false, title, mode, Collections.emptyList(), contentViewTopComponent);
+ initInstance(description, node, childNodeCount, newDataResult);
return newDataResult;
}
/**
- * Creates a new non-default DataResult component linked with a custom data
- * content, 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
+ * 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 mode custom mode to dock this custom TopComponent to
- * @param pathText Descriptive text about the source of the nodes
- * displayed
- * @param givenNode The new root node
- * @param totalMatches Cardinality of root node's children
- * @param dataContentWindow a handle to data content top component window to
- *
- * @return a new, not default, initialized DataResultTopComponent instance
+ * @param title The title for the top component, appears on the top
+ * component's tab.
*/
- public static DataResultTopComponent createInstance(String title, final String mode, String pathText, Node givenNode, int totalMatches, DataContentTopComponent dataContentWindow) {
- DataResultTopComponent newDataResult = new DataResultTopComponent(title, mode, dataContentWindow);
-
- initInstance(pathText, givenNode, totalMatches, newDataResult);
- return newDataResult;
+ public DataResultTopComponent(String title) {
+ this(true, title, null, Collections.emptyList(), null);
}
/**
- * Creates a new non-default DataResult component. You probably want to use
- * initInstance after it
+ * Constructs a result view top component that provides multiple views of
+ * the application data represented by a NetBeans Node.
*
- * @param title
- *
- * @return a new, not default, not fully initialized DataResultTopComponent
- * instance
+ * @param isMain Whether or not this is the "main" result
+ * view top component.
+ * @param title The title for the top component, appears
+ * on the top component's tab.
+ * @param mode The NetBeans Window system mode into which
+ * this top component should be docked. If
+ * null, the editor mode will be used by
+ * default.
+ * @param viewers A collection of result viewers. If empty,
+ * the result viewers provided by the results
+ * viewer extension point will be used.
+ * @param contentViewTopComponent A content view to which this result view
+ * will be linked. If null, this result view
+ * will be linked to the content view docked
+ * into the lower right hand side of the main
+ * application window,
*/
- public static DataResultTopComponent createInstance(String title) {
- final DataResultTopComponent newDataResult = new DataResultTopComponent(false, title);
+ private DataResultTopComponent(boolean isMain, String title, String mode, Collection viewers, DataContentTopComponent contentViewTopComponent) {
+ 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
@@ -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 getActiveComponentIds() {
return new ArrayList<>(activeComponentIds);
}
- /**
- * This method is called from within the constructor to initialize the form.
- * WARNING: Do NOT modify this code. The content of this method is always
- * regenerated by the Form Editor.
- */
- // //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)
- );
- }// //GEN-END:initComponents
- // Variables declaration - do not modify//GEN-BEGIN:variables
- // End of variables declaration//GEN-END:variables
-
@Override
public int getPersistenceType() {
if (customModeName == null) {
@@ -245,16 +282,6 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override
public void open() {
- setCustomMode();
- super.open(); //To change body of generated methods, choose Tools | Templates.
- }
-
- @Override
- public List getViewers() {
- return dataResultPanel.getViewers();
- }
-
- private void setCustomMode() {
if (customModeName != null) {
Mode mode = WindowManager.getDefault().findMode(customModeName);
if (mode != null) {
@@ -264,6 +291,12 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
logger.log(Level.WARNING, "Could not find mode: {0}, will dock into the default one", customModeName);//NON-NLS
}
}
+ super.open();
+ }
+
+ @Override
+ public List getViewers() {
+ return dataResultPanel.getViewers();
}
@Override
@@ -290,7 +323,7 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
} else {
selectedNode = null;
}
-
+
/*
* If the selected node of the content viewer is different than that of
* the result viewer, the content viewer needs to be updated. Otherwise,
@@ -343,31 +376,15 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override
public boolean canClose() {
- /*
- * If this is the results top component in the upper right of the main
- * window, only allow it to be closed when there's no case opened or no
- * data sources in the open case.
- */
Case openCase;
try {
openCase = Case.getOpenCase();
- } catch (NoCurrentCaseException ex) {
+ } catch (NoCurrentCaseException unused) {
return true;
}
return (!this.isMain) || openCase.hasData() == false;
}
- /**
- * Resets the tabs based on the selected Node. If the selected node is null
- * or not supported, disable that tab as well.
- *
- * @param selectedNode the selected content Node
- */
- public void resetTabs(Node selectedNode) {
-
- dataResultPanel.resetTabs(selectedNode);
- }
-
public void setSelectedNodes(Node[] selected) {
dataResultPanel.setSelectedNodes(selected);
}
@@ -376,7 +393,74 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
return dataResultPanel.getRootNode();
}
- void setNumMatches(int matches) {
- this.dataResultPanel.setNumMatches(matches);
+ /**
+ * Sets the cardinality of the current node's children
+ *
+ * @param childNodeCount The cardinality of the node's children.
+ */
+ private void setNumberOfChildNodes(int childNodeCount) {
+ this.dataResultPanel.setNumberOfChildNodes(childNodeCount);
}
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ // //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)
+ );
+ }// //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);
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
index daecf060fd..2201b6973b 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2017 Basis Technology Corp.
+ * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -60,155 +60,153 @@ import org.openide.nodes.Node;
import org.openide.nodes.Node.Property;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
+import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
/**
- * A tabular viewer for the results view.
+ * A tabular result viewer that displays the children of the given root node
+ * using an OutlineView.
*
- * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
- * restore implementation of DataResultViewerTable as a DataResultViewer service
- * provider.
+ * Instances of this class should use the explorer manager of an ancestor top
+ * component to connect the lookups of the nodes displayed in the OutlineView to
+ * the actions global context. The explorer manager can be supplied during
+ * construction, but the typical use case is for the result viewer to find the
+ * ancestor top component's explorer manager at runtime.
*/
-//@ServiceProvider(service = DataResultViewer.class)
-public class DataResultViewerTable extends AbstractDataResultViewer {
+@ServiceProvider(service = DataResultViewer.class)
+public final class DataResultViewerTable extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L;
- private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName());
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
- private static final Color TAGGED_COLOR = new Color(255, 255, 195);
+ static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195);
+ private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
private final String title;
+ private final Map columnMap;
+ private final Map> propertiesMap;
+ private final Outline outline;
+ private final TableListener outlineViewListener;
+ private Node rootNode;
/**
- * The properties map:
+ * Constructs a tabular result viewer that displays the children of the
+ * given root node using an OutlineView. The viewer should have an ancestor
+ * top component to connect the lookups of the nodes displayed in the
+ * OutlineView to the actions global context. The explorer manager will be
+ * discovered at runtime.
+ */
+ public DataResultViewerTable() {
+ this(null, Bundle.DataResultViewerTable_title());
+ }
+
+ /**
+ * Constructs a tabular result viewer that displays the children of a given
+ * root node using an OutlineView. The viewer should have an ancestor top
+ * component to connect the lookups of the nodes displayed in the
+ * OutlineView to the actions global context.
*
- * stored value of column index -> property at that index
- *
- * We move around stored values instead of directly using the column indices
- * in order to not override settings for a column that may not appear in the
- * current table view due to its collection of its children's properties.
- */
- private final Map> 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 columnMap = new HashMap<>();
-
- private Node currentRoot;
-
- /*
- * Convience reference to internal Outline.
- */
- private Outline outline;
-
- /**
- * Listener for table model event and mouse clicks.
- */
- private final TableListener tableListener;
-
- /**
- * Creates a DataResultViewerTable object that is compatible with node
- * multiple selection actions, and the default title.
- *
- * @param explorerManager allow for explorer manager sharing
+ * @param explorerManager The explorer manager of the ancestor top
+ * component.
*/
public DataResultViewerTable(ExplorerManager explorerManager) {
this(explorerManager, Bundle.DataResultViewerTable_title());
}
/**
- * Creates a DataResultViewerTable object that is compatible with node
- * multiple selection actions, and a custom title.
+ * Constructs a tabular result viewer that displays the children of a given
+ * root node using an OutlineView with a given title. The viewer should have
+ * an ancestor top component to connect the lookups of the nodes displayed
+ * in the OutlineView to the actions global context.
*
- * @param explorerManager allow for explorer manager sharing
- * @param title The custom title.
+ * @param explorerManager The explorer manager of the ancestor top
+ * component.
+ * @param title The title.
*/
public DataResultViewerTable(ExplorerManager explorerManager, String title) {
super(explorerManager);
this.title = title;
-
+ this.columnMap = new HashMap<>();
+ this.propertiesMap = new TreeMap<>();
+
+ /*
+ * Execute the code generated by the GUI builder.
+ */
initComponents();
-
+
+ /*
+ * Configure the child OutlineView (explorer view) component.
+ */
outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
outline = outlineView.getOutline();
outline.setRowSelectionAllowed(true);
outline.setColumnSelectionAllowed(true);
outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
- outline.setRootVisible(false); // don't show the root node
+ outline.setRootVisible(false);
outline.setDragEnabled(false);
outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer());
-
- // add a listener so that when columns are moved, the new order is stored
- tableListener = new TableListener();
- outline.getColumnModel().addColumnModelListener(tableListener);
-
- // the listener also moves columns back if user tries to move the first column out of place
- outline.getTableHeader().addMouseListener(tableListener);
+
+ /*
+ * Add a table listener to the child OutlineView (explorer view) to
+ * persist the order of the table columns when a column is moved.
+ */
+ outlineViewListener = new TableListener();
+ outline.getColumnModel().addColumnModelListener(outlineViewListener);
+
+ /*
+ * Add a mouse listener to the child OutlineView (explorer view) to make
+ * sure the first column of the table is kept in place.
+ */
+ outline.getTableHeader().addMouseListener(outlineViewListener);
}
/**
- * Creates a DataResultViewerTable object that is NOT compatible with node
- * multiple selection actions.
- */
- public DataResultViewerTable() {
- this(new ExplorerManager(),Bundle.DataResultViewerTable_title());
- }
-
-
- /**
- * Expand node
+ * Creates a new instance of a tabular result viewer that displays the
+ * children of a given root node using an OutlineView. This method exists to
+ * make it possible to use the default service provider instance of this
+ * class in the "main" results view of the application, while using distinct
+ * instances in other places in the UI.
*
- * @param n Node to expand
+ * @return A new instance of a tabular result viewer,
*/
@Override
- public void expandNode(Node n) {
- super.expandNode(n);
+ public DataResultViewer createInstance() {
+ 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.
- * WARNING: Do NOT modify this code. The content of this method is always
- * regenerated by the Form Editor.
+ * Indicates whether a given node is supported as a root node for this
+ * tabular viewer.
+ *
+ * @param candidateRootNode The candidate root node.
+ *
+ * @return
*/
- @SuppressWarnings("unchecked")
- // //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)
- );
- }// //GEN-END:initComponents
- // Variables declaration - do not modify//GEN-BEGIN:variables
- private org.openide.explorer.view.OutlineView outlineView;
- // End of variables declaration//GEN-END:variables
-
@Override
- public boolean isSupported(Node selectedNode) {
+ public boolean isSupported(Node candidateRootNode) {
return true;
}
+ /**
+ * Sets the current root node of this tabular result viewer.
+ *
+ * @param rootNode The node to set as the current root node, possibly null.
+ */
@Override
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
- public void setNode(Node selectedNode) {
-
+ public void setNode(Node rootNode) {
/*
* The quick filter must be reset because when determining column width,
* ETable.getRowCount is called, and the documentation states that quick
@@ -217,30 +215,30 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* model."
*/
outline.unsetQuickFilter();
- // change the cursor to "waiting cursor" for this operation
+
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
- boolean hasChildren = false;
- if (selectedNode != null) {
- // @@@ This just did a DB round trip to get the count and the results were not saved...
- hasChildren = selectedNode.getChildren().getNodesCount() > 0;
- }
-
- if (hasChildren) {
- currentRoot = selectedNode;
- em.setRootContext(currentRoot);
+ /*
+ * If the given node is not null and has children, set it as the
+ * root context of the child OutlineView, otherwise make an
+ * "empty"node the root context.
+ *
+ * IMPORTANT NOTE: This is the first of many times where a
+ * getChildren call on the current root node causes all of the
+ * children of the root node to be created and defeats lazy child
+ * node creation, if it is enabled. It also likely leads to many
+ * case database round trips.
+ */
+ if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
+ this.rootNode = rootNode;
+ this.getExplorerManager().setRootContext(this.rootNode);
setupTable();
} else {
Node emptyNode = new AbstractNode(Children.LEAF);
- em.setRootContext(emptyNode); // make empty node
+ this.getExplorerManager().setRootContext(emptyNode);
outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
-
- /*
- * Since we are modifying the columns, we don't want to listen
- * to added/removed events as un-hide/hide.
- */
- tableListener.listenToVisibilityChanges(false);
- outlineView.setPropertyColumns(); // set the empty property header
+ outlineViewListener.listenToVisibilityChanges(false);
+ outlineView.setPropertyColumns();
}
} finally {
this.setCursor(null);
@@ -248,16 +246,17 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
/**
- * Create Column Headers based on the Content represented by the Nodes in
- * the table. Load persisted column order, sorting and visibility.
+ * Sets up the Outline view of this tabular result viewer by creating
+ * column headers based on the children of the current root node. The
+ * persisted column order, sorting and visibility is used.
*/
private void setupTable() {
/*
* Since we are modifying the columns, we don't want to listen to
* added/removed events as un-hide/hide, until the table setup is done.
*/
- tableListener.listenToVisibilityChanges(false);
- /**
+ outlineViewListener.listenToVisibilityChanges(false);
+ /*
* OutlineView makes the first column be the result of
* node.getDisplayName with the icon. This duplicates our first column,
* which is the file name, etc. So, pop that property off the list, but
@@ -289,7 +288,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
setColumnWidths();
- //Load column sorting information from preferences file and apply it to columns.
+ /*
+ * Load column sorting information from preferences file and apply it to
+ * columns.
+ */
loadColumnSorting();
/*
@@ -301,7 +303,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
populateColumnMap();
- //Load column visibility information from preferences file and apply it to columns.
+ /*
+ * Load column visibility information from preferences file and apply it
+ * to columns.
+ */
loadColumnVisibility();
/*
@@ -309,32 +314,36 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* it.
*/
SwingUtilities.invokeLater(() -> {
- if (currentRoot instanceof TableFilterNode) {
- NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
+ if (rootNode instanceof TableFilterNode) {
+ NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) {
- Node[] childNodes = currentRoot.getChildren().getNodes(true);
+ Node[] childNodes = rootNode.getChildren().getNodes(true);
for (int i = 0; i < childNodes.length; ++i) {
Node childNode = childNodes[i];
if (selectedChildInfo.matches(childNode)) {
try {
- em.setSelectedNodes(new Node[]{childNode});
+ this.getExplorerManager().setSelectedNodes(new Node[]{childNode});
} catch (PropertyVetoException ex) {
LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
}
break;
}
}
- ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null);
+ ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
}
}
});
- //the table setup is done, so any added/removed events can now be treated as un-hide/hide.
- tableListener.listenToVisibilityChanges(true);
+ /*
+ * The table setup is done, so any added/removed events can now be
+ * treated as un-hide/hide.
+ */
+ outlineViewListener.listenToVisibilityChanges(true);
}
/*
- * Populate the map with references to the column objects for use when
+ * Populates the column map for the child OutlineView of this tabular
+ * result viewer with references to the column objects for use when
* loading/storing the visibility info.
*/
private void populateColumnMap() {
@@ -351,8 +360,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
+ /*
+ * Sets the column widths for the child OutlineView of this tabular results
+ * viewer.
+ */
private void setColumnWidths() {
- if (currentRoot.getChildren().getNodesCount() != 0) {
+ if (rootNode.getChildren().getNodesCount() != 0) {
final Graphics graphics = outlineView.getGraphics();
if (graphics != null) {
final FontMetrics metrics = graphics.getFontMetrics();
@@ -388,8 +401,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
+ /*
+ * Sets up the columns for the child OutlineView of this tabular results
+ * viewer with respect to column names and visisbility.
+ */
synchronized private void assignColumns(List> props) {
- // Get the columns setup with respect to names and sortability
String[] propStrings = new String[props.size() * 2];
for (int i = 0; i < props.size(); i++) {
final Property> prop = props.get(i);
@@ -402,29 +418,25 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
propStrings[2 * i] = prop.getName();
propStrings[2 * i + 1] = prop.getDisplayName();
}
-
outlineView.setPropertyColumns(propStrings);
}
/**
- * Store the current column visibility information into a preference file.
+ * Persists the current column visibility information for the child
+ * OutlineView of this tabular result viewer using a preferences file.
*/
private synchronized void storeColumnVisibility() {
- if (currentRoot == null || propertiesMap.isEmpty()) {
+ if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
- if (currentRoot instanceof TableFilterNode) {
- TableFilterNode tfn = (TableFilterNode) currentRoot;
+ if (rootNode instanceof TableFilterNode) {
+ TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
-
- //store hidden state
for (Map.Entry entry : columnMap.entrySet()) {
-
String columnName = entry.getKey();
final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
final TableColumn column = entry.getValue();
-
boolean columnHidden = columnModel.isColumnHidden(column);
if (columnHidden) {
preferences.putBoolean(columnHiddenKey, true);
@@ -436,16 +448,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
/**
- * Store the current column order information into a preference file.
+ * Persists the current column ordering for the child OutlineView of this
+ * tabular result viewer using a preferences file.
*/
private synchronized void storeColumnOrder() {
- if (currentRoot == null || propertiesMap.isEmpty()) {
+ if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
- if (currentRoot instanceof TableFilterNode) {
- TableFilterNode tfn = (TableFilterNode) currentRoot;
+ if (rootNode instanceof TableFilterNode) {
+ TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
-
// Store the current order of the columns into settings
for (Map.Entry> entry : propertiesMap.entrySet()) {
preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
@@ -454,20 +466,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
/**
- * Store the current column sorting information into a preference file.
+ * Persists the current column sorting information using a preferences file.
*/
private synchronized void storeColumnSorting() {
- if (currentRoot == null || propertiesMap.isEmpty()) {
+ if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
- if (currentRoot instanceof TableFilterNode) {
- final TableFilterNode tfn = ((TableFilterNode) currentRoot);
+ if (rootNode instanceof TableFilterNode) {
+ final TableFilterNode tfn = ((TableFilterNode) rootNode);
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry entry : columnMap.entrySet()) {
ETableColumn etc = entry.getValue();
String columnName = entry.getKey();
-
//store sort rank and order
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
@@ -485,47 +496,43 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
/**
* Reads and applies the column sorting information persisted to the
- * preferences file. Must be called after loadColumnOrder() since it depends
- * on propertiesMap being initialized, and after assignColumns since it
- * cannot set the sort on columns that have not been added to the table.
+ * preferences file. Must be called after loadColumnOrder, since it depends
+ * on the properties map being initialized, and after assignColumns, since
+ * it cannot set the sort on columns that have not been added to the table.
*/
private synchronized void loadColumnSorting() {
- if (currentRoot == null || propertiesMap.isEmpty()) {
+ if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
-
- if (currentRoot instanceof TableFilterNode) {
- final TableFilterNode tfn = (TableFilterNode) currentRoot;
-
+ if (rootNode instanceof TableFilterNode) {
+ final TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
//organize property sorting information, sorted by rank
TreeSet sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
propertiesMap.entrySet().stream().forEach(entry -> {
final String propName = entry.getValue().getName();
//if the sort rank is undefined, it will be defaulted to 0 => unsorted.
-
Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
//default to true => ascending
Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
-
sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
});
-
//apply sort information in rank order.
sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
}
}
+ /**
+ * Reads and applies the column visibility information persisted to the
+ * preferences file.
+ */
private synchronized void loadColumnVisibility() {
- if (currentRoot == null || propertiesMap.isEmpty()) {
+ if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
-
- if (currentRoot instanceof TableFilterNode) {
-
+ if (rootNode instanceof TableFilterNode) {
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
-
- final TableFilterNode tfn = ((TableFilterNode) currentRoot);
+ final TableFilterNode tfn = ((TableFilterNode) rootNode);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry> entry : propertiesMap.entrySet()) {
final String propName = entry.getValue().getName();
@@ -538,7 +545,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
/**
* Gets a list of child properties (columns) in the order persisted in the
- * preference file. Also initialized the propertiesMap with the column
+ * preference file. Also initialized the properties map with the column
* order.
*
* @return a List> of the properties in the persisted
@@ -546,14 +553,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
private synchronized List> loadColumnOrder() {
- List> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100);
+ List> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
// If node is not table filter node, use default order for columns
- if (!(currentRoot instanceof TableFilterNode)) {
+ if (!(rootNode instanceof TableFilterNode)) {
return props;
}
- final TableFilterNode tfn = ((TableFilterNode) currentRoot);
+ final TableFilterNode tfn = ((TableFilterNode) rootNode);
propertiesMap.clear();
/*
@@ -590,24 +597,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
return new ArrayList<>(propertiesMap.values());
}
- @Override
- @NbBundle.Messages("DataResultViewerTable.title=Table")
- public String getTitle() {
- return title;
- }
-
- @Override
- public DataResultViewer createInstance() {
- return new DataResultViewerTable();
- }
-
+ /**
+ * Frees the resources that have been allocated by this tabular results
+ * viewer, in preparation for permanently disposing of it.
+ */
@Override
public void clearComponent() {
this.outlineView.removeAll();
this.outlineView = null;
-
super.clearComponent();
-
}
/**
@@ -778,8 +776,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
// only override the color if a node is not selected
- if (currentRoot != null && !isSelected) {
- Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row));
+ if (rootNode != null && !isSelected) {
+ Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row));
boolean tagFound = false;
if (node != null) {
Node.PropertySet[] propSets = node.getPropertySets();
@@ -799,10 +797,37 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
//if the node does have associated tags, set its background color
if (tagFound) {
- component.setBackground(TAGGED_COLOR);
+ component.setBackground(TAGGED_ROW_COLOR);
}
}
return component;
}
}
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //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)
+ );
+ }// //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
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
index 9ba027f34e..1f2c815a35 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2017 Basis Technology Corp.
+ * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -50,6 +50,7 @@ import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
+import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import static org.sleuthkit.autopsy.corecomponents.Bundle.*;
import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion;
@@ -59,64 +60,67 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
- * A thumbnail viewer for the results view, with paging support.
+ * A thumbnail result viewer, with paging support, that displays the children of
+ * the given root node using an IconView. The paging is intended to reduce
+ * memory footprint by loading no more than two humdred images at a time.
*
- * The paging is intended to reduce memory footprint by load only up to
- * (currently) 200 images at a time. This works whether or not the underlying
- * content nodes are being lazy loaded or not.
- *
- * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
- * restore implementation of DataResultViewerTable as a DataResultViewer service
- * provider.
+ * Instances of this class should use the explorer manager of an ancestor top
+ * component to connect the lookups of the nodes displayed in the IconView to
+ * the actions global context. The explorer manager can be supplied during
+ * construction, but the typical use case is for the result viewer to find the
+ * ancestor top component's explorer manager at runtime.
*/
-//@ServiceProvider(service = DataResultViewer.class)
-final class DataResultViewerThumbnail extends AbstractDataResultViewer {
+@ServiceProvider(service = DataResultViewer.class)
+public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
- private int curPage;
- private int totalPages;
- private int curPageImages;
- private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
private final PageUpdater pageUpdater = new PageUpdater();
- private TableFilterNode tfn;
- private ThumbnailViewChildren tvc;
+ private TableFilterNode rootNode;
+ private ThumbnailViewChildren rootNodeChildren;
+ private NodeSelectionListener selectionListener;
+ private int currentPage;
+ private int totalPages;
+ private int currentPageImages;
+ private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
/**
- * Constructs a thumbnail viewer for the results view, with paging support,
- * that is compatible with node multiple selection actions.
+ * Constructs a thumbnail result viewer, with paging support, that displays
+ * the children of the given root node using an IconView. The viewer should
+ * have an ancestor top component to connect the lookups of the nodes
+ * displayed in the IconView to the actions global context. The explorer
+ * manager will be discovered at runtime.
+ */
+ public DataResultViewerThumbnail() {
+ this(null);
+ }
+
+ /**
+ * Constructs a thumbnail result viewer, with paging support, that displays
+ * the children of the given root node using an IconView. The viewer should
+ * have an ancestor top component to connect the lookups of the nodes
+ * displayed in the IconView to the actions global context.
*
- * @param explorerManager The shared ExplorerManager for the result viewers.
+ * @param explorerManager The explorer manager of the ancestor top
+ * component.
*/
- DataResultViewerThumbnail(ExplorerManager explorerManager) {
- super(explorerManager);
- initialize();
- }
-
- /**
- * Constructs a thumbnail viewer for the results view, with paging support,
- * that is NOT compatible with node multiple selection actions.
- */
- DataResultViewerThumbnail() {
- initialize();
- }
-
- @NbBundle.Messages({"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
+ @NbBundle.Messages({
+ "DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
"DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails",
"DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails"
})
- private void initialize() {
+ public DataResultViewerThumbnail(ExplorerManager explorerManager) {
+ super(explorerManager);
initComponents();
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
- em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
- thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(
- new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
- Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(),
- Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()}));
+ thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{
+ Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
+ Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(),
+ Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()}));
thumbnailSizeComboBox.setSelectedIndex(1);
- curPage = -1;
+ currentPage = -1;
totalPages = 0;
- curPageImages = 0;
+ currentPageImages = 0;
}
/**
@@ -297,24 +301,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
if (thumbSize != newIconSize) {
thumbSize = newIconSize;
- Node root = em.getRootContext();
+ Node root = this.getExplorerManager().getRootContext();
((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize);
-
-
// Temporarily set the explored context to the root, instead of a child node.
// This is a workaround hack to convince org.openide.explorer.ExplorerManager to
// update even though the new and old Node values are identical. This in turn
// will cause the entire view to update completely. After this we
// immediately set the node back to the current child by calling switchPage().
- em.setExploredContext(root);
+ this.getExplorerManager().setExploredContext(root);
switchPage();
}
}//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed
private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed
- List> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100);
- SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn));
+ List> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100);
+ SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(rootNode));
DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle());
Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
createDialog.setVisible(true);
@@ -335,8 +337,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
Node.Property> prop = childProperties.get(i);
String propName = prop.getName();
SortCriterion criterion = criteriaMap.get(prop);
- final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName);
- final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName);
+ final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(rootNode, propName);
+ final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(rootNode, propName);
if (criterion != null) {
preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING);
@@ -346,7 +348,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
preferences.remove(columnSortRankKey);
}
}
- setNode(tfn); //this is just to force a refresh
+ setNode(rootNode); //this is just to force a refresh
}
}//GEN-LAST:event_sortButtonActionPerformed
@@ -379,28 +381,31 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override
public void setNode(Node givenNode) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- if (tvc != null) {
- tvc.cancelLoadingThumbnails();
+ if (selectionListener == null) {
+ this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup
+ }
+ if (rootNodeChildren != null) {
+ rootNodeChildren.cancelLoadingThumbnails();
}
try {
if (givenNode != null) {
- tfn = (TableFilterNode) givenNode;
+ rootNode = (TableFilterNode) givenNode;
/*
* Wrap the given node in a ThumbnailViewChildren that will
* produce ThumbnailPageNodes with ThumbnailViewNode children
* from the child nodes of the given node.
*/
- tvc = new ThumbnailViewChildren(givenNode,thumbSize);
- final Node root = new AbstractNode(tvc);
+ rootNodeChildren = new ThumbnailViewChildren(givenNode, thumbSize);
+ final Node root = new AbstractNode(rootNodeChildren);
pageUpdater.setRoot(root);
root.addNodeListener(pageUpdater);
- em.setRootContext(root);
+ this.getExplorerManager().setRootContext(root);
} else {
- tfn = null;
- tvc = null;
+ rootNode = null;
+ rootNodeChildren = null;
Node emptyNode = new AbstractNode(Children.LEAF);
- em.setRootContext(emptyNode);
+ this.getExplorerManager().setRootContext(emptyNode);
iconView.setBackground(Color.BLACK);
}
} finally {
@@ -422,8 +427,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
public void resetComponent() {
super.resetComponent();
this.totalPages = 0;
- this.curPage = -1;
- curPageImages = 0;
+ this.currentPage = -1;
+ currentPageImages = 0;
updateControls();
}
@@ -435,15 +440,15 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
private void nextPage() {
- if (curPage < totalPages) {
- curPage++;
+ if (currentPage < totalPages) {
+ currentPage++;
switchPage();
}
}
private void previousPage() {
- if (curPage > 1) {
- curPage--;
+ if (currentPage > 1) {
+ currentPage--;
switchPage();
}
}
@@ -465,7 +470,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
return;
}
- curPage = newPage;
+ currentPage = newPage;
switchPage();
}
@@ -488,10 +493,11 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs"));
progress.start();
progress.switchToIndeterminate();
- Node root = em.getRootContext();
- Node pageNode = root.getChildren().getNodeAt(curPage - 1);
- em.setExploredContext(pageNode);
- curPageImages = pageNode.getChildren().getNodesCount();
+ ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager();
+ Node root = explorerManager.getRootContext();
+ Node pageNode = root.getChildren().getNodeAt(currentPage - 1);
+ explorerManager.setExploredContext(pageNode);
+ currentPageImages = pageNode.getChildren().getNodesCount();
return null;
}
@@ -504,8 +510,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
- NotifyDescriptor d =
- new NotifyDescriptor.Message(
+ NotifyDescriptor d
+ = new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg",
ex.getMessage()),
NotifyDescriptor.ERROR_MESSAGE);
@@ -535,20 +541,19 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
} else {
- pageNumLabel.setText(
- NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal",
- Integer.toString(curPage), Integer.toString(totalPages)));
- final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
- final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
+ pageNumLabel.setText(NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal",
+ Integer.toString(currentPage), Integer.toString(totalPages)));
+ final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
+ final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
imagesRangeLabel.setText(imagesFrom + "-" + imagesTo);
- pageNextButton.setEnabled(!(curPage == totalPages));
- pagePrevButton.setEnabled(!(curPage == 1));
+ pageNextButton.setEnabled(!(currentPage == totalPages));
+ pagePrevButton.setEnabled(!(currentPage == 1));
goToPageField.setEnabled(totalPages > 1);
sortButton.setEnabled(true);
thumbnailSizeComboBox.setEnabled(true);
- if (tfn != null) {
- String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream()
+ if (rootNode != null) {
+ String sortString = ResultViewerPersistence.loadSortCriteria(rootNode).stream()
.map(SortCriterion::toString)
.collect(Collectors.joining(" "));
sortString = StringUtils.defaultIfBlank(sortString, "---");
@@ -579,30 +584,30 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
totalPages = root.getChildren().getNodesCount();
if (totalPages == 0) {
- curPage = -1;
+ currentPage = -1;
updateControls();
return;
}
- if (curPage == -1 || curPage > totalPages) {
- curPage = 1;
+ if (currentPage == -1 || currentPage > totalPages) {
+ currentPage = 1;
}
//force load the curPage node
- final Node pageNode = root.getChildren().getNodeAt(curPage - 1);
+ final Node pageNode = root.getChildren().getNodeAt(currentPage - 1);
//em.setSelectedNodes(new Node[]{pageNode});
if (pageNode != null) {
pageNode.addNodeListener(new NodeListener() {
@Override
public void childrenAdded(NodeMemberEvent nme) {
- curPageImages = pageNode.getChildren().getNodesCount();
+ currentPageImages = pageNode.getChildren().getNodesCount();
updateControls();
}
@Override
public void childrenRemoved(NodeMemberEvent nme) {
- curPageImages = 0;
+ currentPageImages = 0;
updateControls();
}
@@ -619,7 +624,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
});
- em.setExploredContext(pageNode);
+ DataResultViewerThumbnail.this.getExplorerManager().setExploredContext(pageNode);
}
updateControls();
@@ -628,7 +633,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override
public void childrenRemoved(NodeMemberEvent nme) {
totalPages = 0;
- curPage = -1;
+ currentPage = -1;
updateControls();
}
@@ -641,14 +646,14 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
}
- private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener {
+ private class NodeSelectionListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
- Node[] selectedNodes = em.getSelectedNodes();
+ Node[] selectedNodes = DataResultViewerThumbnail.this.getExplorerManager().getSelectedNodes();
if (selectedNodes.length == 1) {
AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
if (af == null) {
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java
index 54f2a358b5..9394234e4d 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java
@@ -76,7 +76,8 @@ public class Installer extends ModuleInstall {
private void setLookAndFeel() {
if (System.getProperty("os.name").toLowerCase().contains("mac")) { //NON-NLS
- setOSXLookAndFeel();
+ setUnixLookAndFeel();
+ setModuleSettings("false");
}else if (System.getProperty("os.name").toLowerCase().contains("nux")){
setUnixLookAndFeel();
}
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java
index b715d8ce35..7d66dc4e3c 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java
@@ -358,7 +358,7 @@ public class PlatformUtil {
File[] files = dev.listFiles();
for (File f : files) {
String name = f.getName();
- if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 3) { //NON-NLS
+ if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 5) { //NON-NLS
String path = "/dev/" + name; //NON-NLS
if (canReadDrive(path)) {
try {
@@ -401,7 +401,7 @@ public class PlatformUtil {
File[] files = dev.listFiles();
for (File f : files) {
String name = f.getName();
- if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 4) { //NON-NLS
+ if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 7) { //NON-NLS
String path = "/dev/" + name; //NON-NLS
if (canReadDrive(path)) {
drives.add(new LocalDisk(path, path, f.getTotalSpace()));
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index f51c94aaca..3aef1e5a2a 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -386,8 +386,15 @@ public class DataResultFilterNode extends FilterNode {
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c));
}
// 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));
+ }
}
Content c = ban.getLookup().lookup(File.class);
Node n = null;
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
index 13219f863d..3069b7fb5d 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
@@ -101,7 +101,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private final transient ExplorerManager em = new ExplorerManager();
private static DirectoryTreeTopComponent instance;
- private final DataResultTopComponent dataResult = new DataResultTopComponent(true, Bundle.DirectoryTreeTopComponent_resultsView_title());
+ private final DataResultTopComponent dataResult = new DataResultTopComponent(Bundle.DirectoryTreeTopComponent_resultsView_title());
private final LinkedList backList;
private final LinkedList forwardList;
private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS
diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java
index 6aab063952..9ab4ec281b 100644
--- a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java
+++ b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.guiutils;
-import java.awt.Color;
import java.awt.Component;
import java.time.Duration;
import javax.swing.JTable;
@@ -41,36 +40,47 @@ public class DurationCellRenderer extends GrayableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Long) {
{
- Duration d = Duration.ofMillis((long) value);
- 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);
+ setText(DurationCellRenderer.longToDurationString((long) value));
}
}
grayCellIfTableNotEnabled(table, isSelected);
return this;
}
+ /**
+ * Convert a duration represented by a long to a human readable string with
+ * with days, hours, minutes, and seconds components.
+ *
+ * @param duration - the representation of the duration in long form
+ *
+ * @return - the representation of the duration in String form.
+ */
+ public static String longToDurationString(long duration) {
+ Duration d = Duration.ofMillis(duration);
+ if (d.isNegative()) {
+ d = Duration.ofMillis(-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;
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java
new file mode 100644
index 0000000000..2186dc3be1
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java
@@ -0,0 +1,859 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 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 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 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;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java
new file mode 100644
index 0000000000..6df918acaa
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java
@@ -0,0 +1,34 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java
new file mode 100644
index 0000000000..61ea5a5244
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java
@@ -0,0 +1,53 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java
new file mode 100644
index 0000000000..d44ceca910
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java
@@ -0,0 +1,84 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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()");
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java
index 3a06a3be4d..d512c6670c 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java
@@ -501,13 +501,16 @@ public final class IngestJobSettings {
this.warnings.add(warning);
}
} else {
- try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) {
- settings = (IngestModuleIngestJobSettings) in.readObject();
- } catch (IOException | ClassNotFoundException exception) {
- String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS
- logger.log(Level.WARNING, warning, exception);
- this.warnings.add(warning);
- }
+ // @@@ BC Jython serialization is currently broken and this
+ // throws an exception. (-2323). Commenting out so that
+ // Python modules will at least load with default settings.
+// try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) {
+// settings = (IngestModuleIngestJobSettings) in.readObject();
+// } catch (IOException | ClassNotFoundException exception) {
+// String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS
+// logger.log(Level.WARNING, warning, exception);
+// this.warnings.add(warning);
+// }
}
}
if (settings == null) {
diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java
new file mode 100644
index 0000000000..a269b2bdd2
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java
@@ -0,0 +1,179 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 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(" \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;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java
index 7b48606ea0..d075dec1e8 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java
@@ -18,12 +18,18 @@
*/
package org.sleuthkit.autopsy.modules.encryptiondetection;
-import java.io.BufferedInputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.util.Collections;
import java.util.logging.Level;
-import org.openide.util.NbBundle;
+import org.sleuthkit.datamodel.ReadContentInputStream;
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import org.apache.tika.exception.EncryptedDocumentException;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.AutoDetectParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.sax.BodyContentHandler;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
@@ -37,29 +43,20 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
-import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+
/**
- * File ingest module to detect encryption.
+ * File ingest module to detect encryption and password protection.
*/
final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter {
- static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5;
- static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB;
- static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true;
- static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true;
-
- static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0;
- static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0;
- static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1;
-
private static final int FILE_SIZE_MODULUS = 512;
- private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2))
- private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256;
-
private final IngestServices services = IngestServices.getInstance();
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
private FileTypeDetector fileTypeDetector;
@@ -73,9 +70,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/**
* Create a EncryptionDetectionFileIngestModule object that will detect
- * files that are encrypted and create blackboard artifacts as appropriate.
- * The supplied EncryptionDetectionIngestJobSettings object is used to
- * configure the module.
+ * files that are either encrypted or password protected and create
+ * blackboard artifacts as appropriate. The supplied
+ * EncryptionDetectionIngestJobSettings object is used to configure the
+ * module.
*/
EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
minimumEntropy = settings.getMinimumEntropy();
@@ -101,13 +99,37 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
public IngestModule.ProcessResult process(AbstractFile file) {
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);
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);
return IngestModule.ProcessResult.ERROR;
}
@@ -121,31 +143,23 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
* @throws IngestModule.IngestModuleException If the input is empty,
* invalid, or out of range.
*/
- @NbBundle.Messages({
- "EncryptionDetectionFileIngestModule.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.",
- "EncryptionDetectionFileIngestModule.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater."
- })
private void validateSettings() throws IngestModule.IngestModuleException {
- if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) {
- throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumEntropyInput());
- }
-
- if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) {
- throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumFileSizeInput());
- }
+ EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
+ EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
}
/**
* 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
* was a problem.
*/
- private IngestModule.ProcessResult flagFile(AbstractFile file) {
+ private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) {
try {
- BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED);
+ BlackboardArtifact artifact = file.newArtifact(artifactType);
try {
/*
@@ -159,17 +173,19 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/*
* 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.
*/
StringBuilder detailsSb = new StringBuilder();
- detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append(" \n");
- detailsSb.append("Entropy: ").append(calculatedEntropy);
+ detailsSb.append("File: ").append(file.getParentPath()).append(file.getName());
+ if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
+ detailsSb.append(" \n").append("Entropy: ").append(calculatedEntropy);
+ }
services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
- "Encryption Detected Match: " + file.getName(),
+ artifactType.getDisplayName() + " Match: " + file.getName(),
detailsSb.toString(),
file.getName(),
artifact));
@@ -182,16 +198,87 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
}
/**
- * This method checks if the AbstractFile input is encrypted. Initial
- * qualifications require that it be an actual file that is not known, meets
- * file size requirements, and has a MIME type of
- * 'application/octet-stream'.
+ * This method checks if the AbstractFile input is password protected.
*
* @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
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection
@@ -200,99 +287,20 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
boolean possiblyEncrypted = false;
/*
- * Qualify the file type.
+ * Qualify the size.
*/
- 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)) {
+ long contentSize = file.getSize();
+ if (contentSize >= minimumFileSize) {
+ if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
/*
- * Qualify the size.
+ * Qualify the entropy.
*/
- long contentSize = file.getSize();
- if (contentSize >= minimumFileSize) {
- if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
- /*
- * Qualify the MIME type.
- */
- String mimeType = fileTypeDetector.getMIMEType(file);
- if (mimeType.equals("application/octet-stream")) {
- possiblyEncrypted = true;
- }
- }
+ calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
+ if (calculatedEntropy >= minimumEntropy) {
+ possiblyEncrypted = true;
}
}
}
-
- 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();
- }
- }
+ return possiblyEncrypted;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java
index 2aa6ad860d..5acb97a518 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java
@@ -26,7 +26,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJobSettings {
private static final long serialVersionUID = 1L;
-
+ private static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5;
+ private static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB;
+ private static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true;
+ private static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true;
private double minimumEntropy;
private int minimumFileSize;
private boolean fileSizeMultipleEnforced;
@@ -36,10 +39,10 @@ final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJo
* Instantiate the ingest job settings with default values.
*/
EncryptionDetectionIngestJobSettings() {
- this.minimumEntropy = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_ENTROPY;
- this.minimumFileSize = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_FILE_SIZE;
- this.fileSizeMultipleEnforced = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED;
- this.slackFilesAllowed = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_SLACK_FILES_ALLOWED;
+ this.minimumEntropy = DEFAULT_CONFIG_MINIMUM_ENTROPY;
+ this.minimumFileSize = DEFAULT_CONFIG_MINIMUM_FILE_SIZE;
+ this.fileSizeMultipleEnforced = DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED;
+ this.slackFilesAllowed = DEFAULT_CONFIG_SLACK_FILES_ALLOWED;
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java
old mode 100644
new mode 100755
index 27549f648f..7a2d486841
--- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2017 Basis Technology Corp.
+ * Copyright 2017-2018 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,7 +30,8 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
/**
- * A factory that creates file ingest modules that detect encryption.
+ * A factory that creates file ingest modules that detect encryption and
+ * password protection.
*/
@ServiceProvider(service = IngestModuleFactory.class)
@Messages({
@@ -106,11 +107,14 @@ public class EncryptionDetectionModuleFactory implements IngestModuleFactory {
@Override
public boolean isDataSourceIngestModuleFactory() {
- return false;
+ return true;
}
@Override
public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) {
- throw new UnsupportedOperationException();
+ if (!(settings instanceof EncryptionDetectionIngestJobSettings)) {
+ throw new IllegalArgumentException("Expected settings argument to be an instance of EncryptionDetectionIngestJobSettings.");
+ }
+ return new EncryptionDetectionDataSourceIngestModule((EncryptionDetectionIngestJobSettings) settings);
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java
new file mode 100644
index 0000000000..99dbc0aaeb
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java
@@ -0,0 +1,131 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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() {
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java
index 92e2989e64..ea93d10434 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java
@@ -33,6 +33,8 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
+import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor;
+import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.autopsy.ingest.FileIngestModule;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
@@ -182,8 +184,20 @@ public class HashDbIngestModule implements FileIngestModule {
String md5Hash = file.getMd5Hash();
if (md5Hash == null || md5Hash.isEmpty()) {
try {
+ TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
long calcstart = System.currentTimeMillis();
md5Hash = HashUtility.calculateMd5Hash(file);
+ if (file.getSize() > 0) {
+ // Surprisingly, the hash calculation does not seem to be correlated that
+ // strongly with file size until the files get large.
+ // Only normalize if the file size is greater than ~1MB.
+ if (file.getSize() < 1000000) {
+ EnterpriseHealthMonitor.submitTimingMetric(metric);
+ } else {
+ // In testing, this normalization gave reasonable resuls
+ EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000);
+ }
+ }
file.setMd5Hash(md5Hash);
long delta = (System.currentTimeMillis() - calcstart);
totals.totalCalctime.addAndGet(delta);
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java
index a379fc7d8d..cc58dfd80a 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java
@@ -225,7 +225,7 @@ public final class PromptDialogManager {
@NbBundle.Messages({
"PromptDialogManager.showTimeLineDisabledMessage.contentText="
- + "Timeline functionality is not available for Linux yet."
+ + "Timeline functionality is not available yet."
+ " Timeline will be disabled. ",
"PromptDialogManager.showTimeLineDisabledMessage.headerText="})
static void showTimeLineDisabledMessage() {
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java
new file mode 100755
index 0000000000..36ecd56d8f
--- /dev/null
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java
@@ -0,0 +1,278 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 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 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 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 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 results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%deep folder/' and name != '.' and name != '..'");
+ assertEquals(1, results.size());
+ StringBuffer dirReached = new StringBuffer();
+ ArrayList 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 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 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 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 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 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 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;
+ }
+}
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java
index 9132365e36..70415d3c7e 100644
--- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java
@@ -18,20 +18,14 @@
*/
package org.sleuthkit.autopsy.ingest;
-import java.io.File;
-import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import static junit.framework.Assert.assertFalse;
import org.netbeans.junit.NbModuleSuite;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CaseActionException;
-import org.sleuthkit.autopsy.casemodule.CaseDetails;
import junit.framework.Test;
-import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions;
import org.python.icu.impl.Assert;
@@ -39,7 +33,6 @@ import org.sleuthkit.autopsy.casemodule.ImageDSProcessor;
import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
-import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType;
import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;
@@ -50,11 +43,10 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.FullNameCond
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCondition;
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition;
import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory;
-import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner;
-import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner.ProcessorCallback;
+import org.sleuthkit.autopsy.testutils.CaseUtils;
import org.sleuthkit.autopsy.testutils.IngestJobRunner;
+import org.sleuthkit.autopsy.testutils.IngestUtils;
import org.sleuthkit.datamodel.AbstractFile;
-import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
@@ -77,26 +69,14 @@ public class IngestFileFiltersTest extends NbTestCase {
@Override
public void tearDown() {
- try {
- Case.closeCurrentCase();
- //Seems like we need some time to close the case.
- try {
- Thread.sleep(2000);
- } catch (Exception ex) {
-
- }
- assertFalse(Case.isCaseOpen());
- } catch (CaseActionException ex) {
- Exceptions.printStackTrace(ex);
- Assert.fail(ex);
- }
+ CaseUtils.closeCase();
}
public void testBasicDir() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testBasicDir");
- createCase(casePath);
+ CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
- addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
+ IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap rule = new HashMap<>();
rule.put("Rule", new Rule("testFileType", null, new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null));
@@ -106,8 +86,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList templates = new ArrayList<>();
- templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
- runIngestJob(openCase.getDataSources(), templates, dirFilter);
+ templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
+ IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, dirFilter);
+ IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = openCase.getServices().getFileManager();
List results = fileManager.findFiles("file.jpg", "dir1");
String mimeType = results.get(0).getMIMEType();
@@ -136,9 +117,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testExtAndDirWithOneRule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithOneRule");
- createCase(casePath);
+ CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
- addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
+ IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap rules = new HashMap<>();
rules.put("Rule", new Rule("testExtAndDirWithOneRule", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null));
@@ -148,8 +129,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList templates = new ArrayList<>();
- templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
- runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter);
+ templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
+ IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter);
+ IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List results = fileManager.findFiles("%%");
assertEquals(62, results.size());
@@ -171,9 +153,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testExtAndDirWithTwoRules() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithTwoRules");
- createCase(casePath);
+ CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
- addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
+ IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@@ -184,8 +166,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList templates = new ArrayList<>();
- templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
- runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter);
+ templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
+ IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter);
+ IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List results = fileManager.findFiles("%%");
assertEquals(62, results.size());
@@ -215,9 +198,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testFullFileNameRule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testFullFileNameRule");
- createCase(casePath);
+ CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
- addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
+ IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap rules = new HashMap<>();
rules.put("rule", new Rule("FindFileWithFullName", new FullNameCondition("file.docx"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@@ -227,9 +210,9 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList templates = new ArrayList<>();
- templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
-
- runIngestJob(openCase.getDataSources(), templates, fullNameFilter);
+ templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
+ IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, fullNameFilter);
+ IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List results = fileManager.findFiles("%%");
assertEquals(62, results.size());
@@ -251,9 +234,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testCarvingWithExtRuleAndUnallocSpace() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingWithExtRuleAndUnallocSpace");
- createCase(casePath);
+ CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
- addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
+ IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@@ -265,9 +248,10 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList templates = new ArrayList<>();
- templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
- templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
- runIngestJob(openCase.getDataSources(), templates, extensionFilter);
+ templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
+ templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
+ IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, extensionFilter);
+ IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
List results = fileManager.findFiles("%%");
assertEquals(70, results.size());
@@ -299,9 +283,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testCarvingNoUnallocatedSpace() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingNoUnallocatedSpace");
- createCase(casePath);
+ CaseUtils.createCase(casePath);
ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
- addDataSourceToCase(dataSourceProcessor, IMAGE_PATH);
+ IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH);
HashMap rules = new HashMap<>();
rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null));
@@ -313,8 +297,8 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList templates = new ArrayList<>();
- templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
- templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
+ templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
+ templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory()));
IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, extensionFilter);
try {
List errs = IngestJobRunner.runIngestJob(openCase.getDataSources(), ingestJobSettings);
@@ -333,9 +317,9 @@ public class IngestFileFiltersTest extends NbTestCase {
public void testEmbeddedModule() {
Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testEmbeddedModule");
- createCase(casePath);
+ CaseUtils.createCase(casePath);
LocalFilesDSProcessor dataSourceProcessor = new LocalFilesDSProcessor();
- addDataSourceToCase(dataSourceProcessor, ZIPFILE_PATH);
+ IngestUtils.addDataSource(dataSourceProcessor, ZIPFILE_PATH);
//Build the filter to find jpg files
HashMap rules = new HashMap<>();
@@ -348,9 +332,10 @@ public class IngestFileFiltersTest extends NbTestCase {
try {
Case openCase = Case.getOpenCase();
ArrayList templates = new ArrayList<>();
- templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory()));
- templates.add(getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()));
- runIngestJob(openCase.getDataSources(), templates, embeddedFilter);
+ templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()));
+ templates.add(IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()));
+ IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, embeddedFilter);
+ IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings);
FileManager fileManager = Case.getOpenCase().getServices().getFileManager();
//get all .jpg files in zip file
List results = fileManager.findFiles("%%");
@@ -376,64 +361,4 @@ public class IngestFileFiltersTest extends NbTestCase {
Assert.fail(ex);
}
}
-
- private void runIngestJob(List datasources, ArrayList templates, FilesSet filter) {
- IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, filter);
- try {
- List 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 dataSourceContent = callBack.getDataSourceContent();
- assertEquals(1, dataSourceContent.size());
- List errorMessages = callBack.getErrorMessages();
- assertEquals(0, errorMessages.size());
- } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) {
- Exceptions.printStackTrace(ex);
- Assert.fail(ex);
-
- }
- }
}
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java
new file mode 100755
index 0000000000..4b11fe37fc
--- /dev/null
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java
@@ -0,0 +1,193 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 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 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 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 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 datasources, IngestModuleFactory factory) {
+ IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings();
+ IngestModuleTemplate template = new IngestModuleTemplate(factory, settings);
+ template.setEnabled(true);
+ ArrayList templates = new ArrayList<>();
+ templates.add(template);
+ IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates);
+ try {
+ List 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);
+ }
+ }
+
+}
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java
new file mode 100755
index 0000000000..dad3f6d142
--- /dev/null
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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());
+ }
+ }
+
+}
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java
new file mode 100755
index 0000000000..893f2d8cb2
--- /dev/null
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 errorMessages = callBack.getErrorMessages();
+ assertEquals(0, errorMessages.size());
+ } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) {
+ Exceptions.printStackTrace(ex);
+ Assert.fail(ex);
+
+ }
+ }
+
+ public static void runIngestJob(List datasources, IngestJobSettings ingestJobSettings) {
+ try {
+ List 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;
+ }
+
+}
diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml
index dbd5a31c4c..54e7d881b2 100644
--- a/Experimental/nbproject/project.xml
+++ b/Experimental/nbproject/project.xml
@@ -33,6 +33,14 @@
1.49.1
+
+ org.netbeans.swing.outline
+
+
+
+ 1.34.1
+
+ org.openide.awt
@@ -49,6 +57,14 @@
7.41.1
+
+ org.openide.explorer
+
+
+
+ 6.62.1
+
+ org.openide.filesystems
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java
new file mode 100644
index 0000000000..15374f22ee
--- /dev/null
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java
@@ -0,0 +1,155 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.
+ }
+ }
+}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form
index 9d2a343f56..214254e032 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form
@@ -26,37 +26,29 @@
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -65,60 +57,48 @@
-
+
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -126,22 +106,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -149,22 +113,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -233,34 +181,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -271,33 +191,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java
index 3389a64a03..12218f203c 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java
@@ -19,38 +19,23 @@
package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Cursor;
-import java.awt.EventQueue;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
-import javax.swing.DefaultListSelectionModel;
import java.awt.Color;
+import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.JPanel;
-import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.table.DefaultTableModel;
-import javax.swing.table.TableColumn;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.core.ServicesMonitor;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
-import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot;
-import org.sleuthkit.autopsy.guiutils.DurationCellRenderer;
-import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer;
-import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer;
/**
* A dashboard for monitoring an automated ingest cluster.
@@ -58,35 +43,12 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer;
final class AutoIngestDashboard extends JPanel implements Observer {
private static final long serialVersionUID = 1L;
- private static final int GENERIC_COL_MIN_WIDTH = 30;
- private static final int GENERIC_COL_MAX_WIDTH = 2000;
- private static final int PENDING_TABLE_COL_PREFERRED_WIDTH = 280;
- private static final int RUNNING_TABLE_COL_PREFERRED_WIDTH = 175;
- private static final int PRIORITY_COLUMN_PREFERRED_WIDTH = 60;
- private static final int PRIORITY_COLUMN_MAX_WIDTH = 150;
- private static final int STAGE_TIME_COL_MIN_WIDTH = 250;
- private static final int STAGE_TIME_COL_MAX_WIDTH = 450;
- private static final int TIME_COL_MIN_WIDTH = 30;
- private static final int TIME_COL_MAX_WIDTH = 250;
- private static final int TIME_COL_PREFERRED_WIDTH = 140;
- private static final int NAME_COL_MIN_WIDTH = 100;
- private static final int NAME_COL_MAX_WIDTH = 250;
- private static final int NAME_COL_PREFERRED_WIDTH = 140;
- private static final int STAGE_COL_MIN_WIDTH = 70;
- private static final int STAGE_COL_MAX_WIDTH = 2000;
- private static final int STAGE_COL_PREFERRED_WIDTH = 300;
- private static final int STATUS_COL_MIN_WIDTH = 55;
- private static final int STATUS_COL_MAX_WIDTH = 250;
- private static final int STATUS_COL_PREFERRED_WIDTH = 55;
- private static final int COMPLETED_TIME_COL_MIN_WIDTH = 30;
- private static final int COMPLETED_TIME_COL_MAX_WIDTH = 2000;
- private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280;
private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboard.class.getName());
- private final DefaultTableModel pendingTableModel;
- private final DefaultTableModel runningTableModel;
- private final DefaultTableModel completedTableModel;
private AutoIngestMonitor autoIngestMonitor;
-
+ private AutoIngestJobsPanel pendingJobsPanel;
+ private AutoIngestJobsPanel runningJobsPanel;
+ private AutoIngestJobsPanel completedJobsPanel;
+
/**
* Maintain a mapping of each service to it's last status update.
*/
@@ -113,38 +75,56 @@ final class AutoIngestDashboard extends JPanel implements Observer {
/**
* Constructs a panel for monitoring an automated ingest cluster.
*/
+ @Messages({"AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first",
+ "AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it",
+ "AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already"})
+
private AutoIngestDashboard() {
this.statusByService = new ConcurrentHashMap<>();
-
- pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
-
- runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
-
- completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0);
initComponents();
statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"));
statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"));
statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"));
setServicesStatusMessage();
- initPendingJobsTable();
- initRunningJobsTable();
- initCompletedJobsTable();
-
+ pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.PENDING_JOB);
+ pendingJobsPanel.setSize(pendingScrollPane.getSize());
+ pendingScrollPane.add(pendingJobsPanel);
+ pendingScrollPane.setViewportView(pendingJobsPanel);
+ pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable_toolTipText());
+ runningJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.RUNNING_JOB);
+ runningJobsPanel.setSize(runningScrollPane.getSize());
+ runningScrollPane.add(runningJobsPanel);
+ runningScrollPane.setViewportView(runningJobsPanel);
+ runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable_toolTipText());
+ completedJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.COMPLETED_JOB);
+ completedJobsPanel.setSize(completedScrollPane.getSize());
+ completedScrollPane.add(completedJobsPanel);
+ completedScrollPane.setViewportView(completedJobsPanel);
+ completedJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_completedTable_toolTipText());
/*
* Must set this flag, otherwise pop up menus don't close properly.
*/
+
UIManager.put("PopupMenu.consumeEventOnClose", false);
}
-
+
+ AutoIngestMonitor getMonitor() {
+ return autoIngestMonitor;
+ }
+
+ AutoIngestJobsPanel getPendingJobsPanel() {
+ return pendingJobsPanel;
+ }
+
/**
* Update status of the services on the dashboard
*/
private void displayServicesStatus() {
- tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message",
- statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()),
- statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()),
- statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()),
+ tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message",
+ 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.MESSAGING.toString())));
String upStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up");
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() {
new SwingWorker() {
-
+
@Override
protected Void doInBackground() throws Exception {
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();
}
- /**
- * Sets up the JTable that presents a view of the pending jobs queue for an
- * auto ingest cluster.
- */
- private void initPendingJobsTable() {
- /*
- * Remove some of the jobs table model columns from the JTable. This
- * does not remove the columns from the model, just from this table.
- */
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
- pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader()));
-
- /*
- * Set up a column to display the cases associated with the jobs.
- */
- TableColumn column;
- column = pendingTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader());
- column.setMinWidth(GENERIC_COL_MIN_WIDTH);
- column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
- column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
- column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the data sources associated with the jobs.
- */
- column = pendingTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader());
- column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
- column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
- column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the create times of the jobs.
- */
- column = pendingTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader());
- column.setCellRenderer(new LongDateCellRenderer());
- column.setMinWidth(TIME_COL_MIN_WIDTH);
- column.setMaxWidth(TIME_COL_MAX_WIDTH);
- column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH);
- column.setWidth(TIME_COL_PREFERRED_WIDTH);
-
- column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader());
- column.setCellRenderer(new PrioritizedIconCellRenderer());
- column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH);
- column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH);
- column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH);
- /*
- * Allow sorting when a column header is clicked.
- */
- pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel));
-
- /*
- * Create a row selection listener to enable/disable the Prioritize
- * button.
- */
- pendingTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
- if (e.getValueIsAdjusting()) {
- return;
- }
- int row = pendingTable.getSelectedRow();
-
- boolean enablePrioritizeButtons = false;
- boolean enableDeprioritizeButtons = false;
- if (row >= 0 && row < pendingTable.getRowCount()) {
- enablePrioritizeButtons = true;
- enableDeprioritizeButtons = (Integer) pendingTableModel.getValueAt(row, JobsTableModelColumns.PRIORITY.ordinal()) > 0;
- }
- this.prioritizeJobButton.setEnabled(enablePrioritizeButtons);
- this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons);
- this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons);
- this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons);
- });
- }
-
- /**
- * Sets up the JTable that presents a view of the running jobs list for an
- * auto ingest cluster.
- */
- private void initRunningJobsTable() {
- /*
- * Remove some of the jobs table model columns from the JTable. This
- * does not remove the columns from the model, just from this table.
- */
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()));
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader()));
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()));
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()));
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader()));
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader()));
- runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()));
- /*
- * Set up a column to display the cases associated with the jobs.
- */
- TableColumn column;
- column = runningTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader());
- column.setMinWidth(GENERIC_COL_MIN_WIDTH);
- column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
- column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
- column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the image folders associated with the
- * jobs.
- */
- column = runningTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader());
- column.setMinWidth(GENERIC_COL_MIN_WIDTH);
- column.setMaxWidth(GENERIC_COL_MAX_WIDTH);
- column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
- column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the host names of the cluster nodes
- * processing the jobs.
- */
- column = runningTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader());
- column.setMinWidth(NAME_COL_MIN_WIDTH);
- column.setMaxWidth(NAME_COL_MAX_WIDTH);
- column.setPreferredWidth(NAME_COL_PREFERRED_WIDTH);
- column.setWidth(NAME_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the ingest activities associated with the
- * jobs.
- */
- column = runningTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader());
- column.setMinWidth(STAGE_COL_MIN_WIDTH);
- column.setMaxWidth(STAGE_COL_MAX_WIDTH);
- column.setPreferredWidth(STAGE_COL_PREFERRED_WIDTH);
- column.setWidth(STAGE_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the ingest activity times associated with
- * the jobs.
- */
- column = runningTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader());
- column.setCellRenderer(new DurationCellRenderer());
- column.setMinWidth(GENERIC_COL_MIN_WIDTH);
- column.setMaxWidth(STAGE_TIME_COL_MAX_WIDTH);
- column.setPreferredWidth(STAGE_TIME_COL_MIN_WIDTH);
- column.setWidth(STAGE_TIME_COL_MIN_WIDTH);
-
- /*
- * Prevent sorting when a column header is clicked.
- */
- runningTable.setAutoCreateRowSorter(false);
- }
-
- /**
- * Sets up the JTable that presents a view of the completed jobs list for an
- * auto ingest cluster.
- */
- private void initCompletedJobsTable() {
- /*
- * Remove some of the jobs table model columns from the JTable. This
- * does not remove the columns from the model, just from this table.
- */
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader()));
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()));
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()));
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()));
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader()));
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader()));
- completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()));
- /*
- * Set up a column to display the cases associated with the jobs.
- */
- TableColumn column;
- column = completedTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader());
- column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH);
- column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH);
- column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
- column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the image folders associated with the
- * jobs.
- */
- column = completedTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader());
- column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH);
- column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH);
- column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
- column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the create times of the jobs.
- */
- column = completedTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader());
- column.setCellRenderer(new LongDateCellRenderer());
- column.setMinWidth(TIME_COL_MIN_WIDTH);
- column.setMaxWidth(TIME_COL_MAX_WIDTH);
- column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH);
- column.setWidth(TIME_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the completed times of the jobs.
- */
- column = completedTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader());
- column.setCellRenderer(new LongDateCellRenderer());
- column.setMinWidth(TIME_COL_MIN_WIDTH);
- column.setMaxWidth(TIME_COL_MAX_WIDTH);
- column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH);
- column.setWidth(TIME_COL_PREFERRED_WIDTH);
-
- /*
- * Set up a column to display the statuses of the jobs, with a cell
- * renderer that will choose an icon to represent the job status.
- */
- column = completedTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader());
- column.setCellRenderer(new StatusIconCellRenderer());
- column.setMinWidth(STATUS_COL_MIN_WIDTH);
- column.setMaxWidth(STATUS_COL_MAX_WIDTH);
- column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH);
- column.setWidth(STATUS_COL_PREFERRED_WIDTH);
- /*
- * Allow sorting when a column header is clicked.
- */
- completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel));
- }
-
/**
* Starts up the auto ingest monitor and adds this panel as an observer,
* subscribes to services monitor events and starts a task to populate the
@@ -437,10 +190,10 @@ final class AutoIngestDashboard extends JPanel implements Observer {
private void startUp() throws AutoIngestMonitor.AutoIngestMonitorException {
PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> {
-
+
String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString();
String status = evt.getNewValue().toString();
-
+
if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) {
status = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up");
LOGGER.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS
@@ -450,31 +203,48 @@ final class AutoIngestDashboard extends JPanel implements Observer {
} else {
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 (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) {
return;
}
-
+
statusByService.put(serviceDisplayName, status);
displayServicesStatus();
};
-
+
// Subscribe to all multi-user services in order to display their status
Set servicesList = new HashSet<>();
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());
ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener);
-
+
autoIngestMonitor = new AutoIngestMonitor();
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
public void update(Observable observable, Object arg) {
- EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg));
+ EventQueue.invokeLater(() -> {
+ refreshTables();
+ });
}
/**
@@ -483,177 +253,10 @@ final class AutoIngestDashboard extends JPanel implements Observer {
*
* @param jobsSnapshot The jobs snapshot.
*/
- private void refreshTables(JobsSnapshot jobsSnapshot) {
- List pendingJobs = jobsSnapshot.getPendingJobs();
- List runningJobs = jobsSnapshot.getRunningJobs();
- List completedJobs = jobsSnapshot.getCompletedJobs();
- pendingJobs.sort(new AutoIngestJob.PriorityComparator());
- runningJobs.sort(new AutoIngestJob.DataSourceFileNameComparator());
- completedJobs.sort(new AutoIngestJob.CompletedDateDescendingComparator());
- refreshTable(pendingJobs, pendingTable, pendingTableModel);
- refreshTable(runningJobs, runningTable, runningTableModel);
- refreshTable(completedJobs, completedTable, completedTableModel);
- }
-
- /**
- * Reloads the table model for an auto ingest jobs table and refreshes the
- * JTable that uses the model.
- *
- * @param jobs The list of auto ingest jobs.
- * @param tableModel The table model.
- * @param comparator An optional comparator (may be null) for sorting the
- * table model.
- */
- private void refreshTable(List jobs, JTable table, DefaultTableModel tableModel) {
- try {
- Path currentRow = getSelectedEntry(table, tableModel);
- tableModel.setRowCount(0);
- for (AutoIngestJob job : jobs) {
- AutoIngestJob.StageDetails status = job.getProcessingStageDetails();
- tableModel.addRow(new Object[]{
- job.getManifest().getCaseName(), // CASE
- job.getManifest().getDataSourcePath().getFileName(), job.getProcessingHostName(), // HOST_NAME
- job.getManifest().getDateFileCreated(), // CREATED_TIME
- job.getProcessingStageStartDate(), // STARTED_TIME
- job.getCompletedDate(), // COMPLETED_TIME
- status.getDescription(), // STAGE
- job.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, // STATUS
- ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // STAGE_TIME
- job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH
- job.getManifest().getFilePath(), // MANIFEST_FILE_PATH
- job.getPriority(), // PRIORITY
- job
- });
- }
- setSelectedEntry(table, tableModel, currentRow);
- } catch (Exception ex) {
- LOGGER.log(Level.SEVERE, "Error refreshing table " + table.toString(), ex);
- }
- }
-
- /**
- * Gets a path representing the current selection in a table.
- *
- * @param table The table.
- * @param tableModel The table model of the table.
- *
- * @return A path representing the current selection, or null if there is no
- * selection.
- */
- Path getSelectedEntry(JTable table, DefaultTableModel tableModel) {
- try {
- int currentlySelectedRow = table.getSelectedRow();
- if (currentlySelectedRow >= 0 && currentlySelectedRow < table.getRowCount()) {
- return Paths.get(tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.CASE.ordinal()).toString(),
- tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString());
- }
- } catch (Exception ignored) {
- return null;
- }
- return null;
- }
-
- /**
- * Sets the selection of the table to the passed-in path's item, if that
- * item exists in the table. If it does not, clears the table selection.
- *
- * @param table The table.
- * @param tableModel The table model of the table.
- * @param path The path of the item to set
- */
- void setSelectedEntry(JTable table, DefaultTableModel tableModel, Path path) {
- if (path != null) {
- try {
- for (int row = 0; row < table.getRowCount(); ++row) {
- Path temp = Paths.get(tableModel.getValueAt(row, JobsTableModelColumns.CASE.ordinal()).toString(),
- tableModel.getValueAt(row, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString());
- if (temp.compareTo(path) == 0) { // found it
- table.setRowSelectionInterval(row, row);
- return;
- }
- }
- } catch (Exception ignored) {
- table.clearSelection();
- }
- }
- table.clearSelection();
- }
-
- /*
- * This enum is used in conjunction with the DefaultTableModel class to
- * provide table models for the JTables used to display a view of the
- * pending jobs queue, running jobs list, and completed jobs list for an
- * auto ingest cluster. The enum allows the columns of the table model to be
- * described by either an enum ordinal or a column header string.
- */
- private enum JobsTableModelColumns {
- @Messages({"AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority=Prioritized"})
-
- CASE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Case")),
- DATA_SOURCE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder")),
- HOST_NAME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.HostName")),
- CREATED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CreatedTime")),
- STARTED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StartedTime")),
- COMPLETED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime")),
- STAGE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage")),
- STAGE_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime")),
- STATUS(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Status")),
- CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder")),
- MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath")),
- PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority")),
- JOB(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Job"));
-
- private final String header;
-
- private JobsTableModelColumns(String header) {
- this.header = header;
- }
-
- private String getColumnHeader() {
- return header;
- }
-
- private static final String[] headers = {
- CASE.getColumnHeader(),
- DATA_SOURCE.getColumnHeader(),
- HOST_NAME.getColumnHeader(),
- CREATED_TIME.getColumnHeader(),
- STARTED_TIME.getColumnHeader(),
- COMPLETED_TIME.getColumnHeader(),
- STAGE.getColumnHeader(),
- STATUS.getColumnHeader(),
- STAGE_TIME.getColumnHeader(),
- CASE_DIRECTORY_PATH.getColumnHeader(),
- MANIFEST_FILE_PATH.getColumnHeader(),
- PRIORITY.getColumnHeader(),
- JOB.getColumnHeader()
- };
- };
-
- /**
- * A task that refreshes the UI components on this panel to reflect a
- * snapshot of the pending, running and completed auto ingest jobs lists of
- * an auto ingest cluster.
- */
- private class RefreshComponentsTask implements Runnable {
-
- private final JobsSnapshot jobsSnapshot;
-
- /**
- * Constructs a task that refreshes the UI components on this panel to
- * reflect a snapshot of the pending, running and completed auto ingest
- * jobs lists of an auto ingest cluster.
- *
- * @param jobsSnapshot The jobs snapshot.
- */
- RefreshComponentsTask(JobsSnapshot jobsSnapshot) {
- this.jobsSnapshot = jobsSnapshot;
- }
-
- @Override
- public void run() {
- refreshTables(jobsSnapshot);
- }
+ void refreshTables() {
+ pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
+ runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
+ completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot());
}
/**
@@ -698,75 +301,22 @@ final class AutoIngestDashboard extends JPanel implements Observer {
jButton1 = new javax.swing.JButton();
pendingScrollPane = new javax.swing.JScrollPane();
- pendingTable = new javax.swing.JTable();
runningScrollPane = new javax.swing.JScrollPane();
- runningTable = new javax.swing.JTable();
completedScrollPane = new javax.swing.JScrollPane();
- completedTable = new javax.swing.JTable();
lbPending = new javax.swing.JLabel();
lbRunning = new javax.swing.JLabel();
lbCompleted = new javax.swing.JLabel();
refreshButton = new javax.swing.JButton();
lbServicesStatus = new javax.swing.JLabel();
tbServicesStatusMessage = new javax.swing.JTextField();
- prioritizeJobButton = new javax.swing.JButton();
- prioritizeCaseButton = new javax.swing.JButton();
clusterMetricsButton = new javax.swing.JButton();
- deprioritizeJobButton = new javax.swing.JButton();
- deprioritizeCaseButton = new javax.swing.JButton();
org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N
- pendingTable.setModel(pendingTableModel);
- pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N
- pendingTable.setRowHeight(20);
- pendingTable.setSelectionModel(new DefaultListSelectionModel() {
- private static final long serialVersionUID = 1L;
- @Override
- public void setSelectionInterval(int index0, int index1) {
- if (index0 == pendingTable.getSelectedRow()) {
- pendingTable.clearSelection();
- } else {
- super.setSelectionInterval(index0, index1);
- }
- }
- });
- pendingTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
- pendingScrollPane.setViewportView(pendingTable);
-
- runningTable.setModel(runningTableModel);
- runningTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.runningTable.toolTipText")); // NOI18N
- runningTable.setRowHeight(20);
- runningTable.setSelectionModel(new DefaultListSelectionModel() {
- private static final long serialVersionUID = 1L;
- @Override
- public void setSelectionInterval(int index0, int index1) {
- if (index0 == runningTable.getSelectedRow()) {
- runningTable.clearSelection();
- } else {
- super.setSelectionInterval(index0, index1);
- }
- }
- });
- runningTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
- runningScrollPane.setViewportView(runningTable);
-
- completedTable.setModel(completedTableModel);
- completedTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.completedTable.toolTipText")); // NOI18N
- completedTable.setRowHeight(20);
- completedTable.setSelectionModel(new DefaultListSelectionModel() {
- private static final long serialVersionUID = 1L;
- @Override
- public void setSelectionInterval(int index0, int index1) {
- if (index0 == completedTable.getSelectedRow()) {
- completedTable.clearSelection();
- } else {
- super.setSelectionInterval(index0, index1);
- }
- }
- });
- completedTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
- completedScrollPane.setViewportView(completedTable);
+ pendingScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ pendingScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
+ pendingScrollPane.setOpaque(false);
+ pendingScrollPane.setPreferredSize(new java.awt.Dimension(2, 215));
lbPending.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(lbPending, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.lbPending.text")); // NOI18N
@@ -793,24 +343,6 @@ final class AutoIngestDashboard extends JPanel implements Observer {
tbServicesStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.text")); // NOI18N
tbServicesStatusMessage.setBorder(null);
- org.openide.awt.Mnemonics.setLocalizedText(prioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.text")); // NOI18N
- prioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.toolTipText")); // NOI18N
- prioritizeJobButton.setEnabled(false);
- prioritizeJobButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- prioritizeJobButtonActionPerformed(evt);
- }
- });
-
- org.openide.awt.Mnemonics.setLocalizedText(prioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.text")); // NOI18N
- prioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.toolTipText")); // NOI18N
- prioritizeCaseButton.setEnabled(false);
- prioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- prioritizeCaseButtonActionPerformed(evt);
- }
- });
-
org.openide.awt.Mnemonics.setLocalizedText(clusterMetricsButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.clusterMetricsButton.text")); // NOI18N
clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
@@ -818,24 +350,6 @@ final class AutoIngestDashboard extends JPanel implements Observer {
}
});
- org.openide.awt.Mnemonics.setLocalizedText(deprioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.text")); // NOI18N
- deprioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.toolTipText")); // NOI18N
- deprioritizeJobButton.setEnabled(false);
- deprioritizeJobButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- deprioritizeJobButtonActionPerformed(evt);
- }
- });
-
- org.openide.awt.Mnemonics.setLocalizedText(deprioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.text")); // NOI18N
- deprioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.toolTipText")); // NOI18N
- deprioritizeCaseButton.setEnabled(false);
- deprioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- deprioritizeCaseButtonActionPerformed(evt);
- }
- });
-
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@@ -843,35 +357,27 @@ final class AutoIngestDashboard extends JPanel implements Observer {
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addComponent(pendingScrollPane)
+ .addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(runningScrollPane, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(completedScrollPane, javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
+ .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(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
- .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(18, 18, 18)
- .addComponent(prioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(deprioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(18, 18, 18)
- .addComponent(prioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(deprioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(18, 18, 18)
- .addComponent(clusterMetricsButton))
.addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
- .addComponent(lbServicesStatus)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE)))
- .addGap(0, 0, Short.MAX_VALUE)))
+ .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING))
+ .addGap(0, 0, Short.MAX_VALUE))
+ .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
+ .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(clusterMetricsButton)))
.addContainerGap())
);
- layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, prioritizeCaseButton, prioritizeJobButton, refreshButton});
+ layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, refreshButton});
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -883,23 +389,19 @@ final class AutoIngestDashboard extends JPanel implements Observer {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lbPending, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(1, 1, 1)
- .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(lbRunning)
.addGap(1, 1, 1)
- .addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(runningScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 133, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(lbCompleted)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 179, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(completedScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(refreshButton)
- .addComponent(prioritizeJobButton)
- .addComponent(prioritizeCaseButton)
- .addComponent(clusterMetricsButton)
- .addComponent(deprioritizeJobButton)
- .addComponent(deprioritizeCaseButton))
+ .addComponent(clusterMetricsButton))
.addContainerGap())
);
}// //GEN-END:initComponents
@@ -913,135 +415,25 @@ final class AutoIngestDashboard extends JPanel implements Observer {
*/
private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- JobsSnapshot jobsSnapshot = autoIngestMonitor.refreshJobsSnapshot();
- refreshTables(jobsSnapshot);
+ refreshTables();
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}//GEN-LAST:event_refreshButtonActionPerformed
- @Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."})
- private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed
- if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
- setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal()));
- JobsSnapshot jobsSnapshot;
- try {
- jobsSnapshot = autoIngestMonitor.prioritizeJob(job);
- refreshTables(jobsSnapshot);
- } catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
- String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath());
- LOGGER.log(Level.SEVERE, errorMessage, ex);
- MessageNotifyUtil.Message.error(errorMessage);
- }
- setCursor(Cursor.getDefaultCursor());
- }
- }//GEN-LAST:event_prioritizeJobButtonActionPerformed
-
- @Messages({"AutoIngestDashboard.errorMessage.casePrioritization=Failed to prioritize case \"%s\"."})
- private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed
- if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
- setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString();
- JobsSnapshot jobsSnapshot;
- try {
- jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName);
- refreshTables(jobsSnapshot);
- } catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
- String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName);
- LOGGER.log(Level.SEVERE, errorMessage, ex);
- MessageNotifyUtil.Message.error(errorMessage);
- }
- setCursor(Cursor.getDefaultCursor());
- }
- }//GEN-LAST:event_prioritizeCaseButtonActionPerformed
-
private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed
new AutoIngestMetricsDialog(this.getTopLevelAncestor());
}//GEN-LAST:event_clusterMetricsButtonActionPerformed
- @Messages({"AutoIngestDashboard.errorMessage.jobDeprioritization=Failed to deprioritize job \"%s\"."})
- private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed
- if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
- setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal()));
- JobsSnapshot jobsSnapshot;
- try {
- jobsSnapshot = autoIngestMonitor.deprioritizeJob(job);
- refreshTables(jobsSnapshot);
- } catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
- String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath());
- LOGGER.log(Level.SEVERE, errorMessage, ex);
- MessageNotifyUtil.Message.error(errorMessage);
- }
- setCursor(Cursor.getDefaultCursor());
- }
- }//GEN-LAST:event_deprioritizeJobButtonActionPerformed
-
- @Messages({"AutoIngestDashboard.errorMessage.caseDeprioritization=Failed to deprioritize case \"%s\"."})
- private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed
- if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) {
- setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString();
- JobsSnapshot jobsSnapshot;
- try {
- jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName);
- refreshTables(jobsSnapshot);
- } catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
- String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName);
- LOGGER.log(Level.SEVERE, errorMessage, ex);
- MessageNotifyUtil.Message.error(errorMessage);
- }
- setCursor(Cursor.getDefaultCursor());
- }
- }//GEN-LAST:event_deprioritizeCaseButtonActionPerformed
-
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton clusterMetricsButton;
private javax.swing.JScrollPane completedScrollPane;
- private javax.swing.JTable completedTable;
- private javax.swing.JButton deprioritizeCaseButton;
- private javax.swing.JButton deprioritizeJobButton;
private javax.swing.JButton jButton1;
private javax.swing.JLabel lbCompleted;
private javax.swing.JLabel lbPending;
private javax.swing.JLabel lbRunning;
private javax.swing.JLabel lbServicesStatus;
private javax.swing.JScrollPane pendingScrollPane;
- private javax.swing.JTable pendingTable;
- private javax.swing.JButton prioritizeCaseButton;
- private javax.swing.JButton prioritizeJobButton;
private javax.swing.JButton refreshButton;
private javax.swing.JScrollPane runningScrollPane;
- private javax.swing.JTable runningTable;
private javax.swing.JTextField tbServicesStatusMessage;
// End of variables declaration//GEN-END:variables
-
- private class AutoIngestTableModel extends DefaultTableModel {
-
- private static final long serialVersionUID = 1L;
-
- private AutoIngestTableModel(String[] headers, int i) {
- super(headers, i);
- }
-
- @Override
- public boolean isCellEditable(int row, int column) {
- return false;
- }
-
- @Override
- public Class> getColumnClass(int columnIndex) {
- if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) {
- return Integer.class;
- } else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal()
- || columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal()
- || columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal()
- || columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) {
- return Date.class;
- } else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) {
- return Boolean.class;
- } else {
- return super.getColumnClass(columnIndex);
- }
- }
- }
}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form
index 63887aceb0..5f3eab1a5f 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form
@@ -25,4 +25,4 @@
-
\ No newline at end of file
+
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java
index 83bad29a99..915e2a981f 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.experimental.autoingest;
+import java.awt.Component;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -74,7 +75,7 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
AutoIngestDashboard dashboard = AutoIngestDashboard.createDashboard();
tc.add(dashboard);
dashboard.setSize(dashboard.getPreferredSize());
-
+
tc.open();
}
tc.toFront();
@@ -86,17 +87,24 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
}
}
- public static void closeTopComponent() {
+ @Override
+ protected void componentClosed() {
if (topComponentInitialized) {
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) {
try {
+ for (Component comp : getComponents()) {
+ if (comp instanceof AutoIngestDashboard) {
+ ((AutoIngestDashboard) comp).shutDown();
+ }
+ }
tc.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to close " + PREFERRED_ID, e); // NON-NLS
}
}
}
+ super.componentClosed();
}
public AutoIngestDashboardTopComponent() {
@@ -104,6 +112,20 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
setName(Bundle.CTL_AutoIngestDashboardTopComponent());
}
+ /**
+ * Get the current AutoIngestDashboard if there is one.
+ *
+ * @return the current AutoIngestDashboard or null if there is not one
+ */
+ AutoIngestDashboard getAutoIngestDashboard() {
+ for (Component comp : getComponents()) {
+ if (comp instanceof AutoIngestDashboard) {
+ return (AutoIngestDashboard) comp;
+ }
+ }
+ return null;
+ }
+
@Override
public List availableModes(List modes) {
/*
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java
new file mode 100644
index 0000000000..b623a2281f
--- /dev/null
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java
@@ -0,0 +1,235 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 {
+
+ 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 list) {
+ List 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 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
+ }
+}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form
new file mode 100644
index 0000000000..bde4db4324
--- /dev/null
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form
@@ -0,0 +1,18 @@
+
+
+
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java
new file mode 100644
index 0000000000..843089f948
--- /dev/null
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java
@@ -0,0 +1,211 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ setLayout(new java.awt.BorderLayout());
+ }// //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
+
+}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties
index 633d069cc8..6194815f69 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties
@@ -10,9 +10,6 @@ AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Job Completed
AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage=Stage
AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status
AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path
-AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first
-AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it
-AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already
AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime=Time in Stage
AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case
AutoIngestDashboard.JobsTableModel.ColumnHeader.Job=Job
@@ -222,10 +219,6 @@ FileExporterSettingsPanel.SaveTooltip_1=Save the current rule
AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables
AutoIngestDashboard.refreshButton.text=&Refresh
AutoIngestDashboard.jButton1.text=jButton1
-AutoIngestDashboard.prioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue.
-AutoIngestDashboard.prioritizeJobButton.text=Prioritize &Job
-AutoIngestDashboard.prioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue.
-AutoIngestDashboard.prioritizeCaseButton.text=Prioritize &Case
AutoIngestMetricsDialog.reportTextArea.text=
AutoIngestDashboard.clusterMetricsButton.text=Auto Ingest &Metrics
AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report
@@ -236,10 +229,6 @@ ArchiveFilePanel.browseButton.text=Browse
ArchiveFilePanel.pathTextField.text=
ArchiveFilePanel.errorLabel.text=Error Label
AutoIngestMetricsDialog.startingDataLabel.text=Starting Date:
-AutoIngestDashboard.deprioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue.
-AutoIngestDashboard.deprioritizeJobButton.text=Deprioritize J&ob
-AutoIngestDashboard.deprioritizeCaseButton.text=Deprioritize C&ase
-AutoIngestDashboard.deprioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue.
AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job
AutoIngestControlPanel.bnPrioritizeCase.text=Prioritize Case
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java
new file mode 100644
index 0000000000..d50a85f906
--- /dev/null
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java
@@ -0,0 +1,249 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.
+ }
+ }
+}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java
index 7490c3e7b1..e5f67909da 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java
@@ -49,6 +49,7 @@ final class AddMemoryImageTask implements Runnable {
private final DataSourceProcessorCallback callback;
private volatile VolatilityProcessor volatilityProcessor;
private volatile boolean isCancelled;
+ private final String profile; // empty for autodetect
/**
* Constructs a runnable that adds a memory image to a case database.
@@ -57,6 +58,7 @@ final class AddMemoryImageTask implements Runnable {
* associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID).
* @param memoryImagePath Path to the memory image file.
+ * @param profile Volatility profile to run or empty string to autodetect
* @param pluginsToRun The Volatility plugins to run.
* @param timeZone The time zone to use when processing dates and
* times for the image, obtained from
@@ -65,9 +67,10 @@ final class AddMemoryImageTask implements Runnable {
* during processing.
* @param callback Callback to call when processing is done.
*/
- AddMemoryImageTask(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
+ AddMemoryImageTask(String deviceId, String memoryImagePath, String profile, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
this.deviceId = deviceId;
this.memoryImagePath = memoryImagePath;
+ this.profile = profile;
this.pluginsToRun = pluginsToRun;
this.timeZone = timeZone;
this.callback = callback;
@@ -94,7 +97,7 @@ final class AddMemoryImageTask implements Runnable {
try {
Image dataSource = addImageToCase();
dataSources.add(dataSource);
- volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, pluginsToRun, progressMonitor);
+ volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, profile, pluginsToRun, progressMonitor);
volatilityProcessor.run();
} catch (NoCurrentCaseException | TskCoreException | VolatilityProcessor.VolatilityProcessorException ex) {
criticalErrorOccurred = true;
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties
index 007b78dc5a..1c7d3312da 100755
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties
@@ -3,11 +3,10 @@
# and open the template in the editor.
MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName=Browse for a memory image file:
-MemoryDSInputPanel.PluginsToRunLabel.text=Available plugins to run:
-MemoryDSInputPanel.volExecutableLabel.text=Version of Volatility to Run:
+MemoryDSInputPanel.PluginsToRunLabel.text=Plugins to run:
MemoryDSInputPanel.pathLabel.text=Browse for a memory image file:
MemoryDSInputPanel.pathTextField.text=
MemoryDSInputPanel.errorLabel.text=Error Label
MemoryDSInputPanel.browseButton.text=Browse
-MemoryDSImputPanel.pathTextField.text=
-MemoryDSInputPanel.timeZoneLabel.text=Please select the input timezone:
\ No newline at end of file
+MemoryDSInputPanel.timeZoneLabel.text=Timezone:
+MemoryDSInputPanel.profileLabel.text=Profile:
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form
index 123f9b308b..f899eeb503 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form
@@ -28,13 +28,15 @@
-
-
+
+
-
-
+
+
+
+
@@ -43,8 +45,8 @@
-
+
@@ -66,17 +68,17 @@
-
+
-
-
+
+
-
+
-
+
@@ -85,7 +87,7 @@
-
+
@@ -97,14 +99,17 @@
-
+
+
+
+
-
+
@@ -117,14 +122,14 @@
-
+
-
+
@@ -139,27 +144,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -174,7 +158,7 @@
-
+
@@ -189,5 +173,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java
index f96a9cc801..134cdbc914 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java
@@ -26,7 +26,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SimpleTimeZone;
+import java.util.SortedSet;
import java.util.TimeZone;
+import java.util.TreeSet;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JTable;
@@ -42,6 +44,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PathValidator;
+@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private static final long serialVersionUID = 1L; //default
@@ -53,13 +56,26 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private final PluginListTableModel tableModel = new PluginListTableModel();
private final List PluginListNames = new ArrayList<>();
private final Map pluginListStates = new HashMap<>(); // is set by listeners when users select and deselect items
- private final Boolean isEnabled = true;
+ private final SortedSet profileList = new TreeSet<>(Arrays.asList(
+ "VistaSP0x64", "VistaSP0x86", "VistaSP1x64", "VistaSP1x86",
+ "VistaSP2x64", "VistaSP2x86", "Win10x64", "Win10x64_10586",
+ "Win10x64_14393", "Win10x86", "Win10x86_10586", "Win10x86_14393",
+ "Win2003SP0x86", "Win2003SP1x64", "Win2003SP1x86", "Win2003SP2x64",
+ "Win2003SP2x86", "Win2008R2SP0x64", "Win2008R2SP1x64", "Win2008R2SP1x64_23418",
+ "Win2008SP1x64", "Win2008SP1x86", "Win2008SP2x64", "Win2008SP2x86",
+ "Win2012R2x64", "Win2012R2x64_18340", "Win2012x64", "Win2016x64_14393",
+ "Win7SP0x64", "Win7SP0x86", "Win7SP1x64", "Win7SP1x64_23418", "Win7SP1x86_23418",
+ "Win81U1x64", "Win81U1x86", "Win8SP0x64", "Win8SP0x86", "Win8SP1x64",
+ "Win8SP1x64_18340", "Win8SP1x86", "WinXPSP1x64", "WinXPSP2x64", "WinXPSP2x86",
+ "WinXPSP3x86"));
+ private final static String AUTODETECT_PROFILE = "Auto Detect";
+
/**
* Creates new MemoryDSInputPanel panel for user input
*/
private MemoryDSInputPanel(String context) {
- this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo"};
+ this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo", "dlldump", "moddump", "procdump", "dumpfiles", "dumpregistry"};
Arrays.sort(this.pluginList);
initComponents();
@@ -82,7 +98,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
instance.postInit();
instance.customizePluginListTable();
instance.createTimeZoneList();
- instance.createVolatilityVersionList();
+ instance.populateProfileCombobox();
instance.createPluginList();
return instance;
@@ -95,14 +111,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
}
private void customizePluginListTable() {
- PluginList.setModel(tableModel);
- PluginList.setTableHeader(null);
- PluginList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ pluginTable.setModel(tableModel);
+ pluginTable.setTableHeader(null);
+ pluginTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
final int width = listsScrollPane.getPreferredSize().width;
- PluginList.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
+ pluginTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
TableColumn column;
- for (int i = 0; i < PluginList.getColumnCount(); i++) {
- column = PluginList.getColumnModel().getColumn(i);
+ for (int i = 0; i < pluginTable.getColumnCount(); i++) {
+ column = pluginTable.getColumnModel().getColumn(i);
if (i == 0) {
column.setPreferredWidth(((int) (width * 0.07)));
} else {
@@ -138,11 +154,12 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
timeZoneComboBox.setSelectedItem(formatted);
}
- private void createVolatilityVersionList() {
-
- volExecutableComboBox.addItem("2.6");
- volExecutableComboBox.addItem("2.5");
-
+
+ private void populateProfileCombobox() {
+ profileComboBox.addItem(AUTODETECT_PROFILE);
+ profileList.forEach((profile) -> {
+ profileComboBox.addItem(profile);
+ });
}
private void createPluginList() {
@@ -157,8 +174,10 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
PluginListNames.add(plugin);
if (allEnabled) {
pluginListStates.put(plugin, true);
+ } else if ((pluginMap.containsKey(plugin) && pluginMap.get(plugin).equals("false"))) {
+ pluginListStates.put(plugin, false);
} else {
- pluginListStates.put(plugin, pluginMap.containsKey(plugin));
+ pluginListStates.put(plugin, true);
}
}
tableModel.fireTableDataChanged();
@@ -181,15 +200,20 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
errorLabel = new javax.swing.JLabel();
timeZoneLabel = new javax.swing.JLabel();
timeZoneComboBox = new javax.swing.JComboBox<>();
- volExecutableLabel = new javax.swing.JLabel();
- volExecutableComboBox = new javax.swing.JComboBox<>();
PluginsToRunLabel = new javax.swing.JLabel();
listsScrollPane = new javax.swing.JScrollPane();
- PluginList = new javax.swing.JTable();
+ pluginTable = new javax.swing.JTable();
+ profileLabel = new javax.swing.JLabel();
+ profileComboBox = new javax.swing.JComboBox<>();
org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.text")); // NOI18N
pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathTextField.text")); // NOI18N
+ pathTextField.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ pathTextFieldActionPerformed(evt);
+ }
+ });
org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.browseButton.text")); // NOI18N
browseButton.addActionListener(new java.awt.event.ActionListener() {
@@ -205,18 +229,9 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
timeZoneComboBox.setMaximumRowCount(30);
- org.openide.awt.Mnemonics.setLocalizedText(volExecutableLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.volExecutableLabel.text")); // NOI18N
-
- volExecutableComboBox.setEnabled(false);
- volExecutableComboBox.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- volExecutableComboBoxActionPerformed(evt);
- }
- });
-
org.openide.awt.Mnemonics.setLocalizedText(PluginsToRunLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.PluginsToRunLabel.text")); // NOI18N
- PluginList.setModel(new javax.swing.table.DefaultTableModel(
+ pluginTable.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
{},
{},
@@ -227,7 +242,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
}
));
- listsScrollPane.setViewportView(PluginList);
+ listsScrollPane.setViewportView(pluginTable);
+
+ org.openide.awt.Mnemonics.setLocalizedText(profileLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.profileLabel.text")); // NOI18N
+
+ profileComboBox.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ profileComboBoxActionPerformed(evt);
+ }
+ });
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@@ -241,18 +264,19 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 218, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
- .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 168, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 134, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 199, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE))))
+ .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
+ .addComponent(profileComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, 243, Short.MAX_VALUE)
+ .addComponent(timeZoneComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))))
.addGap(0, 163, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(errorLabel)
- .addComponent(volExecutableLabel)
- .addComponent(PluginsToRunLabel))
+ .addComponent(PluginsToRunLabel)
+ .addComponent(profileLabel))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
layout.setVerticalGroup(
@@ -269,15 +293,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(errorLabel)
- .addGap(18, 18, 18)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(volExecutableLabel)
- .addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addComponent(profileLabel)
+ .addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(PluginsToRunLabel)
- .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addContainerGap(30, Short.MAX_VALUE))
+ .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addContainerGap(73, Short.MAX_VALUE))
);
pathLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName")); // NOI18N
@@ -298,12 +322,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
}
}//GEN-LAST:event_browseButtonActionPerformed
- private void volExecutableComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_volExecutableComboBoxActionPerformed
+ private void profileComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_profileComboBoxActionPerformed
// TODO add your handling code here:
- }//GEN-LAST:event_volExecutableComboBoxActionPerformed
+ }//GEN-LAST:event_profileComboBoxActionPerformed
+
+ private void pathTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pathTextFieldActionPerformed
+ // TODO add your handling code here:
+ }//GEN-LAST:event_pathTextFieldActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
- private javax.swing.JTable PluginList;
private javax.swing.JLabel PluginsToRunLabel;
private javax.swing.JButton browseButton;
private javax.swing.JLabel errorLabel;
@@ -311,10 +338,11 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
private javax.swing.JScrollPane listsScrollPane;
private javax.swing.JLabel pathLabel;
private javax.swing.JTextField pathTextField;
+ private javax.swing.JTable pluginTable;
+ private javax.swing.JComboBox profileComboBox;
+ private javax.swing.JLabel profileLabel;
private javax.swing.JComboBox timeZoneComboBox;
private javax.swing.JLabel timeZoneLabel;
- private javax.swing.JComboBox volExecutableComboBox;
- private javax.swing.JLabel volExecutableLabel;
// End of variables declaration//GEN-END:variables
/**
* Get the path of the user selected image.
@@ -325,17 +353,28 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener {
return pathTextField.getText();
}
+ /**
+ *
+ * @return Profile or empty string if auto detect
+ */
+ String getProfile() {
+ String profile = (String)profileComboBox.getSelectedItem();
+ if (profile.equals(AUTODETECT_PROFILE)) {
+ return "";
+ }
+ return profile;
+ }
+
List getPluginsToRun() {
List enabledPlugins = new ArrayList<>();
- Map pluginMap = new HashMap<>();
+ Map pluginSettingsToSave = new HashMap<>();
for (String plugin : PluginListNames) {
if (pluginListStates.get(plugin)) {
enabledPlugins.add(plugin);
- pluginMap.put(plugin, "");
}
+ pluginSettingsToSave.put(plugin, pluginListStates.get(plugin).toString());
}
-
- ModuleSettings.setConfigSettings(this.contextName, pluginMap);
+ ModuleSettings.setConfigSettings(this.contextName, pluginSettingsToSave);
// @@ Could return keys of set
return enabledPlugins;
}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java
index 9791ad1f09..dc05cb8376 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java
@@ -117,7 +117,7 @@ public class MemoryDSProcessor implements DataSourceProcessor {
@Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
configPanel.storeSettings();
- run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback);
+ run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getProfile(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback);
}
/**
@@ -131,6 +131,7 @@ public class MemoryDSProcessor implements DataSourceProcessor {
* associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID).
* @param memoryImagePath Path to the memory image file.
+ * @param profile Volatility profile to run or empty string to autodetect
* @param pluginsToRun The Volatility plugins to run.
* @param timeZone The time zone to use when processing dates and
* times for the image, obtained from
@@ -139,8 +140,8 @@ public class MemoryDSProcessor implements DataSourceProcessor {
* processing.
* @param callback Callback to call when processing is done.
*/
- private void run(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
- addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, pluginsToRun, timeZone, progressMonitor, callback);
+ private void run(String deviceId, String memoryImagePath, String profile, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
+ addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, profile, pluginsToRun, timeZone, progressMonitor, callback);
new Thread(addImageTask).start();
}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java
index 50246f5255..9d3fcd15d5 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java
@@ -29,7 +29,6 @@ import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.openide.modules.InstalledFileLocator;
-import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@@ -41,13 +40,13 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
-import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
-import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.TskData.EncodingType;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
/**
@@ -70,6 +69,8 @@ class VolatilityProcessor {
private String moduleOutputPath;
private FileManager fileManager;
private volatile boolean isCancelled;
+ private Content outputVirtDir;
+ private String profile;
/**
* Constructs a processor that runs Volatility on a given memory image file
@@ -77,11 +78,13 @@ class VolatilityProcessor {
*
* @param memoryImagePath Path to memory image file.
* @param dataSource The memory image data source.
+ * @param profile Volatility profile to run or empty string to autodetect
* @param plugInToRuns Volatility plugins to run.
* @param progressMonitor Progress monitor for reporting progress during
* processing.
*/
- VolatilityProcessor(String memoryImagePath, Image dataSource, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) {
+ VolatilityProcessor(String memoryImagePath, Image dataSource, String profile, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) {
+ this.profile = profile;
this.memoryImagePath = memoryImagePath;
this.pluginsToRun = plugInToRun;
this.dataSource = dataSource;
@@ -117,6 +120,13 @@ class VolatilityProcessor {
fileManager = currentCase.getServices().getFileManager();
+ try {
+ // make a virtual directory to store the reports
+ outputVirtDir = currentCase.getSleuthkitCase().addVirtualDirectory(dataSource.getId(), "ModuleOutput");
+ } catch (TskCoreException ex) {
+ throw new VolatilityProcessorException("Error creating virtual directory", ex);
+ }
+
/*
* Make an output folder unique to this data source.
*/
@@ -124,9 +134,14 @@ class VolatilityProcessor {
moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), VOLATILITY, dataSourceId.toString()).toString();
File directory = new File(String.valueOf(moduleOutputPath));
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
runVolatilityPlugin("imageinfo"); //NON-NLS
+ profile = getProfileFromImageInfoOutput();
}
progressMonitor.setIndeterminate(false);
@@ -177,24 +192,38 @@ class VolatilityProcessor {
commandLine.add("\"" + executableFile + "\""); //NON-NLS
File memoryImage = new File(memoryImagePath);
commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS
-
- File imageInfoOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS
- if (imageInfoOutputFile.exists()) {
- String memoryProfile = parseImageInfoOutput(imageInfoOutputFile);
- commandLine.add("--profile=" + memoryProfile); //NON-NLS
+ if (!profile.isEmpty()) {
+ commandLine.add("--profile=" + profile); //NON-NLS
}
-
commandLine.add(pluginToRun);
- String outputFile = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS
+ switch (pluginToRun) {
+ case "dlldump":
+ case "moddump":
+ case "procdump":
+ case "dumpregistry":
+ case "dumpfiles":
+ String outputDir = moduleOutputPath + File.separator + pluginToRun;
+ File directory = new File(outputDir);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+ commandLine.add("--dump-dir=" + outputDir); //NON-NLS
+ break;
+ default:
+ break;
+ }
+
+ String outputFileAsString = moduleOutputPath + File.separator + pluginToRun + ".txt"; //NON-NLS
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
/*
* Add an environment variable to force Volatility to run with the same
* permissions Autopsy uses.
*/
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
- processBuilder.redirectOutput(new File(outputFile));
- processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); //NON-NLS
+ File outputFile = new File(outputFileAsString);
+ processBuilder.redirectOutput(outputFile);
+ processBuilder.redirectError(new File(moduleOutputPath + File.separator + "Volatility_err.txt")); //NON-NLS
processBuilder.directory(new File(memoryImage.getParent()));
try {
@@ -210,32 +239,16 @@ class VolatilityProcessor {
if (isCancelled) {
return;
}
-
- /*
- * Add the plugin output file to the case as a report.
- */
+
try {
- Report report = currentCase.getSleuthkitCase().addReport(outputFile, VOLATILITY, VOLATILITY + " " + pluginToRun + " Plugin"); //NON-NLS
- try {
- KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class);
- if (searchService != null) {
- searchService.index(report);
- } else {
- errorMsgs.add(Bundle.VolatilityProcessor_exceptionMessage_searchServiceNotFound(pluginToRun));
- /*
- * Log the exception as well as add it to the error
- * messages, to ensure that the stack trace is not lost.
- */
- logger.log(Level.WARNING, Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun));
- }
- } catch (TskCoreException ex) {
- throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun), ex);
- }
+ String relativePath = new File(currentCase.getCaseDirectory()).toURI().relativize(new File(outputFileAsString).toURI()).getPath();
+ fileManager.addDerivedFile(pluginToRun, relativePath, outputFile.length(), 0, 0, 0, 0, true, outputVirtDir, null, null, null, null, EncodingType.NONE);
} catch (TskCoreException ex) {
- throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorAddingOutput(pluginToRun), ex);
+ errorMsgs.add("Error adding " + pluginToRun + " volatility report as a file");
+ logger.log(Level.WARNING, "Error adding report as derived file", ex);
}
-
- createArtifactsFromPluginOutput(pluginToRun, new File(outputFile));
+
+ createArtifactsFromPluginOutput(pluginToRun, new File(outputFileAsString));
}
/**
@@ -264,12 +277,18 @@ class VolatilityProcessor {
@NbBundle.Messages({
"VolatilityProcessor_exceptionMessage_failedToParseImageInfo=Could not parse image info"
})
- private String parseImageInfoOutput(File imageOutputFile) throws VolatilityProcessorException {
+ private String getProfileFromImageInfoOutput() throws VolatilityProcessorException {
+ File imageOutputFile = new File(moduleOutputPath + File.separator + "imageinfo.txt"); //NON-NLS
try (BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) {
String fileRead = br.readLine();
- String[] profileLine = fileRead.split(":"); //NON-NLS
- String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS
- return memProfile[0].replaceAll("\\s+", ""); //NON-NLS
+ if (fileRead != null) {
+ String[] profileLine = fileRead.split(":"); //NON-NLS
+ String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS
+ return memProfile[0].replaceAll("\\s+", ""); //NON-NLS
+ }
+ else {
+ throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo());
+ }
} catch (IOException ex) {
throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo(), ex);
}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java
similarity index 93%
rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java
rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java
index 739fe787e8..b3a3ec463d 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java
@@ -23,7 +23,7 @@ import java.util.Map;
/**
* Stores data about a search before it is done.
*/
-final class QueryRequest {
+final class AdHocQueryRequest {
private final KeywordSearchQuery query;
private final String queryString;
@@ -36,7 +36,7 @@ final class QueryRequest {
* @param id ID that callers simply increment from 0
* @param query Query that will be performed.
*/
- QueryRequest(Map map, int id, KeywordSearchQuery query) {
+ AdHocQueryRequest(Map map, int id, KeywordSearchQuery query) {
this.queryString = query.getEscapedQueryString();
this.queryProperties = map;
this.query = query;
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java
similarity index 91%
rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java
rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java
index 290fe8b086..4424ead298 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java
@@ -48,7 +48,7 @@ import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
import org.sleuthkit.autopsy.datamodel.EmptyNode;
import org.sleuthkit.autopsy.datamodel.KeyValue;
import org.sleuthkit.autopsy.datamodel.KeyValueNode;
-import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.KeyValueQueryContent;
+import org.sleuthkit.autopsy.keywordsearch.AdHocSearchChildFactory.KeywordHitKey;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
@@ -66,9 +66,9 @@ import org.sleuthkit.datamodel.TskCoreException;
* Responsible for assembling nodes and columns in the right way and performing
* lazy queries as needed.
*/
-class KeywordSearchResultFactory extends ChildFactory {
+class AdHocSearchChildFactory extends ChildFactory {
- private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName());
+ private static final Logger logger = Logger.getLogger(AdHocSearchChildFactory.class.getName());
//common properties (superset of all Node properties) to be displayed as columns
static final List COMMON_PROPERTIES
@@ -82,9 +82,9 @@ class KeywordSearchResultFactory extends ChildFactory {
.map(Object::toString))
.collect(Collectors.toList());
- private final Collection queryRequests;
+ private final Collection queryRequests;
- KeywordSearchResultFactory(Collection queryRequests) {
+ AdHocSearchChildFactory(Collection queryRequests) {
this.queryRequests = queryRequests;
}
@@ -98,7 +98,7 @@ class KeywordSearchResultFactory extends ChildFactory {
@Override
protected boolean createKeys(List toPopulate) {
- for (QueryRequest queryRequest : queryRequests) {
+ for (AdHocQueryRequest queryRequest : queryRequests) {
/**
* Check the validity of the requested query.
*/
@@ -155,7 +155,7 @@ class KeywordSearchResultFactory extends ChildFactory {
}
int hitNumber = 0;
- List tempList = new ArrayList<>();
+ List tempList = new ArrayList<>();
for (KeywordHit hit : getOneHitPerObject(queryResults)) {
/**
@@ -203,7 +203,7 @@ class KeywordSearchResultFactory extends ChildFactory {
hitName = contentName;
}
hitNumber++;
- tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
+ tempList.add(new KeywordHitKey(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
}
@@ -253,8 +253,8 @@ class KeywordSearchResultFactory extends ChildFactory {
protected Node createNodeForKey(KeyValue key) {
Node resultNode;
- if (key instanceof KeyValueQueryContent) {
- AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key);
+ if (key instanceof KeywordHitKey) {
+ AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeywordHitKey) key);
/**
* Place the Content, Artifact and hit results into the lookup for
@@ -262,17 +262,17 @@ class KeywordSearchResultFactory extends ChildFactory {
*/
ArrayList