diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAlertFile.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAlertFile.java index b1f6b1277c..40fff351eb 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAlertFile.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAlertFile.java @@ -28,8 +28,6 @@ import java.util.logging.Level; * Utility for creating and checking for the existence of an automated ingest * alert file. The purpose of the file is to put a marker in the case directory * when an error or warning occurs in connection with an automated ingest job. - * If there is an error creating an alert file, it is logged to the auto ingest - * system log. */ final class AutoIngestAlertFile { @@ -54,7 +52,7 @@ final class AutoIngestAlertFile { * * @return True or false. */ - static void create(Path caseDirectoryPath) { + static void create(Path caseDirectoryPath) throws AutoIngestAlertFileException { try { Files.createFile(caseDirectoryPath.resolve(ERROR_FILE_NAME)); } catch (FileAlreadyExistsException ignored) { @@ -67,15 +65,44 @@ final class AutoIngestAlertFile { * for that case. */ if (!exists(caseDirectoryPath)) { - AutoIngestSystemLogger.getLogger().log(Level.SEVERE, String.format("Error creating automated ingest alert file in %s", caseDirectoryPath), ex); + throw new AutoIngestAlertFileException(String.format("Error creating automated ingest alert file in %s", caseDirectoryPath), ex); } } } + /** + * Exception thrown when there is a problem creating an alert file. + */ + final static class AutoIngestAlertFileException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an exception to throw when there is a problem creating an + * alert file. + * + * @param message The exception message. + */ + private AutoIngestAlertFileException(String message) { + super(message); + } + + /** + * Constructs an exception to throw when there is a problem creating an + * alert file. + * + * @param message The exception message. + * @param cause The cause of the exception, if it was an exception. + */ + private AutoIngestAlertFileException(String message, Throwable cause) { + super(message, cause); + } + } + /** * Prevents instantiation of this utility class. */ private AutoIngestAlertFile() { } - + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java index 9996827e9d..fd56d60a0b 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java @@ -24,23 +24,23 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; /** * Event published when a case is deleted by the automated ingest manager. */ -public final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializable { +@Immutable +final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializable { private static final long serialVersionUID = 1L; - private final AutoIngestManager.CaseDeletionResult result; + private final String caseName; private final String nodeName; /** * Constructs an event that is published when a case is deleted by the * automated ingest manager. * - * @param result The deletion result // RJCTODO: Get rid of logical - * deletion + * @param caseName The case name. * @param nodeName The host name of the node that deleted the case. */ - public AutoIngestCaseDeletedEvent(AutoIngestManager.CaseDeletionResult result, String nodeName) { + AutoIngestCaseDeletedEvent(String caseName, String nodeName) { super(AutoIngestManager.Event.CASE_DELETED.toString(), null, null); - this.result = result; + this.caseName = caseName; this.nodeName = nodeName; } @@ -49,8 +49,8 @@ public final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Se * * @return */ - public String getNodeName() { - return nodeName; + String getCaseName() { + return caseName; } /** @@ -58,8 +58,8 @@ public final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Se * * @return */ - public AutoIngestManager.CaseDeletionResult getResult() { - return result; + String getNodeName() { + return nodeName; } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java index 5fbf380601..0687bc9c2e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java @@ -25,7 +25,7 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; * Event published when an automated ingest manager prioritizes all or part of a * case. */ -public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { +public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { // RJCTODO: Rename to AutoIngestPrioritizationEvent private static final long serialVersionUID = 1L; private final String caseName; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 52c6e00aa4..ccb3d2d156 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -19,67 +19,62 @@ + + + + + + + + + + + - - - - - + + + + + + + + + + + - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - + + @@ -106,7 +101,7 @@ - + @@ -127,9 +122,11 @@ - + + + - + @@ -461,5 +458,15 @@ + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index d876fdc536..6de9713120 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -54,10 +54,13 @@ import org.openide.LifecycleManager; import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.core.ServicesMonitor; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; +import org.sleuthkit.autopsy.experimental.AutoIngestManager.CaseDeletionResult; +import org.sleuthkit.autopsy.experimental.AutoIngestManager.JobsSnapshot; import org.sleuthkit.autopsy.experimental.configuration.OptionsDialog; /** @@ -91,7 +94,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280; private static final String UPDATE_TASKS_THREAD_NAME = "AID-update-tasks-%d"; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); - private static final Logger LOGGER = AutoIngestSystemLogger.getLogger(); + private static final Logger SYS_LOGGER = AutoIngestSystemLogger.getLogger(); private static AutoIngestDashboard instance; private final DefaultTableModel pendingTableModel; private final DefaultTableModel runningTableModel; @@ -121,7 +124,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { STAGE_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime")), STATUS(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Status")), CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder")), - IS_LOCAL_JOB(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.LocalJob")); + IS_LOCAL_JOB(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.LocalJob")), + MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath")); private final String header; @@ -144,7 +148,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { STATUS.getColumnHeader(), STAGE_TIME.getColumnHeader(), CASE_DIRECTORY_PATH.getColumnHeader(), - IS_LOCAL_JOB.getColumnHeader()}; + IS_LOCAL_JOB.getColumnHeader(), + MANIFEST_FILE_PATH.getColumnHeader()}; } /** @@ -232,6 +237,29 @@ public final class AutoIngestDashboard extends JPanel implements Observer { return null; } + /** + * Gets a status string for a given service. + * + * @param service The service to test. + * + * @return The status string. + */ + private String getServiceStatus(ServicesMonitor.Service service) { + String serviceStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Unknown"); + try { + ServicesMonitor servicesMonitor = ServicesMonitor.getInstance(); + serviceStatus = servicesMonitor.getServiceStatus(service.toString()); + if (serviceStatus.compareTo(ServicesMonitor.ServiceStatus.UP.toString()) == 0) { + serviceStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); + } else { + serviceStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"); + } + } catch (ServicesMonitor.ServicesMonitorException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Dashboard error getting service status for %s", service), ex); + } + return serviceStatus; + } + @Override protected void done() { tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", caseDatabaseServerStatus, keywordSearchServiceStatus, keywordSearchServiceStatus, messagingStatus)); @@ -249,30 +277,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { } /** - * Gets a status string for a given service. - * - * @param service The service to test. - * - * @return up, down, or unknown - */ - private String getServiceStatus(ServicesMonitor.Service service) { - String serviceStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Unknown"); - try { - ServicesMonitor servicesMonitor = ServicesMonitor.getInstance(); - serviceStatus = servicesMonitor.getServiceStatus(service.toString()); - if (serviceStatus.compareTo(ServicesMonitor.ServiceStatus.UP.toString()) == 0) { - serviceStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); - } else { - serviceStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"); - } - } catch (ServicesMonitor.ServicesMonitorException ex) { - LOGGER.log(Level.SEVERE, String.format("Dashboard error getting service status for %s", service), ex); - } - return serviceStatus; - } - - /** - * Sets up the JTable that presents a view of the + * Sets up the JTable that presents a view of the system-wide pending jobs + * queue. */ private void initPendingJobsTable() { /* @@ -287,6 +293,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.IS_LOCAL_JOB.getColumnHeader())); pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); + pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); /* * Set up a column to display the cases associated with the jobs. @@ -344,7 +351,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { } /** - * Sets up the header and columns for the running auto ingest jobs table. + * Sets up the JTable that presents a view of the system-wide running jobs + * list. */ private void initRunningJobsTable() { /* @@ -357,6 +365,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.IS_LOCAL_JOB.getColumnHeader())); + runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); /* * Set up a column to display the cases associated with the jobs. @@ -438,8 +447,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { } /** - * Sets up the header, columns, and selection listener for the completed - * auto ingest jobs table. + * Sets up the JTable that presents a view of the system-wide competed jobs + * list. */ private void initCompletedJobsTable() { /* @@ -452,6 +461,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.IS_LOCAL_JOB.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); + completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); /* * Set up a column to display the cases associated with the jobs. @@ -558,11 +568,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { * @param enable Enable/disable the buttons. */ private void enablePendingTableButtons(Boolean enable) { - // RJCTODO: Restore prioritization feature -// bnPrioritizeCase.setEnabled(enable); -// bnPrioritizeFolder.setEnabled(enable); - bnPrioritizeCase.setEnabled(false); - bnPrioritizeFolder.setEnabled(false); + bnPrioritizeCase.setEnabled(enable); + bnPrioritizeJob.setEnabled(enable); } /** @@ -573,12 +580,12 @@ public final class AutoIngestDashboard extends JPanel implements Observer { private void startUp() { /* - * Start up the auto ingest manager (AIM). + * Starts up the auto ingest manager (AIM). */ try { manager.startUp(); } catch (AutoIngestManager.AutoIngestManagerStartupException ex) { - LOGGER.log(Level.SEVERE, "Dashboard error starting up auto ingest", ex); + SYS_LOGGER.log(Level.SEVERE, "Dashboard error starting up auto ingest", ex); tbStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.AutoIngestStartupError")); manager = null; @@ -594,20 +601,6 @@ public final class AutoIngestDashboard extends JPanel implements Observer { return; } - /* - * Attempt to connect the AIM to any other auto ingest nodes (AINs) if - * this is a cluster. - */ - try { - manager.establishRemoteCommunications(); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Dashboard error establishing remote communications for auto ingest", ex); - JOptionPane.showMessageDialog(this, - NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.AutoIngestStartupWarning.Message"), - NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.AutoIngestStartupWarning.Title"), - JOptionPane.WARNING_MESSAGE); - } - /* * Subscribe to services monitor events. */ @@ -621,18 +614,10 @@ public final class AutoIngestDashboard extends JPanel implements Observer { manager.addObserver(this); /* - * Populate the pending, running, and completed auto ingest tables. + * Populate the pending, running, and completed auto ingest job tables. */ updateExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(UPDATE_TASKS_THREAD_NAME).build()); updateExecutor.submit(new UpdateAllJobsTablesTask()); - - /* - * TODO (RC): This does not seem right, given that the AIM does its - * first image folder scan on start up. I think it could actually delay - * the population of the auto ingest job tables, since the table refresh - * and the scan both acquire the job lists manager. Does the AIM send an - * event after a scan? Need to check this. - */ manager.scanInputDirsNow(); bnPause.setEnabled(true); @@ -743,7 +728,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { isPaused = true; }); break; - case PAUSED_FOR_SYSTEM_ERROR: // RJCTODO: Consider making this more detailed again, probably not, too much maintenance and is iverkill + case PAUSED_FOR_SYSTEM_ERROR: EventQueue.invokeLater(() -> { tbStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.PauseDueToSystemError")); bnOptions.setEnabled(true); @@ -777,9 +762,9 @@ public final class AutoIngestDashboard extends JPanel implements Observer { * running, and that the auto ingest manager will not actually pause until * the current auto ingest job completes. * - * @param buttonClicked Is this pause requests in response to a user gesture + * @param buttonClicked Is this pause request in response to a user gesture * or a nofification from the auto ingest manager - * (AMI)? + * (AIM)? */ private void pause(boolean buttonClicked) { /** @@ -1058,15 +1043,16 @@ public final class AutoIngestDashboard extends JPanel implements Observer { job.getNodeName(), // HOST_NAME job.getManifest().getDateFileCreated(), // CREATED_TIME job.getStageStartDate(), // STARTED_TIME - job.getStageStartDate(), // COMPLETED_TIME + job.getCompletedDate(), // COMPLETED_TIME status.getDescription(), // ACTIVITY - (null != job.getCaseDirectoryPath()) ? AutoIngestAlertFile.exists(job.getCaseDirectoryPath()) : false, // STATUS // RJCTODO: awkward? + job.hasErrors(), // STATUS ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME - job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH // RJCTODO: What about nulls? - job.getNodeName().equals(LOCAL_HOST_NAME)}); // IS_LOCAL_JOB // RJCTODO: move method that also does this + job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH + job.getNodeName().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB + job.getManifest().getFilePath()}); // MANIFEST_FILE_PATH } } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Dashboard error refreshing table", ex); // NON-NLS // RJCTODO: Consider AID log + SYS_LOGGER.log(Level.SEVERE, "Dashboard error refreshing table", ex); } } @@ -1100,10 +1086,11 @@ public final class AutoIngestDashboard extends JPanel implements Observer { bnShowCaseLog = new javax.swing.JButton(); tbStatusMessage = new javax.swing.JTextField(); lbStatus = new javax.swing.JLabel(); - bnPrioritizeFolder = new javax.swing.JButton(); + bnPrioritizeJob = new javax.swing.JButton(); lbServicesStatus = new javax.swing.JLabel(); tbServicesStatusMessage = new javax.swing.JTextField(); bnOpenLogDir = new javax.swing.JButton(); + bnReprocessJob = new javax.swing.JButton(); pendingTable.setModel(pendingTableModel); pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N @@ -1257,11 +1244,11 @@ public final class AutoIngestDashboard extends JPanel implements Observer { lbStatus.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(lbStatus, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.lbStatus.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(bnPrioritizeFolder, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.bnPrioritizeFolder.text")); // NOI18N - bnPrioritizeFolder.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.bnPrioritizeFolder.toolTipText")); // NOI18N - bnPrioritizeFolder.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(bnPrioritizeJob, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.bnPrioritizeJob.text")); // NOI18N + bnPrioritizeJob.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.bnPrioritizeJob.toolTipText")); // NOI18N + bnPrioritizeJob.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - bnPrioritizeFolderActionPerformed(evt); + bnPrioritizeJobActionPerformed(evt); } }); @@ -1280,6 +1267,13 @@ public final class AutoIngestDashboard extends JPanel implements Observer { } }); + org.openide.awt.Mnemonics.setLocalizedText(bnReprocessJob, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.bnReprocessJob.text")); // NOI18N + bnReprocessJob.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnReprocessJobActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -1287,53 +1281,50 @@ public final class AutoIngestDashboard extends JPanel implements Observer { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 920, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(bnPrioritizeCase, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnPrioritizeJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 920, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(bnPrioritizeCase, javax.swing.GroupLayout.PREFERRED_SIZE, 117, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(bnPrioritizeFolder, javax.swing.GroupLayout.PREFERRED_SIZE, 117, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(bnPause) + .addGap(18, 18, 18) + .addComponent(bnRefresh, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(bnOptions) + .addGap(18, 18, 18) + .addComponent(bnOpenLogDir) + .addGap(18, 18, 18) + .addComponent(bnExit, javax.swing.GroupLayout.PREFERRED_SIZE, 94, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(50, 50, 50) - .addComponent(bnPause) - .addGap(50, 50, 50) - .addComponent(bnRefresh, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(50, 50, 50) - .addComponent(bnOptions) - .addGap(50, 50, 50) - .addComponent(bnOpenLogDir) - .addGap(50, 50, 50) - .addComponent(bnExit, javax.swing.GroupLayout.PREFERRED_SIZE, 94, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 920, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 920, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) - .addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, 116, Short.MAX_VALUE) - .addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) - .addComponent(bnDeleteCase, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) - .addComponent(bnShowCaseLog, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addGroup(layout.createSequentialGroup() - .addComponent(lbStatus) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(tbStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(lbCompleted) - .addComponent(lbRunning)) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addComponent(lbServicesStatus) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 920, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 920, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) + .addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, 116, Short.MAX_VALUE) + .addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) + .addComponent(bnDeleteCase, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE) + .addComponent(bnShowCaseLog, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnReprocessJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(layout.createSequentialGroup() + .addComponent(lbStatus) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tbStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lbCompleted) + .addComponent(lbRunning) + .addGroup(layout.createSequentialGroup() + .addComponent(lbServicesStatus) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnDeleteCase, bnExit, bnOpenLogDir, bnOptions, bnPause, bnRefresh, bnShowProgress}); @@ -1358,7 +1349,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { .addGap(82, 82, 82) .addComponent(bnPrioritizeCase) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(bnPrioritizeFolder))) + .addComponent(bnPrioritizeJob))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lbRunning) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1374,9 +1365,11 @@ public final class AutoIngestDashboard extends JPanel implements Observer { .addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE))) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(102, 102, 102) + .addGap(68, 68, 68) + .addComponent(bnReprocessJob) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(bnDeleteCase) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(bnShowCaseLog)) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -1419,55 +1412,47 @@ public final class AutoIngestDashboard extends JPanel implements Observer { * @param evt The button click event. */ private void bnDeleteCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDeleteCaseActionPerformed - // RJCTODO: Re-implement -// if (completedTableModel.getRowCount() < 0 || completedTable.getSelectedRow() < 0) { -// return; -// } -// -// String caseName = (String) completedTable.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal()); -// Object[] options = { -// org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.Delete"), -// org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.DoNotDelete") -// }; -// -// // Add checkbox to allow user to delete images in input folder as well -// JCheckBox deleteInputChk = new JCheckBox("Delete input images for this case in shared input folder"); -// Object[] msgContent = {org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.DeleteAreYouSure") + "\"" + caseName + "\"?", deleteInputChk}; -// int reply = JOptionPane.showOptionDialog(this, -// msgContent, -// org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.ConfirmDeletionHeader"), -// JOptionPane.DEFAULT_OPTION, -// JOptionPane.WARNING_MESSAGE, -// null, -// options, -// options[JOptionPane.NO_OPTION]); -// if (reply == JOptionPane.YES_OPTION) { -// bnDeleteCase.setEnabled(false); -// bnShowCaseLog.setEnabled(false); -// if (completedTableModel.getRowCount() > 0 && completedTable.getSelectedRow() >= 0) { -// String caseOutputFolderPath = completedTableModel.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.CASE_FOLDER.ordinal()).toString(); -// String caseAutFilePath = completedTableModel.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal()).toString() + CaseMetadata.getFileExtension(); -// completedTable.clearSelection(); -// this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); -// AutoIngestManager.CaseDeletionResult deletionResult = manager.deleteCase(Paths.get(caseOutputFolderPath), deleteInputChk.isSelected(), caseAutFilePath); -// this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); -// if (deletionResult.getCaseDeletionStatus() == AutoIngestManager.CaseDeletionResult.Status.FAILED) { -// JOptionPane.showMessageDialog(this, "Could not delete case " + caseName + " because it is in use", -// org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.DeletionFailed"), JOptionPane.INFORMATION_MESSAGE); -// } else if (deletionResult.getCaseDeletionStatus() == AutoIngestManager.CaseDeletionResult.Status.PARTIALLY_COMPLETED) { -// String str = "Deleted case \"" + caseName + "\", but not all files could be deleted.\nTo delete these files, stop automated ingest and delete \n" -// + caseOutputFolderPath + "\nand \n" + deletionResult.getCaseImageFolderPath() + "\nif present."; -// JOptionPane.showMessageDialog(this, str, -// org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.DeletionFailed"), JOptionPane.INFORMATION_MESSAGE); -// } -// /** -// * Need to update both the pending jobs table and the completed -// * jobs table since pending jobs for the deleted case are also -// * deleted. -// */ -// updateExecutor.submit(new UpdateAllJobsTablesTask()); -// } -// } + if (completedTableModel.getRowCount() < 0 || completedTable.getSelectedRow() < 0) { + return; + } + + String caseName = (String) completedTable.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal()); + Object[] options = { + org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.Delete"), + org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.DoNotDelete") + }; + Object[] msgContent = {org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.DeleteAreYouSure") + "\"" + caseName + "\"?"}; + int reply = JOptionPane.showOptionDialog(this, + msgContent, + org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "ConfirmationDialog.ConfirmDeletionHeader"), + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + options, + options[JOptionPane.NO_OPTION]); + if (reply == JOptionPane.YES_OPTION) { + bnDeleteCase.setEnabled(false); + bnShowCaseLog.setEnabled(false); + if (completedTableModel.getRowCount() > 0 && completedTable.getSelectedRow() >= 0) { + Path caseDirectoryPath = (Path) completedTableModel.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal()); + completedTable.clearSelection(); + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + CaseDeletionResult result = manager.deleteCase(caseName, caseDirectoryPath); + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (CaseDeletionResult.FAILED == result) { + JOptionPane.showMessageDialog(this, + String.format("Could not delete case %s. It may be in in use.", caseName), + org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.DeletionFailed"), + JOptionPane.INFORMATION_MESSAGE); + } else if (CaseDeletionResult.PARTIALLY_DELETED == result) { + JOptionPane.showMessageDialog(this, + String.format("Could not delete case %s. See system log for details.", caseName), + org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.DeletionFailed"), + JOptionPane.INFORMATION_MESSAGE); + } + updateExecutor.submit(new UpdateAllJobsTablesTask()); + } + } }//GEN-LAST:event_bnDeleteCaseActionPerformed /** @@ -1583,22 +1568,15 @@ public final class AutoIngestDashboard extends JPanel implements Observer { * @param evt The button click event. */ private void bnPrioritizeCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnPrioritizeCaseActionPerformed - // RJCTODO: Re-implement -// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { -// this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); -// String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); -// try { -// List prioritizedQueue = manager.prioritizeCase(caseName); -// refreshTable(prioritizedQueue, pendingTableModel, null); -// } catch (IOException ex) { -// logger.log(Level.SEVERE, String.format("Error while prioritizing case %s", caseName), ex); -// MessageNotifyUtil.Message.error("An error occurred while prioritizing the case."); -// } finally { -// pendingTable.clearSelection(); -// enablePendingTableButtons(false); -// AutoIngestDashboard.this.setCursor(Cursor.getDefaultCursor()); -// } -// } + if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); + List prioritizedQueue = manager.prioritizeCase(caseName); + refreshTable(prioritizedQueue, pendingTableModel, null); + pendingTable.clearSelection(); + enablePendingTableButtons(false); + AutoIngestDashboard.this.setCursor(Cursor.getDefaultCursor()); + } }//GEN-LAST:event_bnPrioritizeCaseActionPerformed /** @@ -1612,16 +1590,20 @@ public final class AutoIngestDashboard extends JPanel implements Observer { int selectedRow = completedTable.getSelectedRow(); if (selectedRow != -1) { Path caseDirectoryPath = (Path) completedTableModel.getValueAt(selectedRow, JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal()); - Path pathToLog = AutoIngestJobLogger.getLogPath(caseDirectoryPath); - if (pathToLog.toFile().exists()) { - Desktop.getDesktop().edit(pathToLog.toFile()); + if (null != caseDirectoryPath) { + Path pathToLog = AutoIngestJobLogger.getLogPath(caseDirectoryPath); + if (pathToLog.toFile().exists()) { + Desktop.getDesktop().edit(pathToLog.toFile()); + } else { + JOptionPane.showMessageDialog(this, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.ShowLogFailed.Message"), + org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.ShowLogFailed.Title"), JOptionPane.ERROR_MESSAGE); + } } else { - JOptionPane.showMessageDialog(this, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.ShowLogFailed.Message"), - org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.ShowLogFailed.Title"), JOptionPane.ERROR_MESSAGE); + MessageNotifyUtil.Message.warn("The case directory for this job has been deleted."); } } } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Dashboard error attempting to display case auto ingest log", ex); + SYS_LOGGER.log(Level.SEVERE, "Dashboard error attempting to display case auto ingest log", ex); Object[] options = {org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "DisplayLogDialog.okay")}; JOptionPane.showOptionDialog(this, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "DisplayLogDialog.cannotFindLog"), @@ -1634,25 +1616,17 @@ public final class AutoIngestDashboard extends JPanel implements Observer { } }//GEN-LAST:event_bnShowCaseLogActionPerformed - private void bnPrioritizeFolderActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnPrioritizeFolderActionPerformed - // RJCTODO: Re-implement -// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { -// this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); -// String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); -// String folderName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.DATA_SOURCE.ordinal())).toString(); -// try { -// List prioritizedQueue = manager.prioritizeJob(caseName, folderName); -// refreshTable(prioritizedQueue, pendingTableModel, null); -// } catch (IOException ex) { -// logger.log(Level.SEVERE, String.format("Error while prioritizing folder %s", folderName), ex); -// MessageNotifyUtil.Message.error("An error occurred while prioritizing the folder."); -// } finally { -// pendingTable.clearSelection(); -// enablePendingTableButtons(false); -// AutoIngestDashboard.this.setCursor(Cursor.getDefaultCursor()); -// } -// } - }//GEN-LAST:event_bnPrioritizeFolderActionPerformed + private void bnPrioritizeJobActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnPrioritizeJobActionPerformed + if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + Path manifestFilePath = (Path) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.MANIFEST_FILE_PATH.ordinal())); + List prioritizedQueue = manager.prioritizeJob(manifestFilePath); + refreshTable(prioritizedQueue, pendingTableModel, null); + pendingTable.clearSelection(); + enablePendingTableButtons(false); + AutoIngestDashboard.this.setCursor(Cursor.getDefaultCursor()); + } + }//GEN-LAST:event_bnPrioritizeJobActionPerformed private void bnOpenLogDirActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenLogDirActionPerformed Path logDirPath = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "var", "log"); @@ -1661,11 +1635,24 @@ public final class AutoIngestDashboard extends JPanel implements Observer { Desktop.getDesktop().open(logDir); } catch (IOException ex) { DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( - String.format("Unable to open log directory %s:\n%s", logDirPath, ex.getLocalizedMessage()), // RJCTODO: Localize + String.format("Unable to open log directory %s:\n%s", logDirPath, ex.getLocalizedMessage()), NotifyDescriptor.ERROR_MESSAGE)); } }//GEN-LAST:event_bnOpenLogDirActionPerformed + private void bnReprocessJobActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnReprocessJobActionPerformed + if (completedTableModel.getRowCount() < 0 || completedTable.getSelectedRow() < 0) { + return; + } + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + Path manifestPath = (Path) completedTableModel.getValueAt(completedTable.getSelectedRow(), JobsTableModelColumns.MANIFEST_FILE_PATH.ordinal()); + JobsSnapshot jobsSnapshot = manager.reprocessJob(manifestPath); + refreshTable(jobsSnapshot.getCompletedJobs(), completedTableModel, null); + refreshTable(jobsSnapshot.getPendingJobs(), pendingTableModel, null); + refreshTable(jobsSnapshot.getRunningJobs(), runningTableModel, null); + AutoIngestDashboard.this.setCursor(Cursor.getDefaultCursor()); + }//GEN-LAST:event_bnReprocessJobActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton bnCancelJob; private javax.swing.JButton bnCancelModule; @@ -1675,8 +1662,9 @@ public final class AutoIngestDashboard extends JPanel implements Observer { private javax.swing.JButton bnOptions; private javax.swing.JButton bnPause; private javax.swing.JButton bnPrioritizeCase; - private javax.swing.JButton bnPrioritizeFolder; + private javax.swing.JButton bnPrioritizeJob; private javax.swing.JButton bnRefresh; + private javax.swing.JButton bnReprocessJob; private javax.swing.JButton bnShowCaseLog; private javax.swing.JButton bnShowProgress; private javax.swing.JScrollPane completedScrollPane; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 71736bc40b..41fb47c081 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -27,13 +27,13 @@ import java.util.Date; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.ingest.IngestJob; /** - * An automated ingest job auto ingest jobs associated with a manifest file. A - * manifest file specifies a co-located data source and a case to which the data - * source is to be added. + * An automated ingest job for a manifest. The manifest specifies a co-located + * data source and a case to which the data source is to be added. */ @ThreadSafe public final class AutoIngestJob implements Comparable, Serializable { @@ -51,14 +51,38 @@ public final class AutoIngestJob implements Comparable, Serializa @GuardedBy("this") private Date stageStartDate; @GuardedBy("this") + transient private DataSourceProcessor dataSourceProcessor; + @GuardedBy("this") transient private IngestJob ingestJob; + @GuardedBy("this") + transient private boolean cancelled; // RJCTODO: Document + @GuardedBy("this") + transient private boolean completed; // RJCTODO: Document + @GuardedBy("this") + private Date completedDate; + @GuardedBy("this") + private boolean errorsOccurred; /** - * RJCTODO + * Constructs an automated ingest job for a manifest. The manifest specifies + * a co-located data source and a case to which the data source is to be + * added. * - * @param manifest + * @param manifest The manifest + * @param caseDirectoryPath The path to the case directory for the job, may + * be null. + * @param priority The priority of the job. The higher the number, + * the higher the priority. + * @param nodeName If the job is in progress, the node doing the + * processing, otherwise the locla host. + * @param stage The processing stage for display purposes. + * @param completedDate The date when the job was completed. Use the + * epoch (January 1, 1970, 00:00:00 GMT) to + * indicate the the job is not completed, i.e., new + * Date(0L). */ - AutoIngestJob(Manifest manifest, Path caseDirectoryPath, int priority, String nodeName, Stage stage) { + // RJCTODO: The null case directory is error-prone and the nodeName is confusing. + AutoIngestJob(Manifest manifest, Path caseDirectoryPath, int priority, String nodeName, Stage stage, Date completedDate, boolean errorsOccurred) { this.manifest = manifest; if (null != caseDirectoryPath) { this.caseDirectoryPath = caseDirectoryPath.toString(); @@ -69,12 +93,14 @@ public final class AutoIngestJob implements Comparable, Serializa this.nodeName = nodeName; this.stage = stage; this.stageStartDate = manifest.getDateFileCreated(); + this.completedDate = completedDate; + this.errorsOccurred = errorsOccurred; } /** - * RJCTODO + * Gets the auto ingest jobmanifest. * - * @return + * @return The manifest. */ Manifest getManifest() { return this.manifest; @@ -86,6 +112,7 @@ public final class AutoIngestJob implements Comparable, Serializa * * @return True or false */ + // RJCTODO: Use this or lose this synchronized boolean hasCaseDirectoryPath() { return (false == this.caseDirectoryPath.isEmpty()); } @@ -114,14 +141,21 @@ public final class AutoIngestJob implements Comparable, Serializa } } + /** + * Sets the priority of the job. A higher number indicates a higher + * priority. + * + * @param priority The priority. + */ synchronized void setPriority(Integer priority) { this.priority = priority; } /** - * RJCTODO + * Gets the priority of the job. A higher number indicates a higher + * priority. * - * @return + * @return The priority. */ synchronized Integer getPriority() { return this.priority; @@ -143,11 +177,7 @@ public final class AutoIngestJob implements Comparable, Serializa * @param stateStartedDate */ synchronized void setStage(Stage newState, Date stateStartedDate) { - if (Stage.CANCELLED == this.stage && Stage.COMPLETED != newState) { - /** - * Do not overwrite canceling status with anything other than - * completed status. - */ + if (Stage.CANCELLING == this.stage && Stage.COMPLETED != newState) { return; } this.stage = newState; @@ -214,10 +244,12 @@ public final class AutoIngestJob implements Comparable, Serializa return new StageDetails(description, startDate); } + synchronized void setDataSourceProcessor(DataSourceProcessor dataSourceProcessor) { + this.dataSourceProcessor = dataSourceProcessor; + } + /** * RJCTODO - * - * @param ingestStatus */ // RJCTODO: Consider moving this class into AIM and making this private synchronized void setIngestJob(IngestJob ingestJob) { @@ -226,8 +258,6 @@ public final class AutoIngestJob implements Comparable, Serializa /** * RJCTODO - * - * @return */ // RJCTODO: Consider moving this class into AIM and making this private. // Or move the AID into a separate package. Or do not worry about it. @@ -235,6 +265,83 @@ public final class AutoIngestJob implements Comparable, Serializa return this.ingestJob; } + /** + * RJCTODO + */ + synchronized void cancel() { + setStage(Stage.CANCELLING); + cancelled = true; + errorsOccurred = true; + if (null != dataSourceProcessor) { + dataSourceProcessor.cancel(); + } + if (null != ingestJob) { + ingestJob.cancel(IngestJob.CancellationReason.USER_CANCELLED); + } + } + + /** + * RJCTODO + */ + synchronized boolean isCancelled() { + return cancelled; + } + + /** + * RJCTODO + */ + synchronized void setCompleted() { + setStage(Stage.COMPLETED); + completed = true; + } + + /** + * RJCTODO + * + * @return + */ + synchronized boolean isCompleted() { + return completed; + } + + /** + * Sets the date the job was completed, with or without cancellation or + * errors. + * + * @param completedDate The completion date. + */ + synchronized void setCompletedDate(Date completedDate) { + this.completedDate = completedDate; + } + + /** + * Gets the date the job was completed, with or without cancellation or + * errors. + * + * @return True or false. + */ + synchronized Date getCompletedDate() { + return completedDate; // RJCTODO: Consider returning null if == 0 (epoch) + } + + /** + * Sets whether or not erros occurred during the processing of the job. + * + * @param errorsOccurred True or false; + */ + synchronized void setErrorsOccurred(boolean errorsOccurred) { + this.errorsOccurred = errorsOccurred; + } + + /** + * Queries whether or not erros occurred during the processing of the job. + * + * @return True or false. + */ + synchronized boolean hasErrors() { + return this.errorsOccurred; + } + /** * RJCTODO Gets name of the node associated with the job, possibly a remote * hose if the job is in progress. @@ -309,30 +416,21 @@ public final class AutoIngestJob implements Comparable, Serializa } /** - * Custom comparator that sorts the pending list with prioritized cases - * first, then nonprioritized cases. Prioritized cases are last in, first - * out. Nonprioritized cases are first in, first out. Prioritized times are - * from the creation time of the "prioritized" state file. Non prioritized - * are from the folder creation time. + * Comparator that sorts auto ingest jobs by priority in descending order. */ public static class PriorityComparator implements Comparator { /** * RJCTODO * - * @param o1 - * @param o2 + * @param job + * @param anotherJob * * @return */ @Override - public int compare(AutoIngestJob o1, AutoIngestJob o2) { - Integer result = o1.getPriority().compareTo(o2.getPriority()); - if (0 != result) { - return result; - } else { - return o1.getManifest().getDateFileCreated().compareTo(o2.getManifest().getDateFileCreated()); - } + public int compare(AutoIngestJob job, AutoIngestJob anotherJob) { + return -(job.getPriority().compareTo(anotherJob.getPriority())); } } @@ -364,6 +462,10 @@ public final class AutoIngestJob implements Comparable, Serializa } } + /** + * RJCTODO + */ + // RJCTODO: Combine this enum with StageDetails to make a single class. enum Stage { PENDING("Pending"), @@ -377,8 +479,7 @@ public final class AutoIngestJob implements Comparable, Serializa ANALYZING_FILES("Analyzing files"), EXPORTING_FILES("Exporting files"), CANCELLING_MODULE("Cancelling module"), - CANCELLED("Cancelled"), - INTERRUPTED("Cancelled"), + CANCELLING("Cancelling"), COMPLETED("Completed"); private final String displayText; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java index 801198b567..8e0fc3aeb4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java @@ -28,7 +28,6 @@ import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Date; -import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.Lock; @@ -59,6 +58,7 @@ final class AutoIngestJobLogger { private static final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss"; private static final SimpleDateFormat logDateFormat = new SimpleDateFormat(DATE_FORMAT_STRING); private final Path manifestPath; + private final String manifestFileName; private final String dataSourceFileName; private final Path caseDirectoryPath; private final String hostName; @@ -73,7 +73,7 @@ final class AutoIngestJobLogger { */ INFO, /** - * Qualifies a log message about an unexpected event or condition during + * Qualifies a log message about an unexpected event or condtion during * automated ingest processing. */ WARNING, @@ -111,6 +111,7 @@ final class AutoIngestJobLogger { */ AutoIngestJobLogger(Path manifestPath, String dataSourceFileName, Path caseDirectoryPath) { this.manifestPath = manifestPath; + manifestFileName = manifestPath.getFileName().toString(); this.dataSourceFileName = dataSourceFileName; this.caseDirectoryPath = caseDirectoryPath; hostName = NetworkUtils.getLocalHostName(); @@ -119,151 +120,146 @@ final class AutoIngestJobLogger { /** * Logs the cancellation of an auto ingest job during processing. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logJobCancelled() throws InterruptedException { + void logJobCancelled() throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.WARNING, "Auto ingest job cancelled during processing"); } /** * Logs the presence of a manifest file without a matching data source. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logMissingDataSource() throws InterruptedException { - log(MessageCategory.ERROR, "Data source file not found"); // RJCTODO: Check for this + void logMissingDataSource() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Data source file not found"); } /** - * Logs an error identifying the type of a data source. + * Logs a failure to extract an archived data source. * - * @param dataSource The data source. - * @param errorMessage The error. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file - * path. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logDataSourceTypeIdError(String errorMessage) throws InterruptedException { - log(MessageCategory.ERROR, String.format("Error identifying data source type: %s", errorMessage)); + void logFailedToExtractDataSource() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Failed to extract data source from archive"); } /** - * RJCTODO + * Logs a failure to parse a Cellebrite logical report data source. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logFailedToIdentifyDataSource() throws InterruptedException { - log(MessageCategory.ERROR, String.format("Failed to identifying data source type, cannot ingest")); + void logFailedToParseLogicalReportDataSource() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Failed to parse Cellebrite logical report data source"); } /** - * RJCTODO + * Logs a failure to identify an image data source as either a drive or + * phone image. * - * @param dataSourceType - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logDataSourceTypeId(String dataSourceType) throws InterruptedException { - log(MessageCategory.INFO, String.format("Identified data source as %s", dataSourceType)); + void logFailedToIdentifyImageDataSource() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, String.format("Failed to identifying data source as drive or phone image")); } /** * Logs cancellation of the addition of a data source to the case database. * - * @param dataSourceType The data source type. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logDataSourceProcessorCancelled(String dataSourceType) throws InterruptedException { // RJCTODO: Is this used now? - log(MessageCategory.WARNING, String.format("Cancelled adding data source to case as %s", dataSourceType)); + void logDataSourceProcessorCancelled() throws AutoIngestJobLoggerException, InterruptedException { // RJCTODO: Is this used now? + log(MessageCategory.WARNING, "Cancelled adding data source to case"); } /** * Logs the addition of a data source to the case database. * - * @param dataSourceType The data source type. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logDataSourceAdded(String dataSourceType) throws InterruptedException { - log(MessageCategory.INFO, String.format("Added data source to case as %s", dataSourceType)); + void logDataSourceAdded() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.INFO, "Added data source to case"); } /** - * Logs a critical error reported by a data source processor when adding a - * data source to the case database. + * Logs an failure adding a data source to the case database. * - * @param dataSourceType The data source type. - * @param errorMessage The error message. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logDataSourceProcessorError(String dataSourceType, String errorMessage) throws InterruptedException { - log(MessageCategory.ERROR, String.format("Critical error adding data source to case as %s: %s", dataSourceType, errorMessage)); + void logFailedToAddDataSource() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Failed to add data source to case"); } /** - * Logs a non-critical error reported by a data source processor when adding - * a data source to the case database. + * Logs failure of a data source to produce content. * - * @param dataSourceType The data source type. - * @param errorMessage The error message. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logDataSourceProcessorWarning(String dataSourceType, String errorMessage) throws InterruptedException { - log(MessageCategory.WARNING, String.format("Critical error adding data source to case as %s: %s", dataSourceType, errorMessage)); + void logNoDataSourceContent() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Data source failed to produce content"); } /** - * Logs an error adding a data source to the case database. + * Logs failure to analyze a data source due to ingest job settings errors. * - * @param dataSourceType The data source type. - * @param dataSource The data source. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logFailedToAddDataSource(String dataSourceType) throws InterruptedException { // RJCTODO: Why this and logDataSourceProcessorError? Bd handling of critical vs. non-critical? - log(MessageCategory.ERROR, String.format("Failed to add data source to case as %s", dataSourceType)); - } - - /** - * RJCTODO: Document and homogenize messages - * - * @param errors - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. - */ - void logIngestJobSettingsErrors(List errors) throws InterruptedException { - for (String error : errors) { - log(MessageCategory.ERROR, String.format("Settings error, analysis of data source by ingest modules not started: %s", error)); - } + void logIngestJobSettingsErrors() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Failed to analyze data source due to settings errors"); } /** * Logs failure to analyze a data source due to ingest module startup * errors. * - * @param errors The ingest module errors. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logIngestModuleStartupErrors(List errors) throws InterruptedException { - for (IngestModuleError error : errors) { - log(MessageCategory.ERROR, String.format("Analysis of data source by ingest modules not started, %s startup error: %s", error.getModuleDisplayName(), error.getThrowable().getLocalizedMessage())); - } + void logIngestModuleStartupErrors() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Failed to analyze data source due to ingest module startup errors"); } /** @@ -272,21 +268,27 @@ final class AutoIngestJobLogger { * * @param ex The ingest manager exception. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logAnalysisStartupError(IngestManagerException ex) throws InterruptedException { - log(MessageCategory.ERROR, String.format("Analysis of data source by ingest modules not started: %s", ex.getLocalizedMessage())); + void logAnalysisStartupError() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Failed to analyze data source due to ingest job startup error"); } /** * Logs the completion of analysis of a data source by the ingest modules. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logAnalysisCompleted() throws InterruptedException { - log(MessageCategory.INFO, "Analysis of data source by ingest modules completed"); + void logAnalysisCompleted() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.INFO, "Analysis of data source completed"); } /** @@ -296,67 +298,79 @@ final class AutoIngestJobLogger { * @param cancelledModuleName The display name of the cancelled ingest * module. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logIngestModuleCancelled(String cancelledModuleName) throws InterruptedException { + void logIngestModuleCancelled(String cancelledModuleName) throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.WARNING, String.format("%s analysis of data source cancelled", cancelledModuleName)); } /** * Logs the cancellation of analysis of a data source by the ingest modules. * - * @param reason The reason for cancellation. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logAnalysisCancelled(String reason) throws InterruptedException { - log(MessageCategory.WARNING, String.format("Analysis of data source by ingest modules cancelled: %s", reason)); + void logAnalysisCancelled() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.WARNING, "Analysis of data source cancelled"); } /** * Logs that automated file export is not enabled. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logFileExportDisabled() throws InterruptedException { + void logFileExportDisabled() throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.WARNING, "Automated file export is not enabled"); } /** - * RJCTODO + * Logs completion of file export. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logFileExportCompleted() throws InterruptedException { + void logFileExportCompleted() throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.INFO, "Automated file export completed"); } /** - * RJCTODO + * Logs failure to complete file export. * - * @param ex - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logFileExportError(Exception ex) throws InterruptedException { - log(MessageCategory.ERROR, String.format("Error exporting files: %s", ex.getMessage())); + void logFileExportError() throws AutoIngestJobLoggerException, InterruptedException { + log(MessageCategory.ERROR, "Error exporting files"); } /** * Logs discovery of a crashed auto ingest job for which recovery will be * attempted. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logCrashRecoveryWithRetry() throws InterruptedException { + void logCrashRecoveryWithRetry() throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.ERROR, "Detected crash while processing, reprocessing"); } @@ -364,24 +378,16 @@ final class AutoIngestJobLogger { * Logs discovery of a crashed auto ingest job for which recovery will not * be attempted because the retry limit for the job has been reached. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - void logCrashRecoveryNoRetry() throws InterruptedException { + void logCrashRecoveryNoRetry() throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.ERROR, "Detected crash while processing, reached retry limit for processing"); } - /** - * Logs an unexpected runtime exception, e.g., an exception caught by the - * automated ingest job processing exception firewall. - * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. - */ - void logErrorCondition(String message) throws InterruptedException { - log(MessageCategory.ERROR, message); - } - /** * Writes a message to the case auto ingest log. *

@@ -392,25 +398,55 @@ final class AutoIngestJobLogger { * @param category The message category. * @param message The message. * - * @throws InterruptedException if interrupted while blocked waiting to - * acquire an exclusive lock on the log file. + * @throws AutoIngestJobLoggerException if there is an error writing the log + * message. + * @throws InterruptedException if interrupted while blocked waiting + * to acquire an exclusive lock on the + * log file. */ - private void log(MessageCategory category, String message) throws InterruptedException { - String prefix = String.format("Failed to write case auto ingest message (\"%s\") for %s", message, manifestPath); + private void log(MessageCategory category, String message) throws AutoIngestJobLoggerException, InterruptedException { try (Lock lock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, getLogPath(caseDirectoryPath).toString(), LOCK_TIME_OUT, LOCK_TIME_OUT_UNIT)) { if (null != lock) { File logFile = getLogPath(caseDirectoryPath).toFile(); try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(logFile, logFile.exists())), true)) { - writer.println(String.format("%s %s: %s\\%s: %-8s: %s", logDateFormat.format((Date.from(Instant.now()).getTime())), hostName, manifestPath, dataSourceFileName, category.toString(), message)); + writer.println(String.format("%s %s: %s: %s: %-8s: %s", logDateFormat.format((Date.from(Instant.now()).getTime())), hostName, manifestFileName, dataSourceFileName, category.toString(), message)); } catch (IOException ex) { - AutoIngestSystemLogger.getLogger().log(Level.SEVERE, String.format("%s due to I/O error", prefix), ex); + throw new AutoIngestJobLoggerException(String.format("Failed to write case auto ingest log message (\"%s\") for %s", message, manifestPath), ex); } } else { - AutoIngestSystemLogger.getLogger().log(Level.SEVERE, String.format("%s due to lock timeout", prefix)); + throw new AutoIngestJobLoggerException(String.format("Failed to write case auto ingest log message (\"%s\") for %s due to time out acquiring log lock", message, manifestPath)); } - } catch (CoordinationServiceException ex) { - AutoIngestSystemLogger.getLogger().log(Level.SEVERE, String.format("%s due to coordination service error", prefix), ex); + throw new AutoIngestJobLoggerException(String.format("Failed to write case auto ingest log message (\"%s\") for %s", message, manifestPath), ex); + } + } + + /** + * Exception thrown when there is a problem writing a log message. + */ + final static class AutoIngestJobLoggerException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an exception to throw when there is a problem writing a + * log message. + * + * @param message The exception message. + */ + private AutoIngestJobLoggerException(String message) { + super(message); + } + + /** + * Constructs an exception to throw when there is a problem writing a + * log message. + * + * @param message The exception message. + * @param cause The cause of the exception, if it was an exception. + */ + private AutoIngestJobLoggerException(String message, Throwable cause) { + super(message, cause); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index d6617edb51..1880cef304 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -1,19 +1,20 @@ AutoIngestDashboard.bnRefresh.text=&Refresh -AutoIngestDashboard.lbCompleted.text=Completed -AutoIngestDashboard.lbRunning.text=Running -AutoIngestDashboard.lbPending.text=Pending +AutoIngestDashboard.lbCompleted.text=Completed Jobs +AutoIngestDashboard.lbRunning.text=Running Jobs +AutoIngestDashboard.lbPending.text=Pending Jobs AutoIngestDashboard.bnCancelModule.text=Cancel &Module AutoIngestDashboard.bnExit.text=&Exit AutoIngestDashboard.bnOptions.text=&Options AutoIngestDashboard.JobsTableModel.ColumnHeader.Case=Case AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder=Data Source AutoIngestDashboard.JobsTableModel.ColumnHeader.HostName=Host Name -AutoIngestDashboard.JobsTableModel.ColumnHeader.CreatedTime=Created Time -AutoIngestDashboard.JobsTableModel.ColumnHeader.StartedTime=Started Time -AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Completed Time +AutoIngestDashboard.JobsTableModel.ColumnHeader.CreatedTime=Job Created +AutoIngestDashboard.JobsTableModel.ColumnHeader.StartedTime=Stage Started +AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Job Completed AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage=Stage AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status -AutoIngestDashboard.bnShowProgress.text=&Show Progress +AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path +AutoIngestDashboard.bnShowProgress.text=Ingest Progress AutoIngestDashboard.bnResume.text=Resume AutoIngestDashboard.bnPause.text=Pause AutoIngestDashboard.bnPause.confirmHeader=Are you sure you want to pause? @@ -42,7 +43,7 @@ AutoIngestDashboard.ExitConsequences=This will cancel any currently running job AutoIngestDashboard.ExitingStatus=Exiting... AutoIngestDashboard.OK=OK AutoIngestDashboard.Cancel=Cancel -AutoIngestDashboard.AutoIngestStartupFailed.Message=Failed to start automated ingest.\nPlease see application log for details. +AutoIngestDashboard.AutoIngestStartupFailed.Message=Failed to start automated ingest.\nPlease see auto ingest system log for details. AutoIngestDashboard.AutoIngestStartupFailed.Title=Automated Ingest Error AutoIngestDashboard.AutoIngestStartupError=Failed to start automated ingest. Verify Multi-user Settings. AutoIngestDashboard.AutoIngestStartupWarning.Title=Automated Ingest Warning @@ -60,7 +61,7 @@ AutoIngestDashboard.tbServicesStatusMessage.Message=Case databases {0}, keyword AutoIngestDashboard.tbServicesStatusMessage.Message.Up=up AutoIngestDashboard.tbServicesStatusMessage.Message.Down=down AutoIngestDashboard.tbServicesStatusMessage.Message.Unknown=unknown -AutoIngestDashboard.PauseDueToSystemError=Paused due to system error, please consult the auto ingest system log" +AutoIngestDashboard.PauseDueToSystemError=Paused due to system error, please consult the auto ingest system log ConfirmationDialog.DoNotDelete=Do not delete ConfirmationDialog.Delete=Permanently delete ConfirmationDialog.DeleteAreYouSure=The entire case will be removed. Are you sure you want to delete case @@ -166,7 +167,7 @@ ReviewModeCasePanel.bnShowLog.text=&Show Log AutoIngestDashboard.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestDashboard.bnPrioritizeCase.text=Prioriti&ze Case AutoIngestDashboard.bnShowCaseLog.toolTipText=Display case log file for selected case -AutoIngestDashboard.bnShowCaseLog.text=Show &Log +AutoIngestDashboard.bnShowCaseLog.text=Show Case &Log ReviewModeCasePanel.bnShowLog.toolTipText=Display case log file for selected case CopyFilesPanel.bnCancelPendingJob.text=Ca&ncel CopyFilesPanel.tbDestinationCase.text= @@ -197,8 +198,6 @@ CaseImportPanel.Complete=Complete CaseImportPanel.Blank= CaseImportPanel.DeleteWarning=Make sure no important files are in the case source directory AutoIngestDashboard.lbStatus.text=Status: -AutoIngestDashboard.bnPrioritizeFolder.text=Prioritize &Folder -AutoIngestDashboard.bnPrioritizeFolder.toolTipText=Move this folder to the top of the Pending queue. SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping SingleUserCaseImporter.WillImport=Will import: SingleUserCaseImporter.None=None @@ -283,4 +282,7 @@ FileExporterSettingsPanel.BrowseReportTooltip_1=Browse for the Reports Folder FileExporterSettingsPanel.NewRuleTooltip_1=Clear the rule editor to begin a new rule FileExporterSettingsPanel.DeleteTooltip_1=Delete the selected rule FileExporterSettingsPanel.SaveTooltip_1=Save the current rule -AutoIngestDashboard.bnOpenLogDir.text=Open Log Directory +AutoIngestDashboard.bnOpenLogDir.text=Open System Logs Directory +AutoIngestDashboard.bnPrioritizeJob.text=Prioritize Job +AutoIngestDashboard.bnPrioritizeJob.toolTipText=Move this folder to the top of the Pending queue. +AutoIngestDashboard.bnReprocessJob.text=Reprocess Job diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java index 9de347eafd..1b1c4a1227 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java @@ -19,13 +19,13 @@ package org.sleuthkit.autopsy.experimental.autoingest; /** - * RJCTODO + * Namespace elements for auto ingest coordination service nodes. */ final class CoordinationServiceNamespace { - static final String ROOT_COORD_SCV_NAMESPACE = "autopsy"; // RJCTODO: Move this elsewhere + private static final String ROOT = "autopsy"; static String getRoot() { - return ROOT_COORD_SCV_NAMESPACE; + return ROOT; } private CoordinationServiceNamespace() { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java index 67ef803136..e4e272edd3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java @@ -19,34 +19,35 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.nio.ByteBuffer; +import java.util.Date; /** - * RJCTODO + * A coordination service node data transfer object for an auto ingest job + * manifest. The data include: processing status, priority, the number of times + * the auto ingest job for the manifest has crashed during processing, and the + * date the auto ingest job for the manifest was completed. */ -// RJCTODO: Consider making this encapsulate the locking as well, and to set the data as well final class ManifestNodeData { - enum ProcessingStatus { - PENDING, - PROCESSING, - COMPLETED, - } - private static final int DEFAULT_PRIORITY = 0; - private final boolean nodeDataIsSet; + private final boolean coordSvcNodeDataWasSet; private ProcessingStatus status; private int priority; private int numberOfCrashes; + private long completedDate; + private boolean errorsOccurred; /** - * RJCTODO + * Constructs a coordination service node data data transfer object for an + * auto ingest manifest from the raw bytes obtained from the coordination + * service. * - * @param nodeData + * @param nodeData The raw bytes received from the coordination service. */ ManifestNodeData(byte[] nodeData) { ByteBuffer buffer = ByteBuffer.wrap(nodeData); - this.nodeDataIsSet = buffer.hasRemaining(); - if (this.nodeDataIsSet) { + this.coordSvcNodeDataWasSet = buffer.hasRemaining(); + if (this.coordSvcNodeDataWasSet) { int rawStatus = buffer.getInt(); if (ProcessingStatus.PENDING.ordinal() == rawStatus) { this.status = ProcessingStatus.PENDING; @@ -54,97 +55,183 @@ final class ManifestNodeData { this.status = ProcessingStatus.PROCESSING; } else if (ProcessingStatus.COMPLETED.ordinal() == rawStatus) { this.status = ProcessingStatus.COMPLETED; + }else if (ProcessingStatus.DELETED.ordinal() == rawStatus) { + this.status = ProcessingStatus.DELETED; } this.priority = buffer.getInt(); this.numberOfCrashes = buffer.getInt(); + this.completedDate = buffer.getLong(); + int errorFlag = buffer.getInt(); + this.errorsOccurred = (1 == errorFlag); } else { this.status = ProcessingStatus.PENDING; this.priority = DEFAULT_PRIORITY; this.numberOfCrashes = 0; + this.completedDate = 0L; + this.errorsOccurred = false; } } /** - * RJCTODO + * Constructs a coordination service node data data transfer object for an + * auto ingest manifest from values provided by the auto ingest system. + * + * @param status The processing status of the manifest. + * @param priority The priority of the manifest. + * @param numberOfCrashes The number of times auto ingest jobs for the + * manifest have crashed during processing. + * @param completedDate The date the auto ingest job for the manifest was + * completed. */ - ManifestNodeData(ProcessingStatus status, int priority, int numberOfCrashes) { - this.nodeDataIsSet = false; + ManifestNodeData(ProcessingStatus status, int priority, int numberOfCrashes, Date completedDate, boolean errorOccurred) { + this.coordSvcNodeDataWasSet = false; this.status = status; this.priority = priority; this.numberOfCrashes = numberOfCrashes; + this.completedDate = completedDate.getTime(); + this.errorsOccurred = errorOccurred; } /** - * RJCTODO + * Indicates whether or not the coordination service node data was set, + * i.e., this object was constructed from raw bytes from the ccordination + * service node for the manifest. * - * @return + * @return True or false. */ - boolean isSet() { - return this.nodeDataIsSet; + // RJCTODO: This is confusing, consider changing the API so that the use case is to + // check the length of the node data from the coordination service before + // constructing an instance of this object. That would be much more clear! + boolean coordSvcNodeDataWasSet() { + return this.coordSvcNodeDataWasSet; } /** - * RJCTODO + * Gets the processing status of the manifest * - * @return + * @return The processing status of the manifest. */ ProcessingStatus getStatus() { return this.status; } /** + * Sets the processing status of the manifest * - * @param status + * @param status The processing status of the manifest. */ void setStatus(ProcessingStatus status) { this.status = status; } /** + * Gets the priority of the manifest. * - * @return + * @return The priority of the manifest. */ int getPriority() { return this.priority; } /** + * Sets the priority of the manifest. A higher number indicates a higheer + * priority. * - * @param priority + * @param priority The priority of the manifest. */ void setPriority(int priority) { this.priority = priority; } /** - * RJCTODO + * Gets the number of times auto ingest jobs for the manifest have crashed + * during processing. * - * @return + * @return The number of times auto ingest jobs for the manifest have + * crashed during processing. */ int getNumberOfCrashes() { return this.numberOfCrashes; } /** - * RJCTODO + * Sets the number of times auto ingest jobs for the manifest have crashed + * during processing. * - * @param attempts + * @param numberOfCrashes The number of times auto ingest jobs for the + * manifest have crashed during processing. */ - void setNumberOfCrashes(int attempts) { - this.numberOfCrashes = attempts; + void setNumberOfCrashes(int numberOfCrashes) { + this.numberOfCrashes = numberOfCrashes; } /** - * RJCTODO + * Gets the date the auto ingest job for the manifest was completed. * - * @return + * @return The date the auto ingest job for the manifest was completed. The + * epoch (January 1, 1970, 00:00:00 GMT) indicates the date is not + * set, i.e., Date.getTime() returns 0L. + */ + Date getCompletedDate() { + return new Date(this.completedDate); + } + + /** + * Sets the date the auto ingest job for the manifest was completed. + * + * @param completedDate The date the auto ingest job for the manifest was + * completed. Use the epoch (January 1, 1970, 00:00:00 + * GMT) to indicate the date is not set, i.e., new + * Date(0L). + */ + void setCompletedDate(Date completedDate) { + this.completedDate = completedDate.getTime(); + } + + /** + * Queries whether or not any errors occurred during the processing of the + * auto ingest job for the manifest. + * + * @return True or false. + */ + boolean getErrorsOccurred() { + return this.errorsOccurred; + } + + /** + * Sets whether or not any errors occurred during the processing of the auto + * ingest job for the manifest. + * + * @param errorsOccurred True or false. + */ + void setErrorsOccurred(boolean errorsOccurred) { + this.errorsOccurred = errorsOccurred; + } + + /** + * Gets the node data as raw bytes that can be sent to the coordination + * service. + * + * @return The manifest node data as a byte array. */ byte[] toArray() { - ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 3); + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 4 + Long.BYTES); buffer.putInt(this.status.ordinal()); buffer.putInt(this.priority); buffer.putInt(this.numberOfCrashes); + buffer.putLong(this.completedDate); + buffer.putInt(this.errorsOccurred ? 1 : 0); return buffer.array(); } + /** + * Processing status for the auto ingest job for the manifest. + */ + enum ProcessingStatus { + PENDING, + PROCESSING, + COMPLETED, + DELETED + } + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java index 7752e4d1dc..622912f95a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java @@ -28,11 +28,9 @@ import java.util.Collections; import java.util.List; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.casemodule.GeneralFilter; -import org.sleuthkit.autopsy.coreutils.Logger; final class PathUtils { - private static final Logger logger = Logger.getLogger(PathUtils.class.getName()); private static final List CASE_METADATA_FILE_EXTS = Arrays.asList(new String[]{CaseMetadata.getFileExtension()}); private static final GeneralFilter caseMetadataFileFilter = new GeneralFilter(CASE_METADATA_FILE_EXTS, "Autopsy Case File"); @@ -71,7 +69,7 @@ final class PathUtils { * * @return A list of the output case folder paths. */ - static List findCaseFolders(Path folderToSearch) { + static List findCaseFolders(Path folderToSearch) { // RJCTODO: Rename File searchFolder = new File(folderToSearch.toString()); if (!searchFolder.isDirectory()) { return Collections.emptyList(); @@ -112,32 +110,6 @@ final class PathUtils { return caseDataFiles.length != 0; } - /** - * Extracts the path to the case images folder path from an image folder - * path. - * - * @param rootImageFoldersPath The root image folders path. - * @param imageFolderPath The image folder path. - * - * @return The root input folder path for a case. - */ - static Path caseImagesPathFromImageFolderPath(Path rootImageFoldersPath, Path imageFolderPath) { - return rootImageFoldersPath.resolve(imageFolderPath.subpath(0, rootImageFoldersPath.getNameCount() + 1).getFileName()); - } - - /** - * Extracts the case name from an image folder path. - * - * @param rootImageFoldersPath The root image folders path. - * @param imageFolderPath The image folder path. - * - * @return The case name. - */ - static String caseNameFromImageFolderPath(Path rootImageFoldersPath, Path imageFolderPath) { - Path caseImagesPath = PathUtils.caseImagesPathFromImageFolderPath(rootImageFoldersPath, imageFolderPath); - return caseImagesPath.getFileName().toString(); - } - /** * Extracts the case name from a case folder path. * @@ -163,7 +135,7 @@ final class PathUtils { * * @return A case folder path with a time stamp suffix. */ - static Path createCaseFolderPath(Path caseFoldersPath, String caseName) { + static Path createCaseFolderPath(Path caseFoldersPath, String caseName) { // RJCTODO: Rename String folderName = caseName + "_" + TimeStampUtils.createTimeStamp(); return Paths.get(caseFoldersPath.toString(), folderName); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java index 5c949ebc36..0f0f46be21 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java @@ -61,8 +61,8 @@ public final class ReviewModeCasePanel extends JPanel { private static final int STATUS_COL_MIN_WIDTH = 55; private static final int STATUS_COL_MAX_WIDTH = 250; private static final int STATUS_COL_PREFERRED_WIDTH = 60; - private static final int MILLISECONDS_TO_WAIT_BEFORE_STARTING = 500; - private static final int MILLISECONDS_TO_WAIT_BETWEEN_UPDATES = 30000; + private static final int MILLISECONDS_TO_WAIT_BEFORE_STARTING = 500; // RJCTODO: Shorten name + private static final int MILLISECONDS_TO_WAIT_BETWEEN_UPDATES = 30000; // RJCTODO: Shorten name private ScheduledThreadPoolExecutor casesTableRefreshExecutor; /* @@ -84,7 +84,7 @@ public final class ReviewModeCasePanel extends JPanel { CREATEDTIME, COMPLETEDTIME, STATUS_ICON, - OUTPUTFOLDER + OUTPUTFOLDER // RJCTODO: Change name } private final String[] columnNames = {CASE_HEADER, CREATEDTIME_HEADER, COMPLETEDTIME_HEADER, STATUS_ICON_HEADER, OUTPUT_FOLDER_HEADER}; private DefaultTableModel caseTableModel; @@ -325,7 +325,7 @@ public final class ReviewModeCasePanel extends JPanel { autoIngestCase.getCaseName(), autoIngestCase.getCreationDate(), autoIngestCase.getLastAccessedDate(), - autoIngestCase.getStatus(), + (AutoIngestCase.CaseStatus.OK != autoIngestCase.getStatus()), autoIngestCase.getCaseDirectoryPath().toString()}); } }