diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java
index 3a5a670f49..f365e8392e 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java
@@ -26,20 +26,22 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.contentviewers.analysisresults.AnalysisResultsViewModel.NodeResults;
import org.sleuthkit.autopsy.contentviewers.analysisresults.AnalysisResultsViewModel.ResultDisplayAttributes;
import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles;
import org.sleuthkit.autopsy.coreutils.EscapeUtil;
import org.sleuthkit.datamodel.AnalysisResult;
+import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Score;
/**
* Displays a list of analysis results in a panel.
*/
public class AnalysisResultsContentPanel extends javax.swing.JPanel {
-
+
private static final long serialVersionUID = 1L;
-
+
private static final String EMPTY_HTML = "
";
// Anchors are inserted into the navigation so that the viewer can navigate to a selection.
@@ -80,27 +82,18 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel {
*
* @param nodeResults The analysis results data to display.
*/
- @NbBundle.Messages("AnalysisResultsContentPanel_aggregateScore_displayKey=Aggregate Score")
void displayResults(NodeResults nodeResults) {
Document document = Jsoup.parse(EMPTY_HTML);
Element body = document.getElementsByTag("body").first();
- // if there is an aggregate score, append a section with the value
- Optional aggregateScore = nodeResults.getAggregateScore();
- if (aggregateScore.isPresent()) {
- appendSection(body,
- MessageFormat.format("{0}: {1}",
- Bundle.AnalysisResultsContentPanel_aggregateScore_displayKey(),
- aggregateScore.get().getSignificance().getDisplayName()),
- Optional.empty());
- }
+ Optional panelHeader = appendPanelHeader(body, nodeResults.getContent(), nodeResults.getAggregateScore());
// for each analysis result item, display the data.
List displayAttributes = nodeResults.getAnalysisResults();
for (int idx = 0; idx < displayAttributes.size(); idx++) {
AnalysisResultsViewModel.ResultDisplayAttributes resultAttrs = displayAttributes.get(idx);
Element sectionDiv = appendResult(body, idx, resultAttrs);
- if (idx > 0 || aggregateScore.isPresent()) {
+ if (idx > 0 || panelHeader.isPresent()) {
sectionDiv.attr("class", ContentViewerHtmlStyles.getSpacedSectionClassName());
}
}
@@ -119,6 +112,48 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel {
}
}
+ /**
+ * Appends the header to the panel.
+ *
+ * @param parent The parent html element.
+ * @param content The content whose name will be added if present.
+ * @param score The aggregate score whose significance will be added if
+ * present.
+ *
+ * @return The html element.
+ */
+ @Messages({
+ "AnalysisResultsContentPanel_aggregateScore_displayKey=Aggregate Score",
+ "AnalysisResultsContentPanel_content_displayKey=Item"
+ })
+ private Optional appendPanelHeader(Element parent, Optional content, Optional score) {
+ if (!content.isPresent() || !score.isPresent()) {
+ return Optional.empty();
+ }
+
+ Element container = parent.appendElement("div");
+
+ // if there is content append the name
+ content.ifPresent((c) -> {
+ container.appendElement("p")
+ .attr("class", ContentViewerHtmlStyles.getTextClassName())
+ .text(MessageFormat.format("{0}: {1}",
+ Bundle.AnalysisResultsContentPanel_content_displayKey(),
+ c.getName()));
+ });
+
+ // if there is an aggregate score, append the value
+ score.ifPresent((s) -> {
+ container.appendElement("p")
+ .attr("class", ContentViewerHtmlStyles.getTextClassName())
+ .text(MessageFormat.format("{0}: {1}",
+ Bundle.AnalysisResultsContentPanel_aggregateScore_displayKey(),
+ s.getSignificance().getDisplayName()));
+ });
+
+ return Optional.ofNullable(container);
+ }
+
/**
* Returns the anchor id to use with the analysis result (based on the id).
*
@@ -151,7 +186,7 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel {
// create a table
Element table = sectionDiv.appendElement("table");
table.attr("class", ContentViewerHtmlStyles.getIndentedClassName());
-
+
Element tableBody = table.appendElement("tbody");
// append a row for each item
@@ -160,7 +195,7 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel {
String keyString = keyVal.getKey() == null ? "" : keyVal.getKey() + ":";
Element keyTd = row.appendElement("td")
.attr("class", ContentViewerHtmlStyles.getTextClassName());
-
+
keyTd.appendElement("span")
.text(keyString)
.attr("class", ContentViewerHtmlStyles.getKeyColumnClassName());
@@ -170,7 +205,7 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel {
.text(valueString)
.attr("class", ContentViewerHtmlStyles.getTextClassName());
}
-
+
return sectionDiv;
}
@@ -199,7 +234,7 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel {
header = (anchorEl == null)
? sectionDiv.appendElement("h1")
: anchorEl.appendElement("h1");
-
+
header.text(headerText);
header.attr("class", ContentViewerHtmlStyles.getHeaderClassName());
header.attr("style", "display: inline-block");
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java
index 8ba7308eb4..00cc170b14 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java
@@ -94,6 +94,7 @@ public class AnalysisResultsViewModel {
private final List analysisResults;
private final Optional selectedResult;
private final Optional aggregateScore;
+ private final Optional content;
/**
* Constructor.
@@ -102,11 +103,13 @@ public class AnalysisResultsViewModel {
* @param selectedResult The selected analysis result or empty if none
* selected.
* @param aggregateScore The aggregate score or empty if no score.
+ * @param content The content associated with these results.
*/
- NodeResults(List analysisResults, Optional selectedResult, Optional aggregateScore) {
+ NodeResults(List analysisResults, Optional selectedResult, Optional aggregateScore, Optional content) {
this.analysisResults = analysisResults;
this.selectedResult = selectedResult;
this.aggregateScore = aggregateScore;
+ this.content = content;
}
/**
@@ -135,6 +138,17 @@ public class AnalysisResultsViewModel {
Optional getAggregateScore() {
return aggregateScore;
}
+
+ /**
+ * Returns the content associated with these results or empty if not
+ * present.
+ *
+ * @return The content associated with these results or empty if not
+ * present.
+ */
+ Optional getContent() {
+ return content;
+ }
}
/**
@@ -221,10 +235,11 @@ public class AnalysisResultsViewModel {
*/
NodeResults getAnalysisResults(Node node) {
if (node == null) {
- return new NodeResults(Collections.emptyList(), Optional.empty(), Optional.empty());
+ return new NodeResults(Collections.emptyList(), Optional.empty(), Optional.empty(), Optional.empty());
}
Optional aggregateScore = Optional.empty();
+ Optional nodeContent = Optional.empty();
// maps id of analysis result to analysis result to prevent duplication
Map allAnalysisResults = new HashMap<>();
Optional selectedResult = Optional.empty();
@@ -236,6 +251,8 @@ public class AnalysisResultsViewModel {
}
try {
+ nodeContent = Optional.of(content);
+
// get the aggregate score of that content
aggregateScore = Optional.ofNullable(content.getAggregateScore());
@@ -273,6 +290,6 @@ public class AnalysisResultsViewModel {
// get view model representation
List displayAttributes = getOrderedDisplayAttributes(allAnalysisResults.values());
- return new NodeResults(displayAttributes, selectedResult, aggregateScore);
+ return new NodeResults(displayAttributes, selectedResult, aggregateScore, nodeContent);
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java
index 981abe05de..7d197a4d11 100644
--- a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java
+++ b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2015-2019 Basis Technology Corp.
+ * Copyright 2015-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.keywordsearchservice;
+import com.google.common.annotations.Beta;
import java.io.Closeable;
import java.io.IOException;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
@@ -105,5 +106,14 @@ public interface KeywordSearchService extends Closeable {
* @throws KeywordSearchServiceException if unable to delete.
*/
void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException;
+
+ /**
+ * A flag to enable or disable OCR on all future text indexing.
+ *
+ * @param state Boolean flag to enable/disable OCR. Set to True to enable
+ * OCR, or False to disable it.
+ */
+ @Beta
+ void changeOcrState(boolean state);
}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java
index a881a96fb2..6680074a17 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java
@@ -157,6 +157,102 @@ final class AutoIngestAdminActions {
}
}
}
+
+ @NbBundle.Messages({"AutoIngestAdminActions.enableOCR.title=Enable OCR For This Case",
+ "AutoIngestAdminActions.enableOCR.error=Failed to enable OCR for case \"%s\"."})
+ static final class EnableOCR extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+ private final AutoIngestJob job;
+
+ EnableOCR(AutoIngestJob job) {
+ super(Bundle.AutoIngestAdminActions_enableOCR_title());
+ this.job = job;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+
+ if (job == null) {
+ return;
+ }
+
+ final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID);
+ if (tc == null) {
+ return;
+ }
+
+ AutoIngestDashboard dashboard = tc.getAutoIngestDashboard();
+ if (dashboard != null) {
+ dashboard.getPendingJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ EventQueue.invokeLater(() -> {
+ try {
+ dashboard.getMonitor().changeOcrStateForCase(job.getManifest().getCaseName(), true);
+ dashboard.getPendingJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshCaseEvent(dashboard.getMonitor(), job.getManifest().getCaseName()));
+ } catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
+ String errorMessage = String.format(Bundle.AutoIngestAdminActions_enableOCR_error(), job.getManifest().getCaseName());
+ 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.
+ }
+ }
+
+ @NbBundle.Messages({"AutoIngestAdminActions.disableOCR.title=Disable OCR For This Case",
+ "AutoIngestAdminActions.disableOCR.error=Failed to disable OCR for case \"%s\"."})
+ static final class DisableOCR extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+ private final AutoIngestJob job;
+
+ DisableOCR(AutoIngestJob job) {
+ super(Bundle.AutoIngestAdminActions_disableOCR_title());
+ this.job = job;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+
+ if (job == null) {
+ return;
+ }
+
+ final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID);
+ if (tc == null) {
+ return;
+ }
+
+ AutoIngestDashboard dashboard = tc.getAutoIngestDashboard();
+ if (dashboard != null) {
+ dashboard.getPendingJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ EventQueue.invokeLater(() -> {
+ try {
+ dashboard.getMonitor().changeOcrStateForCase(job.getManifest().getCaseName(), false);
+ dashboard.getPendingJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshCaseEvent(dashboard.getMonitor(), job.getManifest().getCaseName()));
+ } catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
+ String errorMessage = String.format(Bundle.AutoIngestAdminActions_disableOCR_error(), job.getManifest().getCaseName());
+ 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.
+ }
+ }
@NbBundle.Messages({"AutoIngestAdminActions.progressDialogAction.title=Ingest Progress"})
static final class ProgressDialogAction extends AbstractAction {
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java
index 0e25ca655f..656b98b278 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2015-2018 Basis Technology Corp.
+ * Copyright 2015-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -122,6 +122,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
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 OCR_COLUMN_PREFERRED_WIDTH = 50;
+ private static final int OCR_COLUMN_MAX_WIDTH = 150;
private static final int ACTIVITY_TIME_COL_MIN_WIDTH = 250;
private static final int ACTIVITY_TIME_COL_MAX_WIDTH = 450;
private static final int TIME_COL_MIN_WIDTH = 30;
@@ -133,9 +135,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
private static final int ACTIVITY_COL_MIN_WIDTH = 70;
private static final int ACTIVITY_COL_MAX_WIDTH = 2000;
private static final int ACTIVITY_COL_PREFERRED_WIDTH = 300;
- private static final int STATUS_COL_MIN_WIDTH = 55;
+ private static final int STATUS_COL_MIN_WIDTH = 50;
private static final int STATUS_COL_MAX_WIDTH = 250;
- private static final int STATUS_COL_PREFERRED_WIDTH = 55;
+ private static final int STATUS_COL_PREFERRED_WIDTH = 50;
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;
@@ -179,7 +181,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
"AutoIngestControlPanel.JobsTableModel.ColumnHeader.Status=Status",
"AutoIngestControlPanel.JobsTableModel.ColumnHeader.CaseFolder=Case Folder",
"AutoIngestControlPanel.JobsTableModel.ColumnHeader.LocalJob= Local Job?",
- "AutoIngestControlPanel.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path"
+ "AutoIngestControlPanel.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path",
+ "AutoIngestControlPanel.JobsTableModel.ColumnHeader.OCR=OCR"
})
private enum JobsTableModelColumns {
@@ -195,7 +198,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.CaseFolder")),
IS_LOCAL_JOB(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.LocalJob")),
MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.ManifestFilePath")),
- PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Priority"));
+ PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Priority")),
+ OCR(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.OCR"));
private final String header;
private JobsTableModelColumns(String header) {
@@ -219,7 +223,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
CASE_DIRECTORY_PATH.getColumnHeader(),
IS_LOCAL_JOB.getColumnHeader(),
MANIFEST_FILE_PATH.getColumnHeader(),
- PRIORITY.getColumnHeader()};
+ PRIORITY.getColumnHeader(),
+ OCR.getColumnHeader()};
}
/**
@@ -406,6 +411,12 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH);
column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH);
column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH);
+
+ column = pendingTable.getColumn(JobsTableModelColumns.OCR.getColumnHeader());
+ column.setCellRenderer(new OcrIconCellRenderer());
+ column.setMaxWidth(OCR_COLUMN_MAX_WIDTH);
+ column.setPreferredWidth(OCR_COLUMN_PREFERRED_WIDTH);
+ column.setWidth(OCR_COLUMN_PREFERRED_WIDTH);
/**
* Allow sorting when a column header is clicked.
@@ -457,6 +468,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.IS_LOCAL_JOB.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()));
+ runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.OCR.getColumnHeader()));
+
/*
* Set up a column to display the cases associated with the jobs.
*/
@@ -553,6 +566,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader()));
completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()));
+
/*
* Set up a column to display the cases associated with the jobs.
*/
@@ -603,6 +617,15 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
column.setMaxWidth(STATUS_COL_MAX_WIDTH);
column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH);
column.setWidth(STATUS_COL_PREFERRED_WIDTH);
+
+ /*
+ * Set up a column to display OCR enabled/disabled flag.
+ */
+ column = completedTable.getColumn(JobsTableModelColumns.OCR.getColumnHeader());
+ column.setCellRenderer(new OcrIconCellRenderer());
+ column.setMaxWidth(OCR_COLUMN_MAX_WIDTH);
+ column.setPreferredWidth(OCR_COLUMN_PREFERRED_WIDTH);
+ column.setWidth(OCR_COLUMN_PREFERRED_WIDTH);
/*
* Allow sorting when a column header is clicked.
@@ -856,6 +879,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
case JOB_COMPLETED:
case CASE_DELETED:
case REPROCESS_JOB:
+ case OCR_STATE_CHANGE:
updateExecutor.submit(new UpdateAllJobsTablesTask());
break;
case PAUSED_BY_USER_REQUEST:
@@ -1193,7 +1217,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH
job.getProcessingHostName().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB
job.getManifest().getFilePath(), // MANIFEST_FILE_PATH
- job.getPriority()}); // PRIORITY
+ job.getPriority(), // PRIORITY
+ job.getOcrEnabled()}); // OCR FLAG
}
} catch (Exception ex) {
sysLogger.log(Level.SEVERE, "Dashboard error refreshing table", ex);
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java
index 68487d0fcb..a1ce23b917 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2018 Basis Technology Corp.
+ * Copyright 2011-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -46,7 +46,7 @@ import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotProvider;
final class AutoIngestJob implements Comparable, IngestProgressSnapshotProvider, Serializable {
private static final long serialVersionUID = 1L;
- private static final int CURRENT_VERSION = 3;
+ private static final int CURRENT_VERSION = 4;
private static final int DEFAULT_PRIORITY = 0;
private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName();
@@ -100,6 +100,11 @@ final class AutoIngestJob implements Comparable, IngestProgressSn
private List ingestThreadsSnapshot;
private List ingestJobsSnapshot;
private Map moduleRunTimesSnapshot;
+
+ /*
+ * Version 4 fields.
+ */
+ private boolean ocrEnabled;
/**
* Constructs a new automated ingest job. All job state not specified in the
@@ -194,6 +199,11 @@ final class AutoIngestJob implements Comparable, IngestProgressSn
this.ingestJobsSnapshot = Collections.emptyList();
this.moduleRunTimesSnapshot = Collections.emptyMap();
+ /*
+ * Version 4 fields
+ */
+ this.ocrEnabled = nodeData.getOcrEnabled();
+
} catch (Exception ex) {
throw new AutoIngestJobException(String.format("Error creating automated ingest job"), ex);
}
@@ -253,6 +263,24 @@ final class AutoIngestJob implements Comparable, IngestProgressSn
synchronized Integer getPriority() {
return this.priority;
}
+
+ /**
+ * Gets the OCR flag for the job.
+ *
+ * @return Flag whether OCR is enabled/disabled.
+ */
+ synchronized boolean getOcrEnabled() {
+ return this.ocrEnabled;
+ }
+
+ /**
+ * Sets the OCR enabled/disabled flag for the job.
+ *
+ * @param enabled Flag whether OCR is enabled/disabled.
+ */
+ synchronized void setOcrEnabled(boolean enabled) {
+ this.ocrEnabled = enabled;
+ }
/**
* Sets the processing stage of the job. The start date/time for the stage
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java
index f367fdf553..5e9ab8955d 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2017 Basis Technology Corp.
+ * Copyright 2011-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,7 +31,7 @@ import javax.lang.model.type.TypeKind;
*/
final class AutoIngestJobNodeData {
- private static final int CURRENT_VERSION = 2;
+ private static final int CURRENT_VERSION = 3;
private static final int DEFAULT_PRIORITY = 0;
/*
@@ -47,7 +47,7 @@ final class AutoIngestJobNodeData {
* data. This avoids the need to continuously enlarge the buffer. Once the
* buffer has all the necessary data, it will be resized as appropriate.
*/
- private static final int MAX_POSSIBLE_NODE_DATA_SIZE = 131637;
+ private static final int MAX_POSSIBLE_NODE_DATA_SIZE = 131641;
/*
* Version 0 fields.
@@ -78,6 +78,11 @@ final class AutoIngestJobNodeData {
* Version 2 fields.
*/
private long dataSourceSize;
+
+ /*
+ * Version 3 fields.
+ */
+ private boolean ocrEnabled;
/**
* Gets the current version of the auto ingest job coordination service node
@@ -115,6 +120,7 @@ final class AutoIngestJobNodeData {
setProcessingStageStartDate(job.getProcessingStageStartDate());
setProcessingStageDetails(job.getProcessingStageDetails());
setDataSourceSize(job.getDataSourceSize());
+ setOcrEnabled(job.getOcrEnabled());
}
/**
@@ -128,7 +134,7 @@ final class AutoIngestJobNodeData {
if (null == nodeData || nodeData.length == 0) {
throw new InvalidDataException(null == nodeData ? "Null nodeData byte array" : "Zero-length nodeData byte array");
}
-
+
/*
* Set default values for all fields.
*/
@@ -150,6 +156,7 @@ final class AutoIngestJobNodeData {
this.processingStageDetailsDescription = "";
this.processingStageDetailsStartDate = 0L;
this.dataSourceSize = 0L;
+ this.ocrEnabled = false;
/*
* Get fields from node data.
@@ -192,6 +199,14 @@ final class AutoIngestJobNodeData {
*/
this.dataSourceSize = buffer.getLong();
}
+
+ if (buffer.hasRemaining()) {
+ /*
+ * Get version 3 fields.
+ */
+ int ocrFlag = buffer.getInt();
+ this.ocrEnabled = (1 == ocrFlag);
+ }
} catch (BufferUnderflowException ex) {
throw new InvalidDataException("Node data is incomplete", ex);
@@ -234,6 +249,24 @@ final class AutoIngestJobNodeData {
void setPriority(int priority) {
this.priority = priority;
}
+
+ /**
+ * Gets the OCR flag for the job.
+ *
+ * @return Flag whether OCR is enabled/disabled.
+ */
+ boolean getOcrEnabled() {
+ return this.ocrEnabled;
+ }
+
+ /**
+ * Sets the OCR enabled/disabled flag for the job.
+ *
+ * @param enabled Flag whether OCR is enabled/disabled.
+ */
+ void setOcrEnabled(boolean enabled) {
+ this.ocrEnabled = enabled;
+ }
/**
* Gets the number of times the job has crashed during processing.
@@ -567,6 +600,10 @@ final class AutoIngestJobNodeData {
if (this.version >= 2) {
buffer.putLong(this.dataSourceSize);
}
+
+ if (this.version >= 3) {
+ buffer.putInt(this.ocrEnabled ? 1 : 0);
+ }
}
// Prepare the array
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java
index 3142181117..415b3237b9 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java
@@ -57,7 +57,8 @@ final class AutoIngestJobsNode extends AbstractNode {
"AutoIngestJobsNode.jobCreated.text=Job Created",
"AutoIngestJobsNode.jobCompleted.text=Job Completed",
"AutoIngestJobsNode.priority.text=Prioritized",
- "AutoIngestJobsNode.status.text=Status"
+ "AutoIngestJobsNode.status.text=Status",
+ "AutoIngestJobsNode.ocr.text=OCR"
})
/**
@@ -98,12 +99,14 @@ final class AutoIngestJobsNode extends AbstractNode {
private final Stage jobStage;
private final List jobSnapshot;
private final Integer jobPriority;
+ private final Boolean ocrFlag;
AutoIngestJobWrapper(AutoIngestJob job) {
autoIngestJob = job;
jobStage = job.getProcessingStage();
jobSnapshot = job.getIngestJobSnapshots();
jobPriority = job.getPriority();
+ ocrFlag = job.getOcrEnabled();
}
AutoIngestJob getJob() {
@@ -123,11 +126,12 @@ final class AutoIngestJobsNode extends AbstractNode {
AutoIngestJob thisJob = this.autoIngestJob;
AutoIngestJob otherJob = ((AutoIngestJobWrapper) other).autoIngestJob;
- // Only equal if the manifest paths and processing stage details are the same.
+ // Only equal if the manifest paths, processing stage details, priority, and OCR flag are the same.
return thisJob.getManifest().getFilePath().equals(otherJob.getManifest().getFilePath())
&& jobStage.equals(((AutoIngestJobWrapper) other).jobStage)
&& jobSnapshot.equals(((AutoIngestJobWrapper) other).jobSnapshot)
- && jobPriority.equals(((AutoIngestJobWrapper) other).jobPriority);
+ && jobPriority.equals(((AutoIngestJobWrapper) other).jobPriority)
+ && ocrFlag.equals(((AutoIngestJobWrapper) other).ocrFlag);
}
@Override
@@ -137,6 +141,7 @@ final class AutoIngestJobsNode extends AbstractNode {
hash = 23 * hash + Objects.hashCode(this.jobStage);
hash = 23 * hash + Objects.hashCode(this.jobSnapshot);
hash = 23 * hash + Objects.hashCode(this.jobPriority);
+ hash = 23 * hash + Objects.hashCode(this.ocrFlag);
return hash;
}
@@ -171,6 +176,10 @@ final class AutoIngestJobsNode extends AbstractNode {
Integer getPriority() {
return autoIngestJob.getPriority();
}
+
+ boolean getOcrEnabled() {
+ return autoIngestJob.getOcrEnabled();
+ }
}
/**
@@ -327,6 +336,8 @@ final class AutoIngestJobsNode extends AbstractNode {
jobWrapper.getManifest().getDateFileCreated()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(),
jobWrapper.getPriority()));
+ ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text(),
+ jobWrapper.getOcrEnabled()));
break;
case RUNNING_JOB:
AutoIngestJob.StageDetails status = jobWrapper.getProcessingStageDetails();
@@ -344,6 +355,8 @@ final class AutoIngestJobsNode extends AbstractNode {
jobWrapper.getCompletedDate()));
ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(),
jobWrapper.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK));
+ ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text(),
+ jobWrapper.getOcrEnabled()));
break;
default:
}
@@ -364,6 +377,11 @@ final class AutoIngestJobsNode extends AbstractNode {
PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(jobWrapper.getJob());
deprioritizeCaseAction.setEnabled(jobWrapper.getPriority() > 0);
actions.add(deprioritizeCaseAction);
+
+ actions.add(new AutoIngestAdminActions.EnableOCR(jobWrapper.getJob()));
+ AutoIngestAdminActions.DisableOCR disableOCRAction = new AutoIngestAdminActions.DisableOCR(jobWrapper.getJob());
+ disableOCRAction.setEnabled(jobWrapper.getOcrEnabled() == true);
+ actions.add(disableOCRAction);
break;
case RUNNING_JOB:
actions.add(new AutoIngestAdminActions.ProgressDialogAction(jobWrapper.getJob()));
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java
index 6962057541..d335a35430 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2018 Basis Technology Corp.
+ * Copyright 2018-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -43,6 +43,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa
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_OCR_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;
@@ -81,12 +82,18 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa
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());
+ Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(),
+ Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text());
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_priority_text());
if (indexOfColumn != INVALID_INDEX) {
outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_PRIORITIZED_WIDTH);
outline.getColumnModel().getColumn(indexOfColumn).setCellRenderer(new PrioritizedIconCellRenderer());
}
+ indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_ocr_text());
+ if (indexOfColumn != INVALID_INDEX) {
+ outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_OCR_WIDTH);
+ outline.getColumnModel().getColumn(indexOfColumn).setCellRenderer(new OcrIconCellRenderer());
+ }
break;
case RUNNING_JOB:
outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(),
@@ -102,7 +109,8 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa
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());
+ Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(),
+ Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text());
indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_jobCompleted_text());
if (indexOfColumn != INVALID_INDEX) {
outline.setColumnSorted(indexOfColumn, false, 1);
@@ -112,6 +120,11 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa
outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_STATUS_WIDTH);
outline.getColumnModel().getColumn(indexOfColumn).setCellRenderer(new StatusIconCellRenderer());
}
+ indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_ocr_text());
+ if (indexOfColumn != INVALID_INDEX) {
+ outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_OCR_WIDTH);
+ outline.getColumnModel().getColumn(indexOfColumn).setCellRenderer(new OcrIconCellRenderer());
+ }
break;
default:
}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java
index 98ab0979a5..c7eb4111bf 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java
@@ -105,6 +105,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleError;
import org.sleuthkit.autopsy.ingest.IngestStream;
import org.sleuthkit.autopsy.keywordsearch.KeywordSearchModuleException;
import org.sleuthkit.autopsy.keywordsearch.Server;
+import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
@@ -144,7 +145,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
ControlEventType.SHUTDOWN.toString(),
ControlEventType.GENERATE_THREAD_DUMP_REQUEST.toString(),
Event.CANCEL_JOB.toString(),
- Event.REPROCESS_JOB.toString()}));
+ Event.REPROCESS_JOB.toString(),
+ Event.OCR_STATE_CHANGE.toString()}));
private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
private static final long JOB_STATUS_EVENT_INTERVAL_SECONDS = 10;
private static final String JOB_STATUS_PUBLISHING_THREAD_NAME = "AIM-job-status-event-publisher-%d";
@@ -308,6 +310,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
handleRemoteJobCancelEvent((AutoIngestJobCancelEvent) event);
} else if (event instanceof AutoIngestJobReprocessEvent) {
handleRemoteJobReprocessEvent((AutoIngestJobReprocessEvent) event);
+ } else if (event instanceof AutoIngestOcrStateChangeEvent) {
+ handleRemoteOcrEvent((AutoIngestOcrStateChangeEvent) event);
}
}
}
@@ -466,10 +470,42 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
String hostName = event.getNodeName();
hostNamesToLastMsgTime.put(hostName, Instant.now());
+ // currently the only way to the get latest ZK manifest node contents is to do an input directory scan
scanInputDirsNow();
setChanged();
notifyObservers(Event.CASE_PRIORITIZED);
}
+
+ /**
+ * Processes a case OCR enabled/disabled event from another node.
+ *
+ * @param event OCR enabled/disabled event from another auto ingest node.
+ */
+ private void handleRemoteOcrEvent(AutoIngestOcrStateChangeEvent event) {
+ switch (event.getEventType()) {
+ case OCR_ENABLED:
+ sysLogger.log(Level.INFO, "Received OCR enabled event for case {0} from user {1} on machine {2}",
+ new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()});
+ break;
+ case OCR_DISABLED:
+ sysLogger.log(Level.INFO, "Received OCR disabled event for case {0} from user {1} on machine {2}",
+ new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()});
+ break;
+ default:
+ sysLogger.log(Level.WARNING, "Received invalid OCR enabled/disabled event from user {0} on machine {1}",
+ new Object[]{event.getUserName(), event.getNodeName()});
+ break;
+ }
+
+ String hostName = event.getNodeName();
+ hostNamesToLastMsgTime.put(hostName, Instant.now());
+
+ // currently the only way to the get latest ZK manifest node contents is to do an input directory scan
+ scanInputDirsNow();
+
+ setChanged();
+ notifyObservers(Event.OCR_STATE_CHANGE);
+ }
/**
* Processes a case deletion event from another node by triggering an
@@ -2057,10 +2093,11 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
}
iterator.remove();
- currentJob = job;
+ // create a new job object based on latest ZK node data (i.e. instead of re-using potentially stale local pending AutoIngestJob object).
+ currentJob = new AutoIngestJob(nodeData);
break;
- } catch (AutoIngestJobNodeData.InvalidDataException ex) {
+ } catch (AutoIngestJobNodeData.InvalidDataException | AutoIngestJobException ex) {
sysLogger.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex);
}
}
@@ -2215,21 +2252,20 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
* auto ingest job.
*/
private void attemptJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, JobMetricsCollectionException {
- updateConfiguration();
- if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) {
- return;
- }
verifyRequiredSevicesAreRunning();
if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) {
return;
}
Case caseForJob = openCase();
try {
+ if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) {
+ return;
+ }
+ updateConfiguration();
if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) {
return;
}
runIngestForJob(caseForJob);
-
} finally {
try {
Case.closeCurrentCase();
@@ -2242,7 +2278,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
/**
* Updates the ingest system settings by downloading the latest version
- * of the settings if using shared configuration.
+ * of the settings if using shared configuration. Also updates the OCR
+ * setting.
*
* @throws SharedConfigurationException if there is an error downloading
* shared configuration.
@@ -2258,6 +2295,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
currentJob.setProcessingStage(AutoIngestJob.Stage.UPDATING_SHARED_CONFIG, Date.from(Instant.now()));
new SharedConfiguration().downloadConfiguration();
}
+
+ // update the OCR enabled/disabled setting
+ if (currentJob.getOcrEnabled()) {
+ sysLogger.log(Level.INFO, "Enabling OCR for job {0}", currentJob.getManifest().getFilePath());
+ } else {
+ sysLogger.log(Level.INFO, "Disabling OCR for job {0}", currentJob.getManifest().getFilePath());
+ }
+ KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class);
+ kwsService.changeOcrState(currentJob.getOcrEnabled());
}
/**
@@ -3151,7 +3197,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
REPORT_STATE,
CANCEL_JOB,
REPROCESS_JOB,
- GENERATE_THREAD_DUMP_RESPONSE
+ GENERATE_THREAD_DUMP_RESPONSE,
+ OCR_STATE_CHANGE
}
/**
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java
index fbca3f50ee..0022cf6609 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java
@@ -84,7 +84,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
AutoIngestManager.Event.SHUTTING_DOWN.toString(),
AutoIngestManager.Event.SHUTDOWN.toString(),
AutoIngestManager.Event.RESUMED.toString(),
- AutoIngestManager.Event.GENERATE_THREAD_DUMP_RESPONSE.toString()}));
+ AutoIngestManager.Event.GENERATE_THREAD_DUMP_RESPONSE.toString(),
+ AutoIngestManager.Event.OCR_STATE_CHANGE.toString()}));
private final AutopsyEventPublisher eventPublisher;
private CoordinationService coordinationService;
private final ScheduledThreadPoolExecutor coordSvcQueryExecutor;
@@ -166,6 +167,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
handleAutoIngestNodeStateEvent((AutoIngestNodeStateEvent) event);
} else if (event instanceof ThreadDumpResponseEvent) {
handleRemoteThreadDumpResponseEvent((ThreadDumpResponseEvent) event);
+ } else if (event instanceof AutoIngestOcrStateChangeEvent) {
+ handleOcrStateChangeEvent((AutoIngestOcrStateChangeEvent) event);
}
}
@@ -228,6 +231,15 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
}
}
+ /**
+ * Handles an OCR state change event.
+ *
+ * @param event OCR state change event.
+ */
+ private void handleOcrStateChangeEvent(AutoIngestOcrStateChangeEvent event) {
+ coordSvcQueryExecutor.submit(new StateRefreshTask());
+ }
+
/**
* Handles an auto ingest job/case prioritization event.
*
@@ -427,6 +439,51 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
return new JobsSnapshot();
}
}
+
+ /**
+ * Enables OCR for all pending ingest jobs for a specified case.
+ *
+ * @param caseName The name of the case to enable OCR.
+ *
+ * @throws AutoIngestMonitorException If there is an error enabling OCR for the jobs for the case.
+ *
+ */
+ void changeOcrStateForCase(final String caseName, final boolean ocrState) throws AutoIngestMonitorException {
+ List jobsToPrioritize = new ArrayList<>();
+ synchronized (jobsLock) {
+ for (AutoIngestJob pendingJob : getPendingJobs()) {
+ if (pendingJob.getManifest().getCaseName().equals(caseName)) {
+ jobsToPrioritize.add(pendingJob);
+ }
+ }
+ if (!jobsToPrioritize.isEmpty()) {
+ for (AutoIngestJob job : jobsToPrioritize) {
+ String manifestNodePath = job.getManifest().getFilePath().toString();
+ try {
+ AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath));
+ nodeData.setOcrEnabled(ocrState);
+ coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray());
+ } catch (AutoIngestJobNodeData.InvalidDataException | CoordinationServiceException | InterruptedException ex) {
+ throw new AutoIngestMonitorException("Error enabling OCR for job " + job.toString(), ex);
+ }
+ job.setOcrEnabled(ocrState);
+
+ /**
+ * Update job object in pending jobs queue
+ */
+ jobsSnapshot.addOrReplacePendingJob(job);
+ }
+
+ /*
+ * Publish the OCR enabled event.
+ */
+ new Thread(() -> {
+ eventPublisher.publishRemotely(new AutoIngestOcrStateChangeEvent(LOCAL_HOST_NAME, caseName,
+ AutoIngestManager.getSystemUserNameProperty(), ocrState));
+ }).start();
+ }
+ }
+ }
/**
* Removes the priority (set to zero) of all pending ingest jobs for a
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java
index 338bce31c7..be7cc2408f 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java
@@ -69,7 +69,7 @@ class AutoIngestNodeRefreshEvents {
private final String caseName;
/**
- * Contructs a RefreshCaseEvent
+ * Constructs a RefreshCaseEvent
*
* @param monitor The monitor that will provide access to the current state of the jobs lists.
* @param name The name of the case whose nodes should be refreshed.
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestOcrStateChangeEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestOcrStateChangeEvent.java
new file mode 100755
index 0000000000..682ad711eb
--- /dev/null
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestOcrStateChangeEvent.java
@@ -0,0 +1,99 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 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.Serializable;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+
+/**
+ * Event published when an automated ingest manager enables or disables OCR on a case.
+ */
+public final class AutoIngestOcrStateChangeEvent extends AutopsyEvent implements Serializable {
+
+ /**
+ * Possible event types
+ */
+ enum EventType {
+ OCR_ENABLED,
+ OCR_DISABLED
+ }
+
+ private static final long serialVersionUID = 1L;
+ private final String caseName;
+ private final String nodeName;
+ private final String userName;
+ private final EventType eventType;
+
+ /**
+ * Constructs an event published when an automated ingest manager
+ * enables or disables OCR on a case.
+ *
+ * @param caseName The name of the case.
+ * @param nodeName The host name of the node that enabled/disabled OCR.
+ * @param userName The logged in user
+ * @param ocrState Flag whether OCR is enabled/disabled
+ */
+ public AutoIngestOcrStateChangeEvent(String nodeName, String caseName, String userName, boolean ocrState) {
+ super(AutoIngestManager.Event.OCR_STATE_CHANGE.toString(), null, null);
+ this.caseName = caseName;
+ this.nodeName = nodeName;
+ this.userName = userName;
+ if (ocrState == true) {
+ this.eventType = EventType.OCR_ENABLED;
+ } else {
+ this.eventType = EventType.OCR_DISABLED;
+ }
+ }
+
+ /**
+ * Gets the name of the prioritized case.
+ *
+ * @return The case name.
+ */
+ public String getCaseName() {
+ return caseName;
+ }
+
+ /**
+ * Gets the host name of the node that prioritized the case.
+ *
+ * @return The host name of the node.
+ */
+ public String getNodeName() {
+ return nodeName;
+ }
+
+ /**
+ * Gets the user logged in to the node that prioritized the case.
+ *
+ * @return The user name
+ */
+ String getUserName() {
+ return userName;
+ }
+
+ /**
+ * Gets the type of prioritization
+ *
+ * @return The type
+ */
+ EventType getEventType() {
+ return eventType;
+ }
+}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED
index 4447162072..2a1e361537 100755
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED
@@ -10,6 +10,10 @@ AinStatusNode.status.title=Status
AinStatusNode.status.unknown=Unknown
AutoIngestAdminActions.cancelJobAction.title=Cancel Job
AutoIngestAdminActions.cancelModuleAction.title=Cancel Module
+AutoIngestAdminActions.disableOCR.error=Failed to disable OCR for case "%s".
+AutoIngestAdminActions.disableOCR.title=Disable OCR For This Case
+AutoIngestAdminActions.enableOCR.error=Failed to enable OCR for case "%s".
+AutoIngestAdminActions.enableOCR.title=Enable OCR For This Case
AutoIngestAdminActions.getThreadDump.title=Generate Thread Dump
AutoIngestAdminActions.pause.title=Pause Node
AutoIngestAdminActions.progressDialogAction.title=Ingest Progress
@@ -71,6 +75,7 @@ AutoIngestControlPanel.JobsTableModel.ColumnHeader.HostName=Host Name
AutoIngestControlPanel.JobsTableModel.ColumnHeader.ImageFolder=Data Source
AutoIngestControlPanel.JobsTableModel.ColumnHeader.LocalJob=\ Local Job?
AutoIngestControlPanel.JobsTableModel.ColumnHeader.ManifestFilePath=\ Manifest File Path
+AutoIngestControlPanel.JobsTableModel.ColumnHeader.OCR=OCR
AutoIngestControlPanel.JobsTableModel.ColumnHeader.Priority=Prioritized
AutoIngestControlPanel.JobsTableModel.ColumnHeader.Stage=Stage
AutoIngestControlPanel.JobsTableModel.ColumnHeader.StageTime=Time in Stage
@@ -130,6 +135,7 @@ AutoIngestJobsNode.dataSource.text=Data Source
AutoIngestJobsNode.hostName.text=Host Name
AutoIngestJobsNode.jobCompleted.text=Job Completed
AutoIngestJobsNode.jobCreated.text=Job Created
+AutoIngestJobsNode.ocr.text=OCR
AutoIngestJobsNode.prioritized.false=No
AutoIngestJobsNode.prioritized.true=Yes
AutoIngestJobsNode.priority.text=Prioritized
@@ -206,9 +212,7 @@ DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}...
DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}...
DeleteCaseTask.progress.startMessage=Starting deletion...
DeleteOrphanCaseNodesAction.progressDisplayName=Cleanup Case Znodes
-# {0} - item count
DeleteOrphanCaseNodesDialog.additionalInit.lblNodeCount.text=Znodes found: {0}
-# {0} - item count
DeleteOrphanCaseNodesDialog.additionalInit.znodesTextArea.countMessage=ZNODES FOUND: {0}
DeleteOrphanCaseNodesTask.progress.connectingToCoordSvc=Connecting to the coordination service
# {0} - node path
@@ -224,6 +228,8 @@ DeleteOrphanManifestNodesTask.progress.gettingManifestNodes=Querying the coordin
DeleteOrphanManifestNodesTask.progress.lookingForOrphanedManifestFileZnodes=Looking for orphaned manifest file znodes
DeleteOrphanManifestNodesTask.progress.startMessage=Starting orphaned manifest file znode cleanup
HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases
+OcrIconCellRenderer.disabled.tooltiptext=This job does not have OCR enabled.
+OcrIconCellRenderer.enabled.tooltiptext=This job has OCR enabled.
OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.
OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.
OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File
@@ -333,7 +339,7 @@ PrioritizationAction.deprioritizeCaseAction.error=Failed to deprioritize case "%
PrioritizationAction.deprioritizeCaseAction.title=Deprioritize Case
PrioritizationAction.deprioritizeJobAction.error=Failed to deprioritize job "%s".
PrioritizationAction.deprioritizeJobAction.title=Deprioritize Job
-PrioritizationAction.prioritizeCaseAction.error==Failed to prioritize case "%s".
+PrioritizationAction.prioritizeCaseAction.error=Failed to prioritize case "%s".
PrioritizationAction.prioritizeCaseAction.title=Prioritize Case
PrioritizationAction.prioritizeJobAction.error=Failed to prioritize job "%s".
PrioritizationAction.prioritizeJobAction.title=Prioritize Job
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/OcrIconCellRenderer.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/OcrIconCellRenderer.java
new file mode 100755
index 0000000000..e90754adf8
--- /dev/null
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/OcrIconCellRenderer.java
@@ -0,0 +1,72 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 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.Component;
+import java.lang.reflect.InvocationTargetException;
+import javax.swing.ImageIcon;
+import javax.swing.JTable;
+import static javax.swing.SwingConstants.CENTER;
+import org.openide.nodes.Node;
+import org.openide.util.ImageUtilities;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.guiutils.GrayableCellRenderer;
+import org.sleuthkit.autopsy.datamodel.NodeProperty;
+
+/**
+ * A JTable and Outline view cell renderer that represents whether OCR is enabled for the job.
+ */
+class OcrIconCellRenderer extends GrayableCellRenderer {
+
+ @Messages({
+ "OcrIconCellRenderer.enabled.tooltiptext=This job has OCR enabled.",
+ "OcrIconCellRenderer.disabled.tooltiptext=This job does not have OCR enabled."
+ })
+ private static final long serialVersionUID = 1L;
+ static final ImageIcon checkedIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/experimental/images/tick.png", false));
+
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+ setHorizontalAlignment(CENTER);
+ Object switchValue = null;
+ if ((value instanceof NodeProperty)) {
+ //The Outline view has properties in the cell, the value contained in the property is what we want
+ try {
+ switchValue = ((Node.Property) value).getValue();
+ } catch (IllegalAccessException | InvocationTargetException ignored) {
+ //Unable to get the value from the NodeProperty no Icon will be displayed
+ }
+ } else {
+ //JTables contain the value we want directly in the cell
+ switchValue = value;
+ }
+ if (switchValue instanceof Boolean && (boolean) switchValue == true) {
+ setIcon(checkedIcon);
+ setToolTipText(org.openide.util.NbBundle.getMessage(OcrIconCellRenderer.class, "OcrIconCellRenderer.enabled.tooltiptext"));
+ } else {
+ setIcon(null);
+ if (switchValue instanceof Boolean) {
+ setToolTipText(org.openide.util.NbBundle.getMessage(OcrIconCellRenderer.class, "OcrIconCellRenderer.disabled.tooltiptext"));
+ }
+ }
+ grayCellIfTableNotEnabled(table, isSelected);
+
+ return this;
+ }
+}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java
index 006dc3e580..97ad2cee63 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java
@@ -193,7 +193,7 @@ abstract class PrioritizationAction extends AbstractAction {
* AutoIngestJob is a part of.
*/
@Messages({"PrioritizationAction.prioritizeCaseAction.title=Prioritize Case",
- "PrioritizationAction.prioritizeCaseAction.error==Failed to prioritize case \"%s\"."})
+ "PrioritizationAction.prioritizeCaseAction.error=Failed to prioritize case \"%s\"."})
static final class PrioritizeCaseAction extends PrioritizationAction {
private static final long serialVersionUID = 1L;
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
index 218be24de8..09460a0788 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
@@ -462,4 +462,17 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
}
}
+ /**
+ * A flag to enable or disable OCR on all future text indexing. Also sets the
+ * the "Limited OCR" functionality accordingly.
+ *
+ * @param state Boolean flag to enable/disable OCR. Set to True to enable
+ * OCR, or False to disable it.
+ */
+ @Override
+ public void changeOcrState(boolean state) {
+ KeywordSearchSettings.setOcrOption(state);
+ KeywordSearchSettings.setLimitedOcrOption(state);
+ }
+
}