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); + } + }