19 package org.sleuthkit.autopsy.ingest;
21 import com.google.common.util.concurrent.ThreadFactoryBuilder;
22 import java.awt.EventQueue;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Date;
29 import java.util.EnumSet;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
35 import java.util.concurrent.Callable;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ExecutorService;
38 import java.util.concurrent.Executors;
39 import java.util.concurrent.Future;
40 import java.util.concurrent.atomic.AtomicLong;
41 import java.util.logging.Level;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44 import javax.annotation.concurrent.GuardedBy;
45 import javax.annotation.concurrent.Immutable;
46 import javax.annotation.concurrent.ThreadSafe;
47 import javax.swing.JOptionPane;
48 import org.netbeans.api.progress.ProgressHandle;
49 import org.openide.util.Cancellable;
50 import org.openide.util.NbBundle;
51 import org.openide.windows.WindowManager;
118 @GuardedBy(
"IngestManager.class")
122 private final ExecutorService
startIngestJobsExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-start-ingest-jobs-%d").build());
127 private final ExecutorService
eventPublishingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-ingest-events-%d").build());
146 if (null == instance) {
148 instance.subscribeToServiceMonitorEvents();
149 instance.subscribeToCaseEvents();
187 PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> {
202 logger.log(Level.SEVERE,
"Service {0} is down, cancelling all running ingest jobs", serviceDisplayName);
204 EventQueue.invokeLater(
new Runnable() {
207 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
208 NbBundle.getMessage(this.getClass(),
"IngestManager.cancellingIngest.msgDlg.text"),
209 NbBundle.getMessage(this.getClass(),
"IngestManager.serviceIsDown.msgDlg.text", serviceDisplayName),
210 JOptionPane.ERROR_MESSAGE);
224 Set<String> servicesList =
new HashSet<>();
236 if (event.getNewValue() != null) {
251 void handleCaseOpened() {
256 String channelPrefix = openedCase.
getName();
261 }
catch (NoCurrentCaseException | AutopsyEventException ex) {
262 logger.log(Level.SEVERE,
"Failed to open remote events channel", ex);
263 MessageNotifyUtil.Notify.error(NbBundle.getMessage(
IngestManager.class,
"IngestManager.OpenEventChannel.Fail.Title"),
264 NbBundle.getMessage(
IngestManager.class,
"IngestManager.OpenEventChannel.Fail.ErrMsg"));
276 void handleCaseClosed() {
307 if (job.hasIngestPipeline()) {
326 if (job.hasIngestPipeline()) {
346 if (job.hasIngestPipeline()) {
363 "IngestManager.startupErr.dlgTitle=Ingest Module Startup Failure",
364 "IngestManager.startupErr.dlgMsg=Unable to start up one or more ingest modules, ingest cancelled.",
365 "IngestManager.startupErr.dlgSolution=Please disable the failed modules or fix the errors before restarting ingest.",
366 "IngestManager.startupErr.dlgErrorList=Errors:"
369 List<IngestModuleError> errors = null;
380 EventQueue.invokeLater(
new Runnable() {
384 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
385 NbBundle.getMessage(this.getClass(),
"IngestManager.cancellingIngest.msgDlg.text"),
386 NbBundle.getMessage(this.getClass(),
"IngestManager.serviceIsDown.msgDlg.text", serviceDisplayName),
387 JOptionPane.ERROR_MESSAGE);
405 errors = job.start();
406 if (errors.isEmpty()) {
407 this.fireIngestJobStarted(job.
getId());
414 logger.log(Level.SEVERE, String.format(
"Error starting %s ingest module for job %d", error.getModuleDisplayName(), job.
getId()), error.getThrowable());
418 final StringBuilder message =
new StringBuilder(1024);
419 message.append(Bundle.IngestManager_startupErr_dlgMsg()).append(
"\n");
420 message.append(Bundle.IngestManager_startupErr_dlgSolution()).append(
"\n\n");
421 message.append(Bundle.IngestManager_startupErr_dlgErrorList()).append(
"\n");
423 String moduleName = error.getModuleDisplayName();
424 String errorMessage = error.getThrowable().getLocalizedMessage();
425 message.append(moduleName).append(
": ").append(errorMessage).append(
"\n");
427 message.append(
"\n\n");
428 EventQueue.invokeLater(() -> {
429 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), message, Bundle.IngestManager_startupErr_dlgTitle(), JOptionPane.ERROR_MESSAGE);
444 long jobId = job.
getId();
449 IngestManager.logger.log(Level.INFO,
"Ingest job {0} completed", jobId);
450 fireIngestJobCompleted(jobId);
452 IngestManager.logger.log(Level.INFO,
"Ingest job {0} cancelled", jobId);
453 fireIngestJobCancelled(jobId);
526 void fireIngestJobStarted(
long ingestJobId) {
536 void fireIngestJobCompleted(
long ingestJobId) {
537 AutopsyEvent
event =
new AutopsyEvent(IngestJobEvent.COMPLETED.toString(), ingestJobId, null);
546 void fireIngestJobCancelled(
long ingestJobId) {
547 AutopsyEvent
event =
new AutopsyEvent(IngestJobEvent.CANCELLED.toString(), ingestJobId, null);
559 void fireDataSourceAnalysisStarted(
long ingestJobId,
long dataSourceIngestJobId, Content dataSource) {
560 AutopsyEvent
event =
new DataSourceAnalysisStartedEvent(ingestJobId, dataSourceIngestJobId, dataSource);
572 void fireDataSourceAnalysisCompleted(
long ingestJobId,
long dataSourceIngestJobId, Content dataSource) {
573 AutopsyEvent
event =
new DataSourceAnalysisCompletedEvent(ingestJobId, dataSourceIngestJobId, dataSource, DataSourceAnalysisCompletedEvent.Reason.ANALYSIS_COMPLETED);
585 void fireDataSourceAnalysisCancelled(
long ingestJobId,
long dataSourceIngestJobId, Content dataSource) {
586 AutopsyEvent
event =
new DataSourceAnalysisCompletedEvent(ingestJobId, dataSourceIngestJobId, dataSource, DataSourceAnalysisCompletedEvent.Reason.ANALYSIS_CANCELLED);
596 void fireFileIngestDone(AbstractFile file) {
597 AutopsyEvent
event =
new FileAnalyzedEvent(file);
608 void fireIngestModuleDataEvent(ModuleDataEvent moduleDataEvent) {
609 AutopsyEvent
event =
new BlackboardPostEvent(moduleDataEvent);
620 void fireIngestModuleContentEvent(ModuleContentEvent moduleContentEvent) {
621 AutopsyEvent
event =
new ContentChangedEvent(moduleContentEvent);
630 void initIngestMessageInbox() {
641 void postIngestMessage(IngestMessage message) {
644 if (message.getMessageType() != IngestMessage.MessageType.ERROR && message.getMessageType() != IngestMessage.MessageType.WARNING) {
648 if (errorPosts <= MAX_ERROR_MESSAGE_POSTS) {
650 }
else if (errorPosts == MAX_ERROR_MESSAGE_POSTS + 1) {
651 IngestMessage errorMessageLimitReachedMessage = IngestMessage.createErrorMessage(
652 NbBundle.getMessage(
this.getClass(),
"IngestManager.IngestMessage.ErrorMessageLimitReached.title"),
653 NbBundle.getMessage(
this.getClass(),
"IngestManager.IngestMessage.ErrorMessageLimitReached.subject"),
654 NbBundle.getMessage(
this.getClass(),
"IngestManager.IngestMessage.ErrorMessageLimitReached.msg",
MAX_ERROR_MESSAGE_POSTS));
685 void setIngestTaskProgress(DataSourceIngestTask task, String ingestModuleDisplayName) {
686 ingestThreadActivitySnapshots.put(task.getThreadId(),
new IngestThreadActivitySnapshot(task.getThreadId(), task.getIngestJob().getId(), ingestModuleDisplayName, task.getDataSource()));
700 void setIngestTaskProgress(FileIngestTask task, String ingestModuleDisplayName) {
702 IngestThreadActivitySnapshot newSnap =
new IngestThreadActivitySnapshot(task.getThreadId(), task.getIngestJob().getId(), ingestModuleDisplayName, task.getDataSource(), task.getFile());
704 incrementModuleRunTime(prevSnap.getActivity(), newSnap.getStartTime().getTime() - prevSnap.getStartTime().getTime());
714 void setIngestTaskProgressCompleted(DataSourceIngestTask task) {
725 void setIngestTaskProgressCompleted(FileIngestTask task) {
727 IngestThreadActivitySnapshot newSnap =
new IngestThreadActivitySnapshot(task.getThreadId());
729 incrementModuleRunTime(prevSnap.getActivity(), newSnap.getStartTime().getTime() - prevSnap.getStartTime().getTime());
739 if (moduleDisplayName.equals(
"IDLE")) {
746 if (prevTimeL != null) {
747 prevTime = prevTimeL;
749 prevTime += duration;
759 Map<String, Long> getModuleRunTimes() {
772 List<IngestThreadActivitySnapshot> getIngestThreadActivitySnapshots() {
781 List<DataSourceIngestJob.Snapshot> getIngestJobSnapshots() {
782 List<DataSourceIngestJob.Snapshot> snapShots =
new ArrayList<>();
785 snapShots.addAll(job.getDataSourceIngestJobSnapshots());
797 long getFreeDiskSpace() {
822 if (Thread.currentThread().isInterrupted()) {
830 final String displayName = NbBundle.getMessage(this.getClass(),
"IngestManager.StartIngestJobsTask.run.displayName");
831 this.progress = ProgressHandle.createHandle(displayName,
new Cancellable() {
833 public boolean cancel() {
834 if (progress != null) {
835 progress.setDisplayName(NbBundle.getMessage(
this.getClass(),
"IngestManager.StartIngestJobsTask.run.cancelling", displayName));
849 if (null != progress) {
864 private final BlockingIngestTaskQueue
tasks;
875 IngestTask task = tasks.getNextTask();
876 task.execute(threadId);
877 }
catch (InterruptedException ex) {
880 if (Thread.currentThread().isInterrupted()) {
919 static final class IngestThreadActivitySnapshot {
921 private final long threadId;
922 private final Date startTime;
923 private final String activity;
924 private final String dataSourceName;
925 private final String fileName;
926 private final long jobId;
935 IngestThreadActivitySnapshot(
long threadId) {
936 this.threadId = threadId;
937 startTime =
new Date();
938 this.activity = NbBundle.getMessage(this.getClass(),
"IngestManager.IngestThreadActivitySnapshot.idleThread");
939 this.dataSourceName =
"";
954 IngestThreadActivitySnapshot(
long threadId,
long jobId, String activity, Content dataSource) {
955 this.threadId = threadId;
957 startTime =
new Date();
958 this.activity = activity;
959 this.dataSourceName = dataSource.getName();
975 IngestThreadActivitySnapshot(
long threadId,
long jobId, String activity, Content dataSource, AbstractFile file) {
976 this.threadId = threadId;
978 startTime =
new Date();
979 this.activity = activity;
980 this.dataSourceName = dataSource.getName();
981 this.fileName = file.getName();
989 long getIngestJobId() {
1007 Date getStartTime() {
1016 String getActivity() {
1027 String getDataSourceName() {
1028 return dataSourceName;
1036 String getFileName() {
1137 super(message, cause);
final ConcurrentHashMap< String, Long > ingestModuleRunTimes
final Map< Long, Future< Void > > startIngestJobFutures
String getServiceStatus(String service)
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static final String INGEST_MODULE_EVENT_CHANNEL_NAME
void queueIngestJob(Content dataSource, List< AbstractFile > files, IngestJobSettings settings)
IngestManagerException(String message, Throwable cause)
static synchronized IngestManager getInstance()
static IngestManager instance
IngestJobStartResult startIngestJob(IngestJob job)
final ExecutorService dataSourceLevelIngestJobTasksExecutor
static boolean runningWithGUI
void cancelAllIngestJobs()
void publish(AutopsyEvent event)
static void addPropertyChangeListener(final PropertyChangeListener listener)
static Case getOpenCase()
IngestJobStartResult beginIngestJob(Collection< Content > dataSources, IngestJobSettings settings)
static final Logger logger
final ExecutorService eventPublishingExecutor
void clearIngestMessageBox()
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void subscribeToServiceMonitorEvents()
boolean isIngestRunning()
DATA_SOURCE_ANALYSIS_COMPLETED
static void removePropertyChangeListener(final PropertyChangeListener listener)
static final Set< String > INGEST_MODULE_EVENT_NAMES
volatile IngestMessageTopComponent ingestMessageBox
void addSubscriber(PropertyChangeListener subscriber)
final AutopsyEventPublisher publisher
synchronized void closeRemoteEventChannel()
final ServicesMonitor servicesMonitor
void removeIngestJobEventListener(final PropertyChangeListener listener)
final BlockingIngestTaskQueue tasks
static final String INGEST_JOB_EVENT_CHANNEL_NAME
static final Set< String > INGEST_JOB_EVENT_NAMES
final AutopsyEventPublisher moduleEventPublisher
static int numberOfFileIngestThreads()
synchronized void openRemoteEventChannel(String channelName)
void addIngestJobEventListener(final PropertyChangeListener listener)
final Object ingestMessageBoxLock
IngestManagerException(String message)
void queueIngestJob(Collection< Content > dataSources, IngestJobSettings settings)
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static final int MAX_ERROR_MESSAGE_POSTS
final AtomicLong ingestErrorMessagePosts
volatile boolean caseIsOpen
final AtomicLong nextIngestManagerTaskId
int getNumberOfFileIngestThreads()
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
DATA_SOURCE_ANALYSIS_STARTED
void incrementModuleRunTime(String moduleDisplayName, Long duration)
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
synchronized IngestJob startIngestJob(Collection< Content > dataSources, IngestJobSettings settings)
final ConcurrentHashMap< Long, IngestThreadActivitySnapshot > ingestThreadActivitySnapshots
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
final IngestMonitor ingestMonitor
final ExecutorService fileLevelIngestJobTasksExecutor
static final long serialVersionUID
final Map< Long, IngestJob > ingestJobsById
void subscribeToCaseEvents()
final ExecutorService startIngestJobsExecutor
final int numberOfFileIngestThreads
final AutopsyEventPublisher jobEventPublisher