From d3d08e22c30ce000cc90ab1de7e7da4efaaa4c47 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 6 Feb 2017 09:39:55 -0500 Subject: [PATCH 01/10] Adding option to copy local disk to data source selection --- .../autopsy/casemodule/AddImageTask.java | 9 ++- .../autopsy/casemodule/Bundle.properties | 4 + .../autopsy/casemodule/ImageDSProcessor.java | 2 +- .../casemodule/LocalDiskDSProcessor.java | 8 +- .../autopsy/casemodule/LocalDiskPanel.form | 60 ++++++++++++++- .../autopsy/casemodule/LocalDiskPanel.java | 77 ++++++++++++++++++- 6 files changed, 149 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 227bfd0650..e4f03757fb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -41,6 +41,7 @@ class AddImageTask implements Runnable { private final String deviceId; private final String imagePath; private final String timeZone; + private final String imageWriterPath; private final boolean ignoreFatOrphanFiles; private final DataSourceProcessorProgressMonitor progressMonitor; private final DataSourceProcessorCallback callback; @@ -74,15 +75,19 @@ class AddImageTask implements Runnable { * java.util.TimeZone.getID. * @param ignoreFatOrphanFiles Whether to parse orphans if the image has a * FAT filesystem. + * @param imageWriterPath Path that a copy of the image should be written to. + * Use empty string to disable image writing * @param progressMonitor Progress monitor to report progress during * processing. * @param callback Callback to call when processing is done. */ - AddImageTask(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddImageTask(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, String imageWriterPath, + DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { this.deviceId = deviceId; this.imagePath = imagePath; this.timeZone = timeZone; this.ignoreFatOrphanFiles = ignoreFatOrphanFiles; + this.imageWriterPath = imageWriterPath; this.callback = callback; this.progressMonitor = progressMonitor; tskAddImageProcessLock = new Object(); @@ -101,7 +106,7 @@ class AddImageTask implements Runnable { try { currentCase.getSleuthkitCase().acquireExclusiveLock(); synchronized (tskAddImageProcessLock) { - tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles); + tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles, imageWriterPath); } Thread progressUpdateThread = new Thread(new ProgressUpdater(progressMonitor, tskAddImageProcess)); progressUpdateThread.start(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 12184925e9..18c8263b72 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -241,3 +241,7 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close +LocalDiskPanel.copyImageCheckbox.text=Make a copy of the disk +LocalDiskPanel.imageWriterPathLabel.text=jLabel1 +LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the case directory as : +LocalDiskPanel.imageWriterErrorLabel.text=Error Label diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index 1cceb7ab12..8837c496d0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -185,7 +185,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour * @param callback Callback to call when processing is done. */ public void run(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addImageTask = new AddImageTask(deviceId, imagePath, timeZone, ignoreFatOrphanFiles, progressMonitor, callback); + addImageTask = new AddImageTask(deviceId, imagePath, timeZone, ignoreFatOrphanFiles, "", progressMonitor, callback); new Thread(addImageTask).start(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java index 015f7751ea..80d1306598 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java @@ -54,6 +54,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData private String deviceId; private String drivePath; private String timeZone; + private String imageWriterPath = ""; private boolean ignoreFatOrphanFiles; private boolean setDataSourceOptionsCalled; @@ -137,8 +138,11 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData drivePath = configPanel.getContentPaths(); timeZone = configPanel.getTimeZone(); ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); + if(configPanel.getImageWriterEnabled()){ + imageWriterPath = configPanel.getImageWriterPath(); + } } - addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, progressMonitor, callback); + addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, imageWriterPath, progressMonitor, callback); new Thread(addDiskTask).start(); } @@ -164,7 +168,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData * @param callback Callback to call when processing is done. */ public void run(String deviceId, String drivePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, progressMonitor, callback); + addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, imageWriterPath, progressMonitor, callback); new Thread(addDiskTask).start(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index b1409546e6..4c711c0187 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -35,12 +35,21 @@ + - + + + + + + + + + - + @@ -61,7 +70,16 @@ - + + + + + + + + + + @@ -165,5 +183,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 0d485b3392..88a276494d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -22,6 +22,7 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; +import java.io.File; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -56,6 +57,7 @@ final class LocalDiskPanel extends JPanel { private static final long serialVersionUID = 1L; private List disks; private LocalDiskModel model; + private String fullImageWriterPath = ""; private boolean enableNext = false; /** @@ -106,6 +108,10 @@ final class LocalDiskPanel extends JPanel { timeZoneComboBox = new javax.swing.JComboBox<>(); noFatOrphansCheckbox = new javax.swing.JCheckBox(); descLabel = new javax.swing.JLabel(); + copyImageCheckbox = new javax.swing.JCheckBox(); + imageWriterPathLabel = new javax.swing.JLabel(); + imageWriterPathDescLabel = new javax.swing.JLabel(); + imageWriterErrorLabel = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(0, 65)); setPreferredSize(new java.awt.Dimension(485, 65)); @@ -132,6 +138,16 @@ final class LocalDiskPanel extends JPanel { descLabel.setFont(descLabel.getFont().deriveFont(descLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.descLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(copyImageCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.copyImageCheckbox.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathDescLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathDescLabel.text")); // NOI18N + + imageWriterErrorLabel.setFont(imageWriterErrorLabel.getFont().deriveFont(imageWriterErrorLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + imageWriterErrorLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(imageWriterErrorLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterErrorLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -146,10 +162,17 @@ final class LocalDiskPanel extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(noFatOrphansCheckbox) + .addComponent(copyImageCheckbox) .addGroup(layout.createSequentialGroup() .addGap(21, 21, 21) - .addComponent(descLabel))) - .addGap(0, 102, Short.MAX_VALUE)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(descLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(imageWriterPathDescLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGap(0, 12, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -167,14 +190,26 @@ final class LocalDiskPanel extends JPanel { .addComponent(noFatOrphansCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(descLabel) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(copyImageCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(imageWriterPathDescLabel) + .addComponent(imageWriterPathLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(imageWriterErrorLabel) + .addContainerGap(58, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox copyImageCheckbox; private javax.swing.JLabel descLabel; private javax.swing.JComboBox diskComboBox; private javax.swing.JLabel diskLabel; private javax.swing.JLabel errorLabel; + private javax.swing.JLabel imageWriterErrorLabel; + private javax.swing.JLabel imageWriterPathDescLabel; + private javax.swing.JLabel imageWriterPathLabel; private javax.swing.JCheckBox noFatOrphansCheckbox; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; @@ -214,6 +249,36 @@ final class LocalDiskPanel extends JPanel { boolean getNoFatOrphans() { return noFatOrphansCheckbox.isSelected(); } + + private void setPotentialImageWriterPath(LocalDisk disk){ + System.out.println("\n\nsetPotentialImageWriterPath"); + System.out.println(" name: " + disk.getName()); + System.out.println(" path: " + disk.getPath()); + String path = disk.getName().replaceAll("[:]", ""); + path += ".vhd"; + System.out.println(" Output file: " + path); + imageWriterPathLabel.setText(path); + fullImageWriterPath = Case.getCurrentCase().getCaseDirectory() + "\\" + path; + } + + private boolean imageWriterPathIsValid(){ + + File f = new File(fullImageWriterPath); + if(f.exists()) { + this.imageWriterErrorLabel.setText("Error - file already exists"); // Put in bundle!!!! + return false; + } + imageWriterErrorLabel.setText(""); + return true; + } + + boolean getImageWriterEnabled(){ + return copyImageCheckbox.isSelected(); + } + + String getImageWriterPath(){ + return fullImageWriterPath; + } /** * Should we enable the wizard's next button? Always return true because we @@ -222,6 +287,11 @@ final class LocalDiskPanel extends JPanel { * @return true */ public boolean validatePanel() { + if(copyImageCheckbox.isSelected() && + ! imageWriterPathIsValid()){ + return false; + } + return enableNext; } @@ -318,6 +388,7 @@ final class LocalDiskPanel extends JPanel { if (ready) { selected = (LocalDisk) anItem; enableNext = true; + setPotentialImageWriterPath((LocalDisk) selected); try { firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); From 149b3b594ded1a42613ee861d1addec0fd234c5f Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Feb 2017 10:51:43 -0500 Subject: [PATCH 02/10] Improve UI for enabling image writer. --- .../sleuthkit/autopsy/casemodule/Bundle.properties | 3 ++- .../autopsy/casemodule/LocalDiskPanel.form | 2 +- .../autopsy/casemodule/LocalDiskPanel.java | 13 ++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 18c8263b72..914aa8a48d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -76,6 +76,7 @@ LocalDiskPanel.timeZoneLabel.text=Please select the input timezone: LocalDiskPanel.noFatOrphansCheckbox.toolTipText= LocalDiskPanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems LocalDiskPanel.descLabel.text=(faster results, although some data will not be searched) +LocalDiskPanel.imageWriterError.text=Error - file already exists MissingImageDialog.browseButton.text=Browse MissingImageDialog.pathNameTextField.text= AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status @@ -241,7 +242,7 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close -LocalDiskPanel.copyImageCheckbox.text=Make a copy of the disk +LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is being analyzed LocalDiskPanel.imageWriterPathLabel.text=jLabel1 LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the case directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index 4c711c0187..a8532bb1d4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -79,7 +79,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 88a276494d..676682b258 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -23,6 +23,7 @@ import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.io.File; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -90,6 +91,7 @@ final class LocalDiskPanel extends JPanel { errorLabel.setVisible(false); errorLabel.setText(""); diskComboBox.setEnabled(false); + imageWriterErrorLabel.setText(""); } /** @@ -198,7 +200,7 @@ final class LocalDiskPanel extends JPanel { .addComponent(imageWriterPathLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(58, Short.MAX_VALUE)) + .addContainerGap(63, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables @@ -251,21 +253,18 @@ final class LocalDiskPanel extends JPanel { } private void setPotentialImageWriterPath(LocalDisk disk){ - System.out.println("\n\nsetPotentialImageWriterPath"); - System.out.println(" name: " + disk.getName()); - System.out.println(" path: " + disk.getPath()); String path = disk.getName().replaceAll("[:]", ""); + path += " " + System.currentTimeMillis(); path += ".vhd"; - System.out.println(" Output file: " + path); imageWriterPathLabel.setText(path); - fullImageWriterPath = Case.getCurrentCase().getCaseDirectory() + "\\" + path; + fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); } private boolean imageWriterPathIsValid(){ File f = new File(fullImageWriterPath); if(f.exists()) { - this.imageWriterErrorLabel.setText("Error - file already exists"); // Put in bundle!!!! + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterError.text")); return false; } imageWriterErrorLabel.setText(""); From 46824179a0863a322ba77dc58fef287e0294d4ed Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Feb 2017 11:15:39 -0500 Subject: [PATCH 03/10] Working on moving Image Writer output to a subfolder of ModuleOutput --- .../autopsy/casemodule/LocalDiskPanel.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 676682b258..b7300c8f1c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -252,12 +252,24 @@ final class LocalDiskPanel extends JPanel { return noFatOrphansCheckbox.isSelected(); } + private static String getDefaultImageWriterFolder(){ + //return Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toString(); + return Paths.get(Case.getCurrentCase().getModuleDirectory()).toString(); // Don't want to commit in a broken state + } + private void setPotentialImageWriterPath(LocalDisk disk){ + + File subDirectory = Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toFile(); + if (!subDirectory.exists()) { + subDirectory.mkdirs(); + } + String path = disk.getName().replaceAll("[:]", ""); path += " " + System.currentTimeMillis(); path += ".vhd"; imageWriterPathLabel.setText(path); - fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); + //fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); + fullImageWriterPath = Paths.get(getDefaultImageWriterFolder(), path).toString(); } private boolean imageWriterPathIsValid(){ From 792f0a996a437eedfe3be6a7a75ce2096635520a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Feb 2017 14:56:43 -0500 Subject: [PATCH 04/10] Change default folder for VHD --- .../sleuthkit/autopsy/casemodule/Bundle.properties | 2 +- .../sleuthkit/autopsy/casemodule/LocalDiskPanel.form | 4 ++-- .../sleuthkit/autopsy/casemodule/LocalDiskPanel.java | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 914aa8a48d..f852c046e6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -244,5 +244,5 @@ IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is being analyzed LocalDiskPanel.imageWriterPathLabel.text=jLabel1 -LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the case directory as : +LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the Module Output/Image Writer directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index a8532bb1d4..e43c8b65c9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -49,7 +49,7 @@ - + @@ -79,7 +79,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index b7300c8f1c..ed53684b0c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -92,6 +92,7 @@ final class LocalDiskPanel extends JPanel { errorLabel.setText(""); diskComboBox.setEnabled(false); imageWriterErrorLabel.setText(""); + imageWriterPathLabel.setText(""); } /** @@ -174,7 +175,7 @@ final class LocalDiskPanel extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addGap(0, 12, Short.MAX_VALUE)) + .addGap(0, 0, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -200,7 +201,7 @@ final class LocalDiskPanel extends JPanel { .addComponent(imageWriterPathLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(63, Short.MAX_VALUE)) + .addContainerGap(124, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables @@ -253,13 +254,12 @@ final class LocalDiskPanel extends JPanel { } private static String getDefaultImageWriterFolder(){ - //return Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toString(); - return Paths.get(Case.getCurrentCase().getModuleDirectory()).toString(); // Don't want to commit in a broken state + return Paths.get(Case.getCurrentCase().getModuleDirectory(), "Image Writer").toString(); } private void setPotentialImageWriterPath(LocalDisk disk){ - File subDirectory = Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toFile(); + File subDirectory = Paths.get(getDefaultImageWriterFolder()).toFile(); if (!subDirectory.exists()) { subDirectory.mkdirs(); } @@ -268,7 +268,6 @@ final class LocalDiskPanel extends JPanel { path += " " + System.currentTimeMillis(); path += ".vhd"; imageWriterPathLabel.setText(path); - //fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); fullImageWriterPath = Paths.get(getDefaultImageWriterFolder(), path).toString(); } From d6292a4f94c32bf8b8cf524dce9f6abee4df9c21 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 21 Feb 2017 15:01:25 -0500 Subject: [PATCH 05/10] Finishing the Image Writer finish method --- .../autopsy/casemodule/AddImageTask.java | 3 + .../autopsy/casemodule/Bundle.properties | 1 + .../sleuthkit/autopsy/casemodule/Case.java | 18 ++ .../autopsy/casemodule/ImageWriter.java | 251 ++++++++++++++++++ .../autopsy/casemodule/LocalDiskPanel.form | 14 +- .../autopsy/casemodule/LocalDiskPanel.java | 13 +- 6 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index e4f03757fb..cf7db4a392 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -206,6 +206,9 @@ class AddImageTask implements Runnable { if (!verificationError.isEmpty()) { errorMessages.add(verificationError); } + if(! imageWriterPath.isEmpty()){ + Case.getCurrentCase().scheduleImageWriterFinish(imageId); + } newDataSources.add(newImage); } else { String errorMessage = String.format("Error commiting adding image %s to the case database, no object id returned", imagePath); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index f852c046e6..12e2b15179 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -246,3 +246,4 @@ LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is LocalDiskPanel.imageWriterPathLabel.text=jLabel1 LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the Module Output/Image Writer directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label +LocalDiskPanel.jLabel1.text=Note that at least one ingest module must be run to create a complete copy diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index d681e81b48..f0c42d2fec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -286,6 +286,7 @@ public class Case implements SleuthkitCase.ErrorObserver { private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; private static final Logger logger = Logger.getLogger(Case.class.getName()); private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); + private final ImageWriter imageWriter = new ImageWriter(); private static String appName; private static Case currentCase; private static CoordinationService.Lock currentCaseLock; @@ -1437,6 +1438,7 @@ public class Case implements SleuthkitCase.ErrorObserver { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); + oldCase.closeImageWriter(); completeCaseChange(null); //closes windows, etc if (null != oldCase.tskErrorReporter) { oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case @@ -1613,6 +1615,22 @@ public class Case implements SleuthkitCase.ErrorObserver { } return currentCaseExecutor; } + + /** + * Register the ID of an image that is being copied using ImageWriter. + * This will cause the image to be finished after ingest is complete. + * @param imageID The image ID + */ + void scheduleImageWriterFinish(long imageID){ + imageWriter.addDataSourceId(imageID); + } + + /** + * Cancel all tasks associated with Image Writer + */ + void closeImageWriter(){ + imageWriter.close(); + } /** * Gets the time zone(s) of the image data source(s) in this case. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java new file mode 100644 index 0000000000..6510c5231a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java @@ -0,0 +1,251 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashSet; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import org.netbeans.api.progress.ProgressHandle; +import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent; +import org.sleuthkit.datamodel.SleuthkitJNI; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * The ImageWriter class is used to complete VHD copies created from local disks + * after the ingest process completes. + */ +class ImageWriter { + + private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); + + private final HashSet dataSourceIds = new HashSet<>(); + private final Object dataSourceIdsLock; // Get this lock before accessing dataSourceIds + + private final HashSet> progressUpdaters = new HashSet<>(); + private final HashSet imagesBeingFinished = new HashSet<>(); + private final HashSet progressBars = new HashSet<>(); + private final HashSet> finishTasksInProgress = new HashSet<>(); + private boolean isCancelled; + private final Object currentTasksLock; // Get this lock before accessing imagesBeingFinished, progressBars, progressUpdaters, finishTasksInProgress or isCancelled + + private boolean listenerStarted; + private ScheduledThreadPoolExecutor periodicTasksExecutor = null; + private final boolean doUI; + + ImageWriter(){ + dataSourceIdsLock = new Object(); + currentTasksLock = new Object(); + listenerStarted = false; + isCancelled = false; + + doUI = RuntimeProperties.coreComponentsAreActive(); + if(doUI){ + periodicTasksExecutor = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS + } + } + + /** + * Creates a listener on IngestJobEvents if it hasn't already been started. + * When a DataSourceAnalysisCompletedEvent arrives, if it matches + * the data source ID of an image that is using Image Writer, then finish the image + * (fill in any gaps). The AddImageTask for this data source must have included + * a non-empty imageWriterPath parameter to enable Image Writer. + */ + private synchronized void startListener(){ + if(! listenerStarted){ + IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ + DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; + + if(event.getDataSource() != null){ + long imageId = event.getDataSource().getId(); + String name = event.getDataSource().getName(); + + // Check whether we need to run finishImage for this data source + synchronized(dataSourceIdsLock){ + if( ! ImageWriter.this.dataSourceIds.contains(imageId)){ + // Image writer was not used on this data source or we've already finished it + return; + } else { + // Remove the imageId from the list here so we can't get past this point twice + // for the same image. Multiple DataSourceAnalysisCompletedEvent events can come from + // the same image if more ingest modules are run later, but the imageId is only added + // to the list during the intial task to add the image to the database. + ImageWriter.this.dataSourceIds.remove(imageId); + } + } + logger.log(Level.INFO, String.format("Finishing VHD image for %s", + event.getDataSource().getName())); //NON-NLS + + new Thread(() -> { + try{ + Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(imageId); + ProgressHandle progressHandle = null; + ScheduledFuture progressUpdateTask = null; + + if(doUI){ + progressHandle = ProgressHandle.createHandle("Image writer - " + name); + progressHandle.start(100); + progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( + new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); + } + + synchronized(currentTasksLock){ + ImageWriter.this.imagesBeingFinished.add(image.getImageHandle()); + + if(doUI){ + if(isCancelled){ + progressUpdateTask.cancel(true); + return; + } + ImageWriter.this.progressUpdaters.add(progressUpdateTask); + ImageWriter.this.progressBars.add(progressHandle); + } + } + + // The added complexity here with the Future is because we absolutely need to make sure + // the call to finishImageWriter returns before allowing the TSK data structures to be freed + // during case close. + Future finishTask = Executors.newSingleThreadExecutor().submit(() -> { + try{ + SleuthkitJNI.finishImageWriter(image.getImageHandle()); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }); + synchronized(currentTasksLock){ + ImageWriter.this.finishTasksInProgress.add(finishTask); + } + + // Wait for finishImageWriter to complete + try{ + // The call to get() will happen twice if the user closes the case, which is ok + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + + synchronized(currentTasksLock){ + ImageWriter.this.finishTasksInProgress.remove(finishTask); + ImageWriter.this.imagesBeingFinished.remove(image.getImageHandle()); + + if(doUI){ + progressUpdateTask.cancel(true); + ImageWriter.this.progressUpdaters.remove(progressUpdateTask); + progressHandle.finish(); + ImageWriter.this.progressBars.remove(progressHandle); + } + } + + logger.log(Level.INFO, String.format("Finished writing VHD image for %s", event.getDataSource().getName())); //NON-NLS + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }).start(); + } else { + logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS + } + } + } + }); + } + listenerStarted = true; + } + + /** + * Add a data source ID to the list of images to run finishImage on. + * Also starts the listener if needed. + * @param id The dataSource/Image ID + */ + void addDataSourceId(Long id){ + startListener(); + synchronized(dataSourceIdsLock){ + dataSourceIds.add(id); + } + } + + /** + * Stop any open progress update task, finish the progress bars, and tell + * the finishImage process to stop + */ + void close(){ + synchronized(currentTasksLock){ + isCancelled = true; + + for(ScheduledFuture task:ImageWriter.this.progressUpdaters){ + task.cancel(true); + } + + for(Long handle:imagesBeingFinished){ + SleuthkitJNI.cancelFinishImage(handle); + logger.log(Level.SEVERE, "Case closed before VHD image could be finished"); //NON-NLS + } + + // Wait for all the finish tasks to end + for(Future task:ImageWriter.this.finishTasksInProgress){ + try{ + task.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + } + + for(ProgressHandle progressHandle:ImageWriter.this.progressBars){ + progressHandle.finish(); + } + } + } + + /** + * Task to query the Sleuthkit processing to get the percentage done. + */ + private final class ProgressUpdateTask implements Runnable { + long imageHandle; + ProgressHandle progressHandle; + + ProgressUpdateTask(ProgressHandle progressHandle, long imageHandle){ + this.imageHandle = imageHandle; + this.progressHandle = progressHandle; + } + + @Override + public void run() { + try { + int progress = SleuthkitJNI.getFinishImageProgress(imageHandle); + progressHandle.progress(progress); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Unexpected exception in ProgressUpdateTask", ex); //NON-NLS + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index e43c8b65c9..6162bc0061 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -46,6 +46,7 @@ + @@ -77,9 +78,11 @@ - + + + - + @@ -219,5 +222,12 @@ + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index ed53684b0c..e4f4763888 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -115,6 +115,7 @@ final class LocalDiskPanel extends JPanel { imageWriterPathLabel = new javax.swing.JLabel(); imageWriterPathDescLabel = new javax.swing.JLabel(); imageWriterErrorLabel = new javax.swing.JLabel(); + jLabel1 = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(0, 65)); setPreferredSize(new java.awt.Dimension(485, 65)); @@ -151,6 +152,8 @@ final class LocalDiskPanel extends JPanel { imageWriterErrorLabel.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(imageWriterErrorLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterErrorLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.jLabel1.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -174,7 +177,8 @@ final class LocalDiskPanel extends JPanel { .addComponent(imageWriterPathDescLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 423, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addGap(0, 0, Short.MAX_VALUE)) ); layout.setVerticalGroup( @@ -199,9 +203,11 @@ final class LocalDiskPanel extends JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(imageWriterPathDescLabel) .addComponent(imageWriterPathLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGap(5, 5, 5) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(124, Short.MAX_VALUE)) + .addContainerGap(52, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables @@ -213,6 +219,7 @@ final class LocalDiskPanel extends JPanel { private javax.swing.JLabel imageWriterErrorLabel; private javax.swing.JLabel imageWriterPathDescLabel; private javax.swing.JLabel imageWriterPathLabel; + private javax.swing.JLabel jLabel1; private javax.swing.JCheckBox noFatOrphansCheckbox; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; From 2db6808410218d23c16725bc50c7f2333d573b01 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 2 Mar 2017 12:55:08 -0500 Subject: [PATCH 06/10] Add path selection for Image Writer to Local Disk panel --- .../autopsy/casemodule/Bundle.properties | 9 +- .../autopsy/casemodule/LocalDiskPanel.form | 101 +++++----- .../autopsy/casemodule/LocalDiskPanel.java | 176 ++++++++++++------ 3 files changed, 189 insertions(+), 97 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 12e2b15179..e25d76738d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -76,7 +76,10 @@ LocalDiskPanel.timeZoneLabel.text=Please select the input timezone: LocalDiskPanel.noFatOrphansCheckbox.toolTipText= LocalDiskPanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems LocalDiskPanel.descLabel.text=(faster results, although some data will not be searched) -LocalDiskPanel.imageWriterError.text=Error - file already exists +LocalDiskPanel.imageWriterDirError.text=Error - directory does not exist +LocalDiskPanel.imageWriterEmptyPathError.text=Error - enter path for VHD +LocalDiskPanel.imageWriterIsDirError.text=Error - VHD path is a directory +LocalDiskPanel.imageWriterFileExistsError.text=Error - VHD path already exists MissingImageDialog.browseButton.text=Browse MissingImageDialog.pathNameTextField.text= AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status @@ -243,7 +246,7 @@ IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is being analyzed -LocalDiskPanel.imageWriterPathLabel.text=jLabel1 -LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the Module Output/Image Writer directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label LocalDiskPanel.jLabel1.text=Note that at least one ingest module must be run to create a complete copy +LocalDiskPanel.pathTextField.text= +LocalDiskPanel.browseButton.text=Browse diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index 6162bc0061..89835246a9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -3,10 +3,10 @@
- + - + @@ -26,31 +26,38 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - + + + + + + + + + - + @@ -75,14 +82,14 @@ - - + + - + - + - + @@ -192,20 +199,9 @@ - - - - - - - - - - - - - - + + + @@ -229,5 +225,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index e4f4763888..2572c0c61d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -32,6 +32,7 @@ import java.util.TimeZone; import java.util.concurrent.CancellationException; import java.util.logging.Level; import javax.swing.ComboBoxModel; +import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; @@ -58,8 +59,8 @@ final class LocalDiskPanel extends JPanel { private static final long serialVersionUID = 1L; private List disks; private LocalDiskModel model; - private String fullImageWriterPath = ""; private boolean enableNext = false; + private final JFileChooser fc = new JFileChooser(); /** * Creates new form LocalDiskPanel @@ -91,8 +92,10 @@ final class LocalDiskPanel extends JPanel { errorLabel.setVisible(false); errorLabel.setText(""); diskComboBox.setEnabled(false); + imageWriterErrorLabel.setOpaque(true); imageWriterErrorLabel.setText(""); - imageWriterPathLabel.setText(""); + pathTextField.setEnabled(copyImageCheckbox.isSelected()); + browseButton.setEnabled(copyImageCheckbox.isSelected()); } /** @@ -112,13 +115,13 @@ final class LocalDiskPanel extends JPanel { noFatOrphansCheckbox = new javax.swing.JCheckBox(); descLabel = new javax.swing.JLabel(); copyImageCheckbox = new javax.swing.JCheckBox(); - imageWriterPathLabel = new javax.swing.JLabel(); - imageWriterPathDescLabel = new javax.swing.JLabel(); imageWriterErrorLabel = new javax.swing.JLabel(); jLabel1 = new javax.swing.JLabel(); + pathTextField = new javax.swing.JTextField(); + browseButton = new javax.swing.JButton(); - setMinimumSize(new java.awt.Dimension(0, 65)); - setPreferredSize(new java.awt.Dimension(485, 65)); + setMinimumSize(new java.awt.Dimension(0, 420)); + setPreferredSize(new java.awt.Dimension(485, 410)); diskLabel.setFont(diskLabel.getFont().deriveFont(diskLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(diskLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.diskLabel.text")); // NOI18N @@ -143,10 +146,11 @@ final class LocalDiskPanel extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.descLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(copyImageCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.copyImageCheckbox.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathDescLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathDescLabel.text")); // NOI18N + copyImageCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + copyImageCheckboxActionPerformed(evt); + } + }); imageWriterErrorLabel.setFont(imageWriterErrorLabel.getFont().deriveFont(imageWriterErrorLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); imageWriterErrorLabel.setForeground(new java.awt.Color(255, 0, 0)); @@ -154,32 +158,53 @@ final class LocalDiskPanel extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.jLabel1.text")); // NOI18N + pathTextField.setText(org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.pathTextField.text")); // NOI18N + pathTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + pathTextFieldKeyReleased(evt); + } + public void keyTyped(java.awt.event.KeyEvent evt) { + pathTextFieldKeyTyped(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.browseButton.text")); // NOI18N + browseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(diskLabel) - .addComponent(diskComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 345, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(errorLabel) .addGroup(layout.createSequentialGroup() - .addComponent(timeZoneLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(noFatOrphansCheckbox) - .addComponent(copyImageCheckbox) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 362, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(diskLabel) + .addComponent(diskComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 345, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(errorLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(timeZoneLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(noFatOrphansCheckbox) + .addComponent(copyImageCheckbox) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(descLabel)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseButton)) .addGroup(layout.createSequentialGroup() .addGap(21, 21, 21) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(descLabel) - .addGroup(layout.createSequentialGroup() - .addComponent(imageWriterPathDescLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 423, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 423, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(imageWriterErrorLabel)))) + .addGap(0, 29, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -201,30 +226,72 @@ final class LocalDiskPanel extends JPanel { .addComponent(copyImageCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(imageWriterPathDescLabel) - .addComponent(imageWriterPathLabel)) - .addGap(5, 5, 5) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(52, Short.MAX_VALUE)) + .addContainerGap(170, Short.MAX_VALUE)) ); }// //GEN-END:initComponents + + private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + String oldText = pathTextField.getText(); + // set the current directory of the FileChooser if the ImagePath Field is valid + File currentFile = new File(oldText); + if ((currentFile.getParentFile() != null) && (currentFile.getParentFile().exists())) { + fc.setCurrentDirectory(currentFile.getParentFile()); + } + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + pathTextField.setText(path); + } + fireUpdateEvent(); + }//GEN-LAST:event_browseButtonActionPerformed + + private void copyImageCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_copyImageCheckboxActionPerformed + pathTextField.setEnabled(copyImageCheckbox.isSelected()); + browseButton.setEnabled(copyImageCheckbox.isSelected()); + fireUpdateEvent(); + }//GEN-LAST:event_copyImageCheckboxActionPerformed + + private void pathTextFieldKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_pathTextFieldKeyTyped + + }//GEN-LAST:event_pathTextFieldKeyTyped + + private void pathTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_pathTextFieldKeyReleased + fireUpdateEvent(); + }//GEN-LAST:event_pathTextFieldKeyReleased + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseButton; private javax.swing.JCheckBox copyImageCheckbox; private javax.swing.JLabel descLabel; private javax.swing.JComboBox diskComboBox; private javax.swing.JLabel diskLabel; private javax.swing.JLabel errorLabel; private javax.swing.JLabel imageWriterErrorLabel; - private javax.swing.JLabel imageWriterPathDescLabel; - private javax.swing.JLabel imageWriterPathLabel; private javax.swing.JLabel jLabel1; private javax.swing.JCheckBox noFatOrphansCheckbox; + private javax.swing.JTextField pathTextField; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; // End of variables declaration//GEN-END:variables + private void fireUpdateEvent(){ + try { + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } catch (Exception e) { + logger.log(Level.SEVERE, "LocalDiskPanel listener threw exception", e); //NON-NLS + MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr"), + NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr.msg"), + MessageNotifyUtil.MessageType.ERROR); + } + } + /** * Return the currently selected disk path. * @@ -274,17 +341,30 @@ final class LocalDiskPanel extends JPanel { String path = disk.getName().replaceAll("[:]", ""); path += " " + System.currentTimeMillis(); path += ".vhd"; - imageWriterPathLabel.setText(path); - fullImageWriterPath = Paths.get(getDefaultImageWriterFolder(), path).toString(); + pathTextField.setText(Paths.get(getDefaultImageWriterFolder(), path).toString()); } private boolean imageWriterPathIsValid(){ - - File f = new File(fullImageWriterPath); - if(f.exists()) { - imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterError.text")); + if(pathTextField.getText().isEmpty()){ + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterEmptyPathError.text")); return false; } + + File f = new File(pathTextField.getText()); + if(((f.getParentFile() != null) && (! f.getParentFile().exists())) || + (f.getParentFile() == null)) { + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterDirError.text")); + return false; + } + if(f.isDirectory()){ + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterIsDirError.text")); + return false; + } + if(f.exists()){ + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterFileExistsError.text")); + return false; + } + imageWriterErrorLabel.setText(""); return true; } @@ -294,14 +374,14 @@ final class LocalDiskPanel extends JPanel { } String getImageWriterPath(){ - return fullImageWriterPath; + return pathTextField.getText(); } /** - * Should we enable the wizard's next button? Always return true because we - * control the possible selections. + * Should we enable the wizard's next button? We control all the possible + * selections except for Image Writer. * - * @return true + * @return true if panel is valid */ public boolean validatePanel() { if(copyImageCheckbox.isSelected() && @@ -406,15 +486,7 @@ final class LocalDiskPanel extends JPanel { selected = (LocalDisk) anItem; enableNext = true; setPotentialImageWriterPath((LocalDisk) selected); - - try { - firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); - } catch (Exception e) { - logger.log(Level.SEVERE, "LocalDiskPanel listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr"), - NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr.msg"), - MessageNotifyUtil.MessageType.ERROR); - } + fireUpdateEvent(); } } From 56781ac73adca856b4f1ad1e6bb555b3a67860ca Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 3 Mar 2017 14:12:19 -0500 Subject: [PATCH 07/10] Moved ImageWriter to the datasourceprocessors package. --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 1 + .../ImageWriter.java | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) rename Core/src/org/sleuthkit/autopsy/{casemodule => datasourceprocessors}/ImageWriter.java (98%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index f0c42d2fec..52166dab6a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter; import java.awt.Cursor; import java.awt.Frame; import java.beans.PropertyChangeListener; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java rename to Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 6510c5231a..efddf47398 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.datasourceprocessors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; @@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import org.netbeans.api.progress.ProgressHandle; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Image; @@ -42,7 +43,7 @@ import org.sleuthkit.datamodel.TskCoreException; * The ImageWriter class is used to complete VHD copies created from local disks * after the ingest process completes. */ -class ImageWriter { +public class ImageWriter { private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); @@ -60,7 +61,7 @@ class ImageWriter { private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - ImageWriter(){ + public ImageWriter(){ dataSourceIdsLock = new Object(); currentTasksLock = new Object(); listenerStarted = false; @@ -187,7 +188,7 @@ class ImageWriter { * Also starts the listener if needed. * @param id The dataSource/Image ID */ - void addDataSourceId(Long id){ + public void addDataSourceId(Long id){ startListener(); synchronized(dataSourceIdsLock){ dataSourceIds.add(id); @@ -198,7 +199,7 @@ class ImageWriter { * Stop any open progress update task, finish the progress bars, and tell * the finishImage process to stop */ - void close(){ + public void close(){ synchronized(currentTasksLock){ isCancelled = true; From 457dc82dd11f6ab1b5d7fd87d9983e28b6adec27 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 6 Mar 2017 13:57:36 -0500 Subject: [PATCH 08/10] Refactor image writer --- .../autopsy/casemodule/AddImageTask.java | 5 +- .../sleuthkit/autopsy/casemodule/Case.java | 18 -- .../datasourceprocessors/ImageWriter.java | 287 +++++++++--------- 3 files changed, 150 insertions(+), 160 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index cf7db4a392..58b4ada66f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitJNI; @@ -207,7 +208,9 @@ class AddImageTask implements Runnable { errorMessages.add(verificationError); } if(! imageWriterPath.isEmpty()){ - Case.getCurrentCase().scheduleImageWriterFinish(imageId); + // The ImageWriter object registers itself as an event listener and will + // stick around after this task is complete. + ImageWriter writer = new ImageWriter(imageId); } newDataSources.add(newImage); } else { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 52166dab6a..f27bd4e4f6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -287,7 +287,6 @@ public class Case implements SleuthkitCase.ErrorObserver { private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; private static final Logger logger = Logger.getLogger(Case.class.getName()); private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); - private final ImageWriter imageWriter = new ImageWriter(); private static String appName; private static Case currentCase; private static CoordinationService.Lock currentCaseLock; @@ -1439,7 +1438,6 @@ public class Case implements SleuthkitCase.ErrorObserver { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); - oldCase.closeImageWriter(); completeCaseChange(null); //closes windows, etc if (null != oldCase.tskErrorReporter) { oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case @@ -1616,22 +1614,6 @@ public class Case implements SleuthkitCase.ErrorObserver { } return currentCaseExecutor; } - - /** - * Register the ID of an image that is being copied using ImageWriter. - * This will cause the image to be finished after ingest is complete. - * @param imageID The image ID - */ - void scheduleImageWriterFinish(long imageID){ - imageWriter.addDataSourceId(imageID); - } - - /** - * Cancel all tasks associated with Image Writer - */ - void closeImageWriter(){ - imageWriter.close(); - } /** * Gets the time zone(s) of the image data source(s) in this case. diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index efddf47398..13cfe70f0e 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datasourceprocessors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.HashSet; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; @@ -41,192 +40,198 @@ import org.sleuthkit.datamodel.TskCoreException; /** * The ImageWriter class is used to complete VHD copies created from local disks - * after the ingest process completes. + * after the ingest process completes. The AddImageTask for this data source must have included + * a non-empty imageWriterPath parameter to enable Image Writer. */ -public class ImageWriter { +public class ImageWriter implements PropertyChangeListener{ private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); - private final HashSet dataSourceIds = new HashSet<>(); - private final Object dataSourceIdsLock; // Get this lock before accessing dataSourceIds + private final Long dataSourceId; + private Long imageHandle = null; - private final HashSet> progressUpdaters = new HashSet<>(); - private final HashSet imagesBeingFinished = new HashSet<>(); - private final HashSet progressBars = new HashSet<>(); - private final HashSet> finishTasksInProgress = new HashSet<>(); + private Future finishTask; + ProgressHandle progressHandle = null; + ScheduledFuture progressUpdateTask = null; private boolean isCancelled; - private final Object currentTasksLock; // Get this lock before accessing imagesBeingFinished, progressBars, progressUpdaters, finishTasksInProgress or isCancelled + private boolean isStarted; + private boolean isFinished; + private final Object currentTasksLock; // Get this lock before accessing finishTask, progressHandle, progressUpdateTask, isCancelled, + // isStarted, or isFinished - private boolean listenerStarted; private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - public ImageWriter(){ - dataSourceIdsLock = new Object(); + private static int numFinishJobsInProgress = 0; + + public ImageWriter(Long dataSourceId){ + this.dataSourceId = dataSourceId; + currentTasksLock = new Object(); - listenerStarted = false; isCancelled = false; + isStarted = false; + isFinished = false; + progressHandle = null; + progressUpdateTask = null; + finishTask = null; doUI = RuntimeProperties.coreComponentsAreActive(); if(doUI){ - periodicTasksExecutor = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS + periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS } + + IngestManager.getInstance().addIngestJobEventListener(this); + Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); } /** - * Creates a listener on IngestJobEvents if it hasn't already been started. - * When a DataSourceAnalysisCompletedEvent arrives, if it matches - * the data source ID of an image that is using Image Writer, then finish the image - * (fill in any gaps). The AddImageTask for this data source must have included - * a non-empty imageWriterPath parameter to enable Image Writer. + * Handle the events: + * DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete + * CURRENT_CASE (case closing) - cancel the finish image process (if necessary) */ - private synchronized void startListener(){ - if(! listenerStarted){ - IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ - DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; + @Override + public void propertyChange(PropertyChangeEvent evt) { + if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ + + DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; - if(event.getDataSource() != null){ - long imageId = event.getDataSource().getId(); - String name = event.getDataSource().getName(); - - // Check whether we need to run finishImage for this data source - synchronized(dataSourceIdsLock){ - if( ! ImageWriter.this.dataSourceIds.contains(imageId)){ - // Image writer was not used on this data source or we've already finished it - return; - } else { - // Remove the imageId from the list here so we can't get past this point twice - // for the same image. Multiple DataSourceAnalysisCompletedEvent events can come from - // the same image if more ingest modules are run later, but the imageId is only added - // to the list during the intial task to add the image to the database. - ImageWriter.this.dataSourceIds.remove(imageId); - } - } - logger.log(Level.INFO, String.format("Finishing VHD image for %s", - event.getDataSource().getName())); //NON-NLS - - new Thread(() -> { - try{ - Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(imageId); - ProgressHandle progressHandle = null; - ScheduledFuture progressUpdateTask = null; - - if(doUI){ - progressHandle = ProgressHandle.createHandle("Image writer - " + name); - progressHandle.start(100); - progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( - new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); - } - - synchronized(currentTasksLock){ - ImageWriter.this.imagesBeingFinished.add(image.getImageHandle()); - - if(doUI){ - if(isCancelled){ - progressUpdateTask.cancel(true); - return; - } - ImageWriter.this.progressUpdaters.add(progressUpdateTask); - ImageWriter.this.progressBars.add(progressHandle); - } - } - - // The added complexity here with the Future is because we absolutely need to make sure - // the call to finishImageWriter returns before allowing the TSK data structures to be freed - // during case close. - Future finishTask = Executors.newSingleThreadExecutor().submit(() -> { - try{ - SleuthkitJNI.finishImageWriter(image.getImageHandle()); - } catch (TskCoreException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - }); - synchronized(currentTasksLock){ - ImageWriter.this.finishTasksInProgress.add(finishTask); - } - - // Wait for finishImageWriter to complete - try{ - // The call to get() will happen twice if the user closes the case, which is ok - finishTask.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - - synchronized(currentTasksLock){ - ImageWriter.this.finishTasksInProgress.remove(finishTask); - ImageWriter.this.imagesBeingFinished.remove(image.getImageHandle()); - - if(doUI){ - progressUpdateTask.cancel(true); - ImageWriter.this.progressUpdaters.remove(progressUpdateTask); - progressHandle.finish(); - ImageWriter.this.progressBars.remove(progressHandle); - } - } - - logger.log(Level.INFO, String.format("Finished writing VHD image for %s", event.getDataSource().getName())); //NON-NLS - } catch (TskCoreException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - }).start(); - } else { - logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS - } - } + if(event.getDataSource() != null){ + long imageId = event.getDataSource().getId(); + String name = event.getDataSource().getName(); + + // Check that the event corresponds to this datasource + if(imageId != dataSourceId){ + return; } - }); + new Thread(() -> { + startFinishImage(name); + }).start(); + + } else { + logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS + } + } + else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){ + close(); } - listenerStarted = true; } - /** - * Add a data source ID to the list of images to run finishImage on. - * Also starts the listener if needed. - * @param id The dataSource/Image ID - */ - public void addDataSourceId(Long id){ - startListener(); - synchronized(dataSourceIdsLock){ - dataSourceIds.add(id); + private void startFinishImage(String dataSourceName){ + synchronized(currentTasksLock){ + // If we've already started the finish process for this datasource, return. + // Multiple DataSourceAnalysisCompletedEvent events can come from + // the same image if more ingest modules are run later + if(isStarted){ + return; + } else { + isStarted = true; + } + } + + logger.log(Level.INFO, String.format("Finishing VHD image for %s", + dataSourceName)); //NON-NLS + + try{ + Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId); + imageHandle = image.getImageHandle(); + + synchronized(currentTasksLock){ + if(isCancelled){ + return; + } + + if(doUI){ + progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName); + progressHandle.start(100); + progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( + new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); + } + + // The added complexity here with the Future is because we absolutely need to make sure + // the call to finishImageWriter returns before allowing the TSK data structures to be freed + // during case close. + numFinishJobsInProgress++; + finishTask = Executors.newSingleThreadExecutor().submit(() -> { + try{ + SleuthkitJNI.finishImageWriter(imageHandle); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }); + } + + // Wait for finishImageWriter to complete + try{ + // The call to get() will happen twice if the user closes the case, which is ok + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + numFinishJobsInProgress--; + + IngestManager.getInstance().removeIngestJobEventListener(this); + Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); + synchronized(currentTasksLock){ + if(doUI && ! isCancelled){ + progressUpdateTask.cancel(true); + progressHandle.finish(); + } + isFinished = true; + } + + + logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS + } catch (TskCoreException | IllegalStateException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } } /** - * Stop any open progress update task, finish the progress bars, and tell - * the finishImage process to stop + * Tell the finishImage process to stop and wait for it to do so. */ - public void close(){ + private void close(){ synchronized(currentTasksLock){ isCancelled = true; - for(ScheduledFuture task:ImageWriter.this.progressUpdaters){ - task.cancel(true); + if(imageHandle == null){ + // The case got closed during ingest (before the finish process could start) + return; } - for(Long handle:imagesBeingFinished){ - SleuthkitJNI.cancelFinishImage(handle); + if(!isFinished){ + SleuthkitJNI.cancelFinishImage(imageHandle); logger.log(Level.SEVERE, "Case closed before VHD image could be finished"); //NON-NLS - } - // Wait for all the finish tasks to end - for(Future task:ImageWriter.this.finishTasksInProgress){ + // Wait for the finish task to end try{ - task.get(); + finishTask.get(); } catch (InterruptedException | ExecutionException ex){ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } - } - for(ProgressHandle progressHandle:ImageWriter.this.progressBars){ - progressHandle.finish(); + // Stop the progress bar and progress bar update task. + // The thread from startFinishImage will also stop these + // once the task completes, but we have to make absolutely sure + // this gets done before the Sleuthkit data structures are freed. + if(progressUpdateTask != null){ + progressUpdateTask.cancel(true); + } + + if(progressHandle != null){ + progressHandle.finish(); + } } } } + /** + * Get the number of images currently being finished. + * @return number of images in progress + */ + public static int numberOfJobsInProgress(){ + return numFinishJobsInProgress; + } + /** * Task to query the Sleuthkit processing to get the percentage done. */ From 507c8851c96add0db0b5ff5fafb3307ab46e267a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Mar 2017 08:32:42 -0500 Subject: [PATCH 09/10] Move event subscribers. Update to new GUI check. --- .../sleuthkit/autopsy/casemodule/AddImageTask.java | 4 ++-- .../autopsy/datasourceprocessors/ImageWriter.java | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 58b4ada66f..0e650a5f60 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter; +import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitJNI; @@ -208,9 +209,8 @@ class AddImageTask implements Runnable { errorMessages.add(verificationError); } if(! imageWriterPath.isEmpty()){ - // The ImageWriter object registers itself as an event listener and will - // stick around after this task is complete. ImageWriter writer = new ImageWriter(imageId); + writer.subscribeToEvents(); } newDataSources.add(newImage); } else { diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 13cfe70f0e..0278c0895d 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -64,6 +64,11 @@ public class ImageWriter implements PropertyChangeListener{ private static int numFinishJobsInProgress = 0; + /** + * Create the Image Writer object. + * After creation, startListeners() should be called. + * @param dataSourceId + */ public ImageWriter(Long dataSourceId){ this.dataSourceId = dataSourceId; @@ -75,11 +80,16 @@ public class ImageWriter implements PropertyChangeListener{ progressUpdateTask = null; finishTask = null; - doUI = RuntimeProperties.coreComponentsAreActive(); + doUI = RuntimeProperties.runningWithGUI(); if(doUI){ periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS } - + } + + /** + * Add this ImageWriter object as a listener to the necessary events + */ + public void subscribeToEvents(){ IngestManager.getInstance().addIngestJobEventListener(this); Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); } From 94dc472738cf5c9030af7bc59e67e9e62ddfaca0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Mar 2017 14:47:36 -0500 Subject: [PATCH 10/10] Incorporating image writer feedback --- .../datasourceprocessors/ImageWriter.java | 187 +++++++++--------- 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 0278c0895d..7201344100 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datasourceprocessors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; @@ -48,21 +49,21 @@ public class ImageWriter implements PropertyChangeListener{ private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); private final Long dataSourceId; - private Long imageHandle = null; - private Future finishTask; - ProgressHandle progressHandle = null; - ScheduledFuture progressUpdateTask = null; - private boolean isCancelled; - private boolean isStarted; - private boolean isFinished; - private final Object currentTasksLock; // Get this lock before accessing finishTask, progressHandle, progressUpdateTask, isCancelled, - // isStarted, or isFinished + private Long imageHandle = null; + private Future finishTask = null; + private ProgressHandle progressHandle = null; + private ScheduledFuture progressUpdateTask = null; + private boolean isCancelled = false; + private boolean isStarted = false; + private boolean isFinished = false; + private final Object currentTasksLock = new Object(); // Get this lock before accessing imageHandle, finishTask, progressHandle, progressUpdateTask, + // isCancelled, isStarted, or isFinished private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - private static int numFinishJobsInProgress = 0; + private static final AtomicInteger numFinishJobsInProgress = new AtomicInteger(0); /** * Create the Image Writer object. @@ -70,20 +71,8 @@ public class ImageWriter implements PropertyChangeListener{ * @param dataSourceId */ public ImageWriter(Long dataSourceId){ - this.dataSourceId = dataSourceId; - - currentTasksLock = new Object(); - isCancelled = false; - isStarted = false; - isFinished = false; - progressHandle = null; - progressUpdateTask = null; - finishTask = null; - - doUI = RuntimeProperties.runningWithGUI(); - if(doUI){ - periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS - } + this.dataSourceId = dataSourceId; + doUI = RuntimeProperties.runningWithGUI(); } /** @@ -94,6 +83,14 @@ public class ImageWriter implements PropertyChangeListener{ Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); } + /** + * Deregister this object from the events. This is ok to call multiple times. + */ + public void unsubscribeFromEvents(){ + IngestManager.getInstance().removeIngestJobEventListener(this); + Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); + } + /** * Handle the events: * DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete @@ -101,7 +98,7 @@ public class ImageWriter implements PropertyChangeListener{ */ @Override public void propertyChange(PropertyChangeEvent evt) { - if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ + if(evt instanceof DataSourceAnalysisCompletedEvent){ DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; @@ -122,12 +119,20 @@ public class ImageWriter implements PropertyChangeListener{ } } else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){ + // Technically this could be a case open event (and not the expected case close event) but + // that would probably mean something bad is going on, and we'd still want to cancel + // Image Writer. close(); } } private void startFinishImage(String dataSourceName){ + synchronized(currentTasksLock){ + if(isCancelled){ + return; + } + // If we've already started the finish process for this datasource, return. // Multiple DataSourceAnalysisCompletedEvent events can come from // the same image if more ingest modules are run later @@ -136,64 +141,63 @@ public class ImageWriter implements PropertyChangeListener{ } else { isStarted = true; } + + Image image; + try{ + image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId); + imageHandle = image.getImageHandle(); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error loading image", ex); + + // Stay subscribed to the events for now. Case close will clean everything up. + return; + } + + logger.log(Level.INFO, String.format("Finishing VHD image for %s", + dataSourceName)); //NON-NLS + + if(doUI){ + periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS + progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName); + progressHandle.start(100); + progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( + new ProgressUpdateTask(progressHandle, imageHandle), 0, 250, TimeUnit.MILLISECONDS); + } + + // The added complexity here with the Future is because we absolutely need to make sure + // the call to finishImageWriter returns before allowing the TSK data structures to be freed + // during case close. + numFinishJobsInProgress.incrementAndGet(); + finishTask = Executors.newSingleThreadExecutor().submit(() -> { + try{ + SleuthkitJNI.finishImageWriter(imageHandle); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }); } - logger.log(Level.INFO, String.format("Finishing VHD image for %s", - dataSourceName)); //NON-NLS - + // Wait for finishImageWriter to complete try{ - Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId); - imageHandle = image.getImageHandle(); - - synchronized(currentTasksLock){ - if(isCancelled){ - return; - } - - if(doUI){ - progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName); - progressHandle.start(100); - progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( - new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); - } - - // The added complexity here with the Future is because we absolutely need to make sure - // the call to finishImageWriter returns before allowing the TSK data structures to be freed - // during case close. - numFinishJobsInProgress++; - finishTask = Executors.newSingleThreadExecutor().submit(() -> { - try{ - SleuthkitJNI.finishImageWriter(imageHandle); - } catch (TskCoreException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - }); - } - - // Wait for finishImageWriter to complete - try{ - // The call to get() will happen twice if the user closes the case, which is ok - finishTask.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - numFinishJobsInProgress--; - - IngestManager.getInstance().removeIngestJobEventListener(this); - Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); - synchronized(currentTasksLock){ - if(doUI && ! isCancelled){ - progressUpdateTask.cancel(true); - progressHandle.finish(); - } - isFinished = true; - } - - - logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS - } catch (TskCoreException | IllegalStateException ex){ + // The call to get() will happen twice if the user closes the case, which is ok + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } + numFinishJobsInProgress.decrementAndGet(); + + synchronized(currentTasksLock){ + unsubscribeFromEvents(); + if(doUI){ + // Some of these may be called twice if the user closes the case + progressUpdateTask.cancel(true); + progressHandle.finish(); + periodicTasksExecutor.shutdown(); + } + isFinished = true; + } + + logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS } /** @@ -203,8 +207,12 @@ public class ImageWriter implements PropertyChangeListener{ synchronized(currentTasksLock){ isCancelled = true; + unsubscribeFromEvents(); + + // The case either got closed during ingest (before the startFinishImage got called) + // or an error occurred. If imageHandle is not null, we know that the first block of + // startFinishImage() completed so all the tasks have also been initialized. if(imageHandle == null){ - // The case got closed during ingest (before the finish process could start) return; } @@ -219,17 +227,14 @@ public class ImageWriter implements PropertyChangeListener{ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } - // Stop the progress bar and progress bar update task. - // The thread from startFinishImage will also stop these + // Stop the progress bar update task. + // The thread from startFinishImage will also stop it // once the task completes, but we have to make absolutely sure // this gets done before the Sleuthkit data structures are freed. - if(progressUpdateTask != null){ - progressUpdateTask.cancel(true); - } - - if(progressHandle != null){ - progressHandle.finish(); - } + // Since we've stopped the update task, we'll stop the associated progress + // bar now, too. + progressUpdateTask.cancel(true); + progressHandle.finish(); } } } @@ -239,15 +244,15 @@ public class ImageWriter implements PropertyChangeListener{ * @return number of images in progress */ public static int numberOfJobsInProgress(){ - return numFinishJobsInProgress; + return numFinishJobsInProgress.get(); } /** * Task to query the Sleuthkit processing to get the percentage done. */ private final class ProgressUpdateTask implements Runnable { - long imageHandle; - ProgressHandle progressHandle; + final long imageHandle; + final ProgressHandle progressHandle; ProgressUpdateTask(ProgressHandle progressHandle, long imageHandle){ this.imageHandle = imageHandle;