mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge pull request #2590 from sleuthkit/img_writer
Merge image writer into develop
This commit is contained in:
commit
5ed4490335
@ -26,6 +26,8 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback
|
|||||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
|
||||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.imagewriter.ImageWriterService;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import org.sleuthkit.datamodel.Content;
|
import org.sleuthkit.datamodel.Content;
|
||||||
import org.sleuthkit.datamodel.Image;
|
import org.sleuthkit.datamodel.Image;
|
||||||
import org.sleuthkit.datamodel.SleuthkitJNI;
|
import org.sleuthkit.datamodel.SleuthkitJNI;
|
||||||
@ -41,6 +43,7 @@ class AddImageTask implements Runnable {
|
|||||||
private final String deviceId;
|
private final String deviceId;
|
||||||
private final String imagePath;
|
private final String imagePath;
|
||||||
private final String timeZone;
|
private final String timeZone;
|
||||||
|
private final String imageWriterPath;
|
||||||
private final boolean ignoreFatOrphanFiles;
|
private final boolean ignoreFatOrphanFiles;
|
||||||
private final DataSourceProcessorProgressMonitor progressMonitor;
|
private final DataSourceProcessorProgressMonitor progressMonitor;
|
||||||
private final DataSourceProcessorCallback callback;
|
private final DataSourceProcessorCallback callback;
|
||||||
@ -74,15 +77,19 @@ class AddImageTask implements Runnable {
|
|||||||
* java.util.TimeZone.getID.
|
* java.util.TimeZone.getID.
|
||||||
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
|
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
|
||||||
* FAT filesystem.
|
* 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
|
* @param progressMonitor Progress monitor to report progress during
|
||||||
* processing.
|
* processing.
|
||||||
* @param callback Callback to call when processing is done.
|
* @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.deviceId = deviceId;
|
||||||
this.imagePath = imagePath;
|
this.imagePath = imagePath;
|
||||||
this.timeZone = timeZone;
|
this.timeZone = timeZone;
|
||||||
this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
|
this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
|
||||||
|
this.imageWriterPath = imageWriterPath;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.progressMonitor = progressMonitor;
|
this.progressMonitor = progressMonitor;
|
||||||
tskAddImageProcessLock = new Object();
|
tskAddImageProcessLock = new Object();
|
||||||
@ -101,7 +108,7 @@ class AddImageTask implements Runnable {
|
|||||||
try {
|
try {
|
||||||
currentCase.getSleuthkitCase().acquireExclusiveLock();
|
currentCase.getSleuthkitCase().acquireExclusiveLock();
|
||||||
synchronized (tskAddImageProcessLock) {
|
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));
|
Thread progressUpdateThread = new Thread(new ProgressUpdater(progressMonitor, tskAddImageProcess));
|
||||||
progressUpdateThread.start();
|
progressUpdateThread.start();
|
||||||
@ -201,6 +208,9 @@ class AddImageTask implements Runnable {
|
|||||||
if (!verificationError.isEmpty()) {
|
if (!verificationError.isEmpty()) {
|
||||||
errorMessages.add(verificationError);
|
errorMessages.add(verificationError);
|
||||||
}
|
}
|
||||||
|
if(! imageWriterPath.isEmpty()){
|
||||||
|
ImageWriterService.createImageWriter(imageId);
|
||||||
|
}
|
||||||
newDataSources.add(newImage);
|
newDataSources.add(newImage);
|
||||||
} else {
|
} else {
|
||||||
String errorMessage = String.format("Error commiting adding image %s to the case database, no object id returned", imagePath); //NON-NLS
|
String errorMessage = String.format("Error commiting adding image %s to the case database, no object id returned", imagePath); //NON-NLS
|
||||||
|
@ -66,6 +66,10 @@ LocalDiskPanel.timeZoneLabel.text=Please select the input timezone:
|
|||||||
LocalDiskPanel.noFatOrphansCheckbox.toolTipText=
|
LocalDiskPanel.noFatOrphansCheckbox.toolTipText=
|
||||||
LocalDiskPanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems
|
LocalDiskPanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems
|
||||||
LocalDiskPanel.descLabel.text=(faster results, although some data will not be searched)
|
LocalDiskPanel.descLabel.text=(faster results, although some data will not be searched)
|
||||||
|
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.browseButton.text=Browse
|
||||||
MissingImageDialog.pathNameTextField.text=
|
MissingImageDialog.pathNameTextField.text=
|
||||||
AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status
|
AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status
|
||||||
@ -222,6 +226,11 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default
|
|||||||
IngestJobInfoPanel.jLabel1.text=Ingest Modules
|
IngestJobInfoPanel.jLabel1.text=Ingest Modules
|
||||||
IngestJobInfoPanel.jLabel2.text=Ingest Jobs
|
IngestJobInfoPanel.jLabel2.text=Ingest Jobs
|
||||||
CaseInformationPanel.closeButton.text=Close
|
CaseInformationPanel.closeButton.text=Close
|
||||||
|
LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is being analyzed
|
||||||
|
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
|
||||||
CasePropertiesPanel.updateCaseNameButton.text=Update Name
|
CasePropertiesPanel.updateCaseNameButton.text=Update Name
|
||||||
CasePropertiesPanel.caseNameTextField.text=
|
CasePropertiesPanel.caseNameTextField.text=
|
||||||
CasePropertiesPanel.caseDirLabel.text=Case Directory:
|
CasePropertiesPanel.caseDirLabel.text=Case Directory:
|
||||||
|
@ -185,7 +185,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
|||||||
* @param callback Callback to call when processing is done.
|
* @param callback Callback to call when processing is done.
|
||||||
*/
|
*/
|
||||||
public void run(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
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();
|
new Thread(addImageTask).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData
|
|||||||
private String deviceId;
|
private String deviceId;
|
||||||
private String drivePath;
|
private String drivePath;
|
||||||
private String timeZone;
|
private String timeZone;
|
||||||
|
private String imageWriterPath = "";
|
||||||
private boolean ignoreFatOrphanFiles;
|
private boolean ignoreFatOrphanFiles;
|
||||||
private boolean setDataSourceOptionsCalled;
|
private boolean setDataSourceOptionsCalled;
|
||||||
|
|
||||||
@ -137,8 +138,11 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData
|
|||||||
drivePath = configPanel.getContentPaths();
|
drivePath = configPanel.getContentPaths();
|
||||||
timeZone = configPanel.getTimeZone();
|
timeZone = configPanel.getTimeZone();
|
||||||
ignoreFatOrphanFiles = configPanel.getNoFatOrphans();
|
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();
|
new Thread(addDiskTask).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +168,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData
|
|||||||
* @param callback Callback to call when processing is done.
|
* @param callback Callback to call when processing is done.
|
||||||
*/
|
*/
|
||||||
public void run(String deviceId, String drivePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
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();
|
new Thread(addDiskTask).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
<Dimension value="[0, 65]"/>
|
<Dimension value="[0, 420]"/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
<Dimension value="[485, 65]"/>
|
<Dimension value="[485, 410]"/>
|
||||||
</Property>
|
</Property>
|
||||||
</Properties>
|
</Properties>
|
||||||
<AuxValues>
|
<AuxValues>
|
||||||
@ -26,21 +26,38 @@
|
|||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Group type="102" attributes="0">
|
<Group type="102" attributes="0">
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Component id="diskLabel" min="-2" max="-2" attributes="0"/>
|
<Group type="102" alignment="0" attributes="0">
|
||||||
<Component id="diskComboBox" min="-2" pref="345" max="-2" attributes="0"/>
|
<Group type="103" groupAlignment="1" attributes="0">
|
||||||
<Component id="errorLabel" min="-2" max="-2" attributes="0"/>
|
<Component id="pathTextField" alignment="1" min="-2" pref="362" max="-2" attributes="0"/>
|
||||||
<Group type="102" attributes="0">
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Component id="timeZoneLabel" min="-2" max="-2" attributes="0"/>
|
<Component id="diskLabel" min="-2" max="-2" attributes="0"/>
|
||||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
<Component id="diskComboBox" min="-2" pref="345" max="-2" attributes="0"/>
|
||||||
<Component id="timeZoneComboBox" min="-2" pref="215" max="-2" attributes="0"/>
|
<Component id="errorLabel" min="-2" max="-2" attributes="0"/>
|
||||||
|
<Group type="102" attributes="0">
|
||||||
|
<Component id="timeZoneLabel" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||||
|
<Component id="timeZoneComboBox" min="-2" pref="215" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
<Component id="noFatOrphansCheckbox" min="-2" max="-2" attributes="0"/>
|
||||||
|
<Component id="copyImageCheckbox" min="-2" max="-2" attributes="0"/>
|
||||||
|
<Group type="102" attributes="0">
|
||||||
|
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
|
||||||
|
<Component id="descLabel" min="-2" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Component id="browseButton" min="-2" max="-2" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
<Component id="noFatOrphansCheckbox" min="-2" max="-2" attributes="0"/>
|
<Group type="102" alignment="0" attributes="0">
|
||||||
<Group type="102" attributes="0">
|
<EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
|
||||||
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
<Component id="descLabel" min="-2" max="-2" attributes="0"/>
|
<Component id="jLabel1" min="-2" pref="423" max="-2" attributes="0"/>
|
||||||
|
<Component id="imageWriterErrorLabel" min="-2" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
<EmptySpace min="0" pref="102" max="32767" attributes="0"/>
|
<EmptySpace min="0" pref="29" max="32767" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
@ -61,7 +78,18 @@
|
|||||||
<Component id="noFatOrphansCheckbox" min="-2" max="-2" attributes="0"/>
|
<Component id="noFatOrphansCheckbox" min="-2" max="-2" attributes="0"/>
|
||||||
<EmptySpace max="-2" attributes="0"/>
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
<Component id="descLabel" min="-2" max="-2" attributes="0"/>
|
<Component id="descLabel" min="-2" max="-2" attributes="0"/>
|
||||||
<EmptySpace max="32767" attributes="0"/>
|
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||||
|
<Component id="copyImageCheckbox" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Group type="103" groupAlignment="3" attributes="0">
|
||||||
|
<Component id="pathTextField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||||
|
<Component id="browseButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Component id="imageWriterErrorLabel" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace pref="170" max="32767" attributes="0"/>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
@ -165,5 +193,58 @@
|
|||||||
</Property>
|
</Property>
|
||||||
</Properties>
|
</Properties>
|
||||||
</Component>
|
</Component>
|
||||||
|
<Component class="javax.swing.JCheckBox" name="copyImageCheckbox">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="LocalDiskPanel.copyImageCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="copyImageCheckboxActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="imageWriterErrorLabel">
|
||||||
|
<Properties>
|
||||||
|
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
|
||||||
|
<FontInfo relative="true">
|
||||||
|
<Font bold="false" component="imageWriterErrorLabel" property="font" relativeSize="false" size="11"/>
|
||||||
|
</FontInfo>
|
||||||
|
</Property>
|
||||||
|
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||||
|
<Color blue="0" green="0" red="ff" type="rgb"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="LocalDiskPanel.imageWriterErrorLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="jLabel1">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="LocalDiskPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JTextField" name="pathTextField">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="LocalDiskPanel.pathTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="keyReleased" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="pathTextFieldKeyReleased"/>
|
||||||
|
<EventHandler event="keyTyped" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="pathTextFieldKeyTyped"/>
|
||||||
|
</Events>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JButton" name="browseButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="LocalDiskPanel.browseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="browseButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</Component>
|
||||||
</SubComponents>
|
</SubComponents>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -22,6 +22,8 @@ import java.awt.BorderLayout;
|
|||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -30,6 +32,7 @@ import java.util.TimeZone;
|
|||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.swing.ComboBoxModel;
|
import javax.swing.ComboBoxModel;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
@ -57,6 +60,7 @@ final class LocalDiskPanel extends JPanel {
|
|||||||
private List<LocalDisk> disks;
|
private List<LocalDisk> disks;
|
||||||
private LocalDiskModel model;
|
private LocalDiskModel model;
|
||||||
private boolean enableNext = false;
|
private boolean enableNext = false;
|
||||||
|
private final JFileChooser fc = new JFileChooser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form LocalDiskPanel
|
* Creates new form LocalDiskPanel
|
||||||
@ -88,6 +92,10 @@ final class LocalDiskPanel extends JPanel {
|
|||||||
errorLabel.setVisible(false);
|
errorLabel.setVisible(false);
|
||||||
errorLabel.setText("");
|
errorLabel.setText("");
|
||||||
diskComboBox.setEnabled(false);
|
diskComboBox.setEnabled(false);
|
||||||
|
imageWriterErrorLabel.setOpaque(true);
|
||||||
|
imageWriterErrorLabel.setText("");
|
||||||
|
pathTextField.setEnabled(copyImageCheckbox.isSelected());
|
||||||
|
browseButton.setEnabled(copyImageCheckbox.isSelected());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,9 +114,14 @@ final class LocalDiskPanel extends JPanel {
|
|||||||
timeZoneComboBox = new javax.swing.JComboBox<>();
|
timeZoneComboBox = new javax.swing.JComboBox<>();
|
||||||
noFatOrphansCheckbox = new javax.swing.JCheckBox();
|
noFatOrphansCheckbox = new javax.swing.JCheckBox();
|
||||||
descLabel = new javax.swing.JLabel();
|
descLabel = new javax.swing.JLabel();
|
||||||
|
copyImageCheckbox = new javax.swing.JCheckBox();
|
||||||
|
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));
|
setMinimumSize(new java.awt.Dimension(0, 420));
|
||||||
setPreferredSize(new java.awt.Dimension(485, 65));
|
setPreferredSize(new java.awt.Dimension(485, 410));
|
||||||
|
|
||||||
diskLabel.setFont(diskLabel.getFont().deriveFont(diskLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
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
|
org.openide.awt.Mnemonics.setLocalizedText(diskLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.diskLabel.text")); // NOI18N
|
||||||
@ -132,24 +145,66 @@ final class LocalDiskPanel extends JPanel {
|
|||||||
descLabel.setFont(descLabel.getFont().deriveFont(descLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
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(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
|
||||||
|
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));
|
||||||
|
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
|
||||||
|
|
||||||
|
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);
|
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||||
this.setLayout(layout);
|
this.setLayout(layout);
|
||||||
layout.setHorizontalGroup(
|
layout.setHorizontalGroup(
|
||||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
.addGroup(layout.createSequentialGroup()
|
.addGroup(layout.createSequentialGroup()
|
||||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
.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()
|
.addGroup(layout.createSequentialGroup()
|
||||||
.addComponent(timeZoneLabel)
|
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
.addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 362, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE))
|
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
.addComponent(noFatOrphansCheckbox)
|
.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()
|
.addGroup(layout.createSequentialGroup()
|
||||||
.addGap(21, 21, 21)
|
.addGap(21, 21, 21)
|
||||||
.addComponent(descLabel)))
|
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
.addGap(0, 102, 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.setVerticalGroup(
|
||||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
@ -167,19 +222,76 @@ final class LocalDiskPanel extends JPanel {
|
|||||||
.addComponent(noFatOrphansCheckbox)
|
.addComponent(noFatOrphansCheckbox)
|
||||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
.addComponent(descLabel)
|
.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(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.RELATED)
|
||||||
|
.addComponent(imageWriterErrorLabel)
|
||||||
|
.addContainerGap(170, Short.MAX_VALUE))
|
||||||
);
|
);
|
||||||
}// </editor-fold>//GEN-END:initComponents
|
}// </editor-fold>//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
|
// 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.JLabel descLabel;
|
||||||
private javax.swing.JComboBox<LocalDisk> diskComboBox;
|
private javax.swing.JComboBox<LocalDisk> diskComboBox;
|
||||||
private javax.swing.JLabel diskLabel;
|
private javax.swing.JLabel diskLabel;
|
||||||
private javax.swing.JLabel errorLabel;
|
private javax.swing.JLabel errorLabel;
|
||||||
|
private javax.swing.JLabel imageWriterErrorLabel;
|
||||||
|
private javax.swing.JLabel jLabel1;
|
||||||
private javax.swing.JCheckBox noFatOrphansCheckbox;
|
private javax.swing.JCheckBox noFatOrphansCheckbox;
|
||||||
|
private javax.swing.JTextField pathTextField;
|
||||||
private javax.swing.JComboBox<String> timeZoneComboBox;
|
private javax.swing.JComboBox<String> timeZoneComboBox;
|
||||||
private javax.swing.JLabel timeZoneLabel;
|
private javax.swing.JLabel timeZoneLabel;
|
||||||
// End of variables declaration//GEN-END:variables
|
// 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.
|
* Return the currently selected disk path.
|
||||||
*
|
*
|
||||||
@ -214,14 +326,69 @@ final class LocalDiskPanel extends JPanel {
|
|||||||
boolean getNoFatOrphans() {
|
boolean getNoFatOrphans() {
|
||||||
return noFatOrphansCheckbox.isSelected();
|
return noFatOrphansCheckbox.isSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getDefaultImageWriterFolder(){
|
||||||
|
return Paths.get(Case.getCurrentCase().getModuleDirectory(), "Image Writer").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPotentialImageWriterPath(LocalDisk disk){
|
||||||
|
|
||||||
|
File subDirectory = Paths.get(getDefaultImageWriterFolder()).toFile();
|
||||||
|
if (!subDirectory.exists()) {
|
||||||
|
subDirectory.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = disk.getName().replaceAll("[:]", "");
|
||||||
|
path += " " + System.currentTimeMillis();
|
||||||
|
path += ".vhd";
|
||||||
|
pathTextField.setText(Paths.get(getDefaultImageWriterFolder(), path).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean imageWriterPathIsValid(){
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean getImageWriterEnabled(){
|
||||||
|
return copyImageCheckbox.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getImageWriterPath(){
|
||||||
|
return pathTextField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should we enable the wizard's next button? Always return true because we
|
* Should we enable the wizard's next button? We control all the possible
|
||||||
* control the possible selections.
|
* selections except for Image Writer.
|
||||||
*
|
*
|
||||||
* @return true
|
* @return true if panel is valid
|
||||||
*/
|
*/
|
||||||
public boolean validatePanel() {
|
public boolean validatePanel() {
|
||||||
|
if(copyImageCheckbox.isSelected() &&
|
||||||
|
! imageWriterPathIsValid()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return enableNext;
|
return enableNext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,15 +485,8 @@ final class LocalDiskPanel extends JPanel {
|
|||||||
if (ready) {
|
if (ready) {
|
||||||
selected = (LocalDisk) anItem;
|
selected = (LocalDisk) anItem;
|
||||||
enableNext = true;
|
enableNext = true;
|
||||||
|
setPotentialImageWriterPath((LocalDisk) selected);
|
||||||
try {
|
fireUpdateEvent();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,3 +10,4 @@ RawDSInputPanel.jBreakFileUpLabel.text=Break image up into:
|
|||||||
RawDSInputPanel.jNoBreakupRadioButton.text=Do not break up
|
RawDSInputPanel.jNoBreakupRadioButton.text=Do not break up
|
||||||
RawDSInputPanel.j2GBBreakupRadioButton.text=2GB chunks
|
RawDSInputPanel.j2GBBreakupRadioButton.text=2GB chunks
|
||||||
RawDSInputPanel.timeZoneLabel.text=Please select the input timezone:
|
RawDSInputPanel.timeZoneLabel.text=Please select the input timezone:
|
||||||
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
# To change this license header, choose License Headers in Project Properties.
|
||||||
|
# To change this template file, choose Tools | Templates
|
||||||
|
# and open the template in the editor.
|
||||||
|
|
||||||
|
ImageWriterService.serviceName=Image Writer
|
||||||
|
ImageWriterService.waitingForVHDs=Waiting for VHD(s) to complete
|
||||||
|
ImageWriterService.shouldWait=Wait for VHD(s) in progress to complete?
|
||||||
|
ImageWriterService.localDisk=Local disk image copy
|
288
Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java
Normal file
288
Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> 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.imagewriter;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
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.casemodule.Case;
|
||||||
|
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. The AddImageTask for this data source must have included
|
||||||
|
* a non-empty imageWriterPath parameter to enable Image Writer.
|
||||||
|
*
|
||||||
|
* Most of the cancellation/cleanup is handled through ImageWriterService
|
||||||
|
*/
|
||||||
|
class ImageWriter implements PropertyChangeListener{
|
||||||
|
|
||||||
|
private final Logger logger = Logger.getLogger(ImageWriter.class.getName());
|
||||||
|
|
||||||
|
private final Long dataSourceId;
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the Image Writer object.
|
||||||
|
* After creation, startListeners() should be called.
|
||||||
|
* @param dataSourceId
|
||||||
|
*/
|
||||||
|
ImageWriter(Long dataSourceId){
|
||||||
|
this.dataSourceId = dataSourceId;
|
||||||
|
doUI = RuntimeProperties.runningWithGUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add this ImageWriter object as a listener to the necessary events
|
||||||
|
*/
|
||||||
|
void subscribeToEvents(){
|
||||||
|
IngestManager.getInstance().addIngestJobEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregister this object from the events. This is ok to call multiple times.
|
||||||
|
*/
|
||||||
|
void unsubscribeFromEvents(){
|
||||||
|
IngestManager.getInstance().removeIngestJobEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the events:
|
||||||
|
* DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
if(evt instanceof DataSourceAnalysisCompletedEvent){
|
||||||
|
|
||||||
|
DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if(isStarted){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image image;
|
||||||
|
try{
|
||||||
|
image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId);
|
||||||
|
imageHandle = image.getImageHandle();
|
||||||
|
} catch (IllegalStateException ex){
|
||||||
|
// This exception means that getCurrentCase() failed because no case was open.
|
||||||
|
// This can happen when the user closes the case while ingest is ongoing - canceling
|
||||||
|
// ingest fires off the DataSourceAnalysisCompletedEvent while the case is in the
|
||||||
|
// process of closing.
|
||||||
|
logger.log(Level.WARNING, String.format("Case closed before ImageWriter could start the finishing process for %s",
|
||||||
|
dataSourceName));
|
||||||
|
return;
|
||||||
|
} catch (TskCoreException ex){
|
||||||
|
logger.log(Level.SEVERE, "Error loading image", ex);
|
||||||
|
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.
|
||||||
|
finishTask = Executors.newSingleThreadExecutor().submit(() -> {
|
||||||
|
try{
|
||||||
|
SleuthkitJNI.finishImageWriter(imageHandle);
|
||||||
|
} catch (TskCoreException ex){
|
||||||
|
logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setting this means that finishTask and all the UI updaters are initialized (if running UI)
|
||||||
|
isStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for finishImageWriter to complete
|
||||||
|
try{
|
||||||
|
// The call to get() can happen multiple times 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){
|
||||||
|
if(doUI){
|
||||||
|
// Some of these may be called twice if the user closes the case
|
||||||
|
progressUpdateTask.cancel(true);
|
||||||
|
progressHandle.finish();
|
||||||
|
periodicTasksExecutor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a task hasn't been started yet, set the cancel flag so it can no longer
|
||||||
|
* start.
|
||||||
|
* This is intended to be used in case close so a job doesn't suddenly start
|
||||||
|
* up during cleanup.
|
||||||
|
*/
|
||||||
|
void cancelIfNotStarted(){
|
||||||
|
synchronized(currentTasksLock){
|
||||||
|
if(! isStarted){
|
||||||
|
isCancelled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the finishTask process is running.
|
||||||
|
* @return true if the finish task is still going on, false if it is finished or
|
||||||
|
* never started
|
||||||
|
*/
|
||||||
|
boolean jobIsInProgress(){
|
||||||
|
synchronized(currentTasksLock){
|
||||||
|
return((isStarted) && (! finishTask.isDone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a single job.
|
||||||
|
* Does not wait for the job to complete. Safe to call with Image Writer in any state.
|
||||||
|
*/
|
||||||
|
void cancelJob(){
|
||||||
|
synchronized(currentTasksLock){
|
||||||
|
// All of the following is redundant but safe to call on a complete job
|
||||||
|
isCancelled = true;
|
||||||
|
|
||||||
|
if(isStarted){
|
||||||
|
SleuthkitJNI.cancelFinishImage(imageHandle);
|
||||||
|
|
||||||
|
// Stop the progress bar update task.
|
||||||
|
// The thread from startFinishImage will also stop it
|
||||||
|
// once the task completes, but we don't have a guarantee on
|
||||||
|
// when that happens.
|
||||||
|
// Since we've stopped the update task, we'll stop the associated progress
|
||||||
|
// bar now, too.
|
||||||
|
if(doUI){
|
||||||
|
progressUpdateTask.cancel(true);
|
||||||
|
progressHandle.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks while all finishImage tasks complete.
|
||||||
|
* Also makes sure the progressUpdateTask is canceled.
|
||||||
|
*/
|
||||||
|
void waitForJobToFinish(){
|
||||||
|
synchronized(currentTasksLock){
|
||||||
|
// Wait for the finish task to end
|
||||||
|
if(isStarted){
|
||||||
|
try{
|
||||||
|
finishTask.get();
|
||||||
|
} catch (InterruptedException | ExecutionException ex){
|
||||||
|
Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
if(doUI){
|
||||||
|
progressUpdateTask.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task to query the Sleuthkit processing to get the percentage done.
|
||||||
|
*/
|
||||||
|
private final class ProgressUpdateTask implements Runnable {
|
||||||
|
final long imageHandle;
|
||||||
|
final 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> 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.imagewriter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.openide.DialogDescriptor;
|
||||||
|
import org.openide.DialogDisplayer;
|
||||||
|
import org.openide.NotifyDescriptor;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.openide.util.lookup.ServiceProvider;
|
||||||
|
import org.openide.util.lookup.ServiceProviders;
|
||||||
|
import org.sleuthkit.autopsy.framework.AutopsyService;
|
||||||
|
|
||||||
|
@ServiceProviders(value = {@ServiceProvider(service = AutopsyService.class)})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and handles closing of ImageWriter objects.
|
||||||
|
* Currently, ImageWriter is only enabled for local disks, and local disks can
|
||||||
|
* not be processed in multi user mode. If ImageWriter is ever enabled for multi user
|
||||||
|
* cases this code will need to be revised.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ImageWriterService implements AutopsyService {
|
||||||
|
|
||||||
|
private static final Set<ImageWriter> imageWriters = new HashSet<>(); // Contains all Image Writer objects
|
||||||
|
private static final Object imageWritersLock = new Object(); // Get this lock before accessing currentImageWriters
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an image writer object for the given data source ID.
|
||||||
|
* @param imageId ID for the image
|
||||||
|
*/
|
||||||
|
public static void createImageWriter(Long imageId){
|
||||||
|
|
||||||
|
// ImageWriter objects are created during the addImageTask. They can not arrive while
|
||||||
|
// we're closing case resources so we don't need to worry about one showing up while
|
||||||
|
// doing our close/cleanup.
|
||||||
|
synchronized(imageWritersLock){
|
||||||
|
ImageWriter writer = new ImageWriter(imageId);
|
||||||
|
writer.subscribeToEvents();
|
||||||
|
imageWriters.add(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceName() {
|
||||||
|
return NbBundle.getMessage(this.getClass(), "ImageWriterService.serviceName");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeCaseResources(CaseContext context) throws AutopsyServiceException {
|
||||||
|
context.getProgressIndicator().progress(NbBundle.getMessage(this.getClass(), "ImageWriterService.waitingForVHDs"));
|
||||||
|
|
||||||
|
synchronized(imageWritersLock){
|
||||||
|
// If any of our ImageWriter objects haven't started the finish task, set the cancel flag
|
||||||
|
// to make sure they don't start now. The reason they haven't started is that
|
||||||
|
// ingest was not complete, and the user already confirmed that they want to exit
|
||||||
|
// even though ingest is not complete so we will take that to mean that they
|
||||||
|
// also don't want to wait for Image Writer.
|
||||||
|
for(ImageWriter writer: imageWriters){
|
||||||
|
writer.cancelIfNotStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test whether any finishImage tasks are in progress
|
||||||
|
boolean jobsAreInProgress = false;
|
||||||
|
for(ImageWriter writer: imageWriters){
|
||||||
|
if(writer.jobIsInProgress()){
|
||||||
|
jobsAreInProgress = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(jobsAreInProgress){
|
||||||
|
// If jobs are in progress, ask the user if they want to wait for them to complete
|
||||||
|
NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
|
||||||
|
NbBundle.getMessage(this.getClass(), "ImageWriterService.shouldWait"),
|
||||||
|
NbBundle.getMessage(this.getClass(), "ImageWriterService.localDisk"),
|
||||||
|
NotifyDescriptor.YES_NO_OPTION,
|
||||||
|
NotifyDescriptor.WARNING_MESSAGE);
|
||||||
|
descriptor.setValue(NotifyDescriptor.NO_OPTION);
|
||||||
|
Object response = DialogDisplayer.getDefault().notify(descriptor);
|
||||||
|
|
||||||
|
if(response == DialogDescriptor.NO_OPTION){
|
||||||
|
// Cancel all the jobs
|
||||||
|
for(ImageWriter writer: imageWriters){
|
||||||
|
writer.cancelJob();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all finishImage jobs to complete. If the jobs got cancelled
|
||||||
|
// this will be very fast.
|
||||||
|
for(ImageWriter writer: imageWriters){
|
||||||
|
writer.waitForJobToFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop listening for events
|
||||||
|
for(ImageWriter writer: imageWriters){
|
||||||
|
writer.unsubscribeFromEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out the list of Image Writers
|
||||||
|
imageWriters.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user