fixed some things, and rolled back a lot of comments.

This commit is contained in:
Sean Moss 2013-03-26 10:59:39 -04:00
parent 4909cbbd6e
commit 661016cb52
29 changed files with 1349 additions and 901 deletions

18
.gitignore vendored
View File

@ -46,13 +46,13 @@ genfiles.properties
/branding/nbproject/*
!/branding/nbproject/project.xml
!/branding/nbproject/project.properties
/Testing/script/input/
/Testing/script/output/
/Testing/script/gold/
/Testing/script/ScriptLog.txt
/Testing/build/
/Testing/dist/
/Testing/nbproject/*
/Test/input/
/Test/input/*
/Test/output/*
/Test/script/ScriptLog.txt
/Test/build/
/Test/dist/
/Test/nbproject/*
!/Testing/nbproject/project.xml
!/Testing/nbproject/project.properties
*~
@ -64,5 +64,5 @@ genfiles.properties
hs_err_pid*.log
Core/src/org/sleuthkit/autopsy/casemodule/docs/QuickStart.html
Core/src/org/sleuthkit/autopsy/casemodule/docs/screenshot.png
/Testing/script/myconfig.xml
/Testing/script/*/*.xml
/Test/script/myconfig.xml
/Test/script/*/*.xml

View File

@ -1,4 +1,4 @@
Last Updated: Sept 28, 2012
Last Updated: March 23, 2013
This file outlines what it takes to build Autopsy from source.
@ -14,6 +14,11 @@ STEPS:
1a) Download and install 32-bit version of JDK version 1.7 (32-bit is currently
needed even if you have a 64-bit system).
Autopsy has been used and tested with Oracle JavaSE and the included JavaFX support
(http://www.oracle.com/technetwork/java/javase/downloads/index.html).
OpenJDK and OpenJFX might work, but they are not fully tested with Autopsy.
1b) Ensure that JDK_HOME is set to the root JDK directory.
1c) (optional) Download and install Netbeans IDE (http://netbeans.org/)
@ -28,7 +33,7 @@ need to set JRE_HOME to the root JRE directory.
2) Get Sleuth Kit Setup
2a) Download and build the release version of Libewf2 (20120304 or
2a) Download and build the release version of Libewf2 (20130119 or
later). All you need is the dll file. Note that you will get a
launching error if you use libewf 1.
- http://sourceforge.net/projects/libewf/

View File

@ -116,6 +116,8 @@ public class Case {
private static Case currentCase = null;
private Services services;
private static final Logger logger = Logger.getLogger(Case.class.getName());
static final String CASE_EXTENSION = "aut";
static final String CASE_DOT_EXTENSION = "." + CASE_EXTENSION;
/**
* Constructor for the Case class
@ -202,7 +204,7 @@ public class Case {
static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException {
logger.log(Level.INFO, "Creating new case.\ncaseDir: {0}\ncaseName: {1}", new Object[]{caseDir, caseName});
String configFilePath = caseDir + File.separator + caseName + ".aut";
String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION;
XMLCaseManagement xmlcm = new XMLCaseManagement();
xmlcm.create(caseDir, caseName, examiner, caseNumber); // create a new XML config file
@ -260,7 +262,12 @@ public class Case {
// close the previous case if there's any
CaseCloseAction closeCase = SystemAction.get(CaseCloseAction.class);
closeCase.actionPerformed(null);
throw new CaseActionException("Error opening the case", ex);
if (!configFilePath.endsWith(CASE_DOT_EXTENSION)) {
throw new CaseActionException("Check that you selected the correct case file (usually with "
+ CASE_DOT_EXTENSION + " extension)", ex);
} else {
throw new CaseActionException("Error opening the case", ex);
}
}
}
@ -562,7 +569,6 @@ public class Case {
}
}
/**
* Get absolute module output directory path where modules should save their
* permanent data The directory is a subdirectory of this case dir.
@ -916,7 +922,7 @@ public class Case {
Frame f = WindowManager.getDefault().getMainWindow();
f.setTitle(Case.getAppName()); // set the window name to just application name
//try to force gc to happen
System.gc();
System.gc();
@ -924,7 +930,7 @@ public class Case {
//log memory usage after case changed
logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo());
}

View File

@ -16,7 +16,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Component;
@ -27,6 +26,8 @@ import java.util.Collections;
import java.util.logging.Level;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -36,33 +37,35 @@ import org.sleuthkit.autopsy.coreutils.Logger;
*/
@ServiceProvider(service = CaseOpenAction.class)
public final class CaseOpenAction implements ActionListener {
private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName());
private static final String PROP_BASECASE = "LBL_BaseCase_PATH";
private final JFileChooser fc = new JFileChooser();
private FileFilter autFilter;
JFileChooser fc = new JFileChooser();
GeneralFilter autFilter = new GeneralFilter(Collections.<String>singletonList(".aut"), "AUTOPSY File (*.aut)");
/** The constructor */
/**
* The constructor
*/
public CaseOpenAction() {
autFilter = new FileNameExtensionFilter(org.sleuthkit.autopsy.coreutils.Version.getName()
+ " Case File ( " + Case.CASE_DOT_EXTENSION + ")",
Case.CASE_EXTENSION);
fc.setDragEnabled(false);
fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
fc.setMultiSelectionEnabled(false);
//fc.setAcceptAllFileFilterUsed(false);
fc.addChoosableFileFilter(autFilter);
//fc.addChoosableFileFilter(fc.getAcceptAllFileFilter());
try{
if(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE) != null)
fc.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE)));
}
catch(Exception e){
fc.setFileFilter(autFilter);
try {
if (ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE) != null) {
fc.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE)));
}
} catch (Exception e) {
}
}
/**
* Pop-up the File Chooser to open the existing case (.aut file)
*
* @param e the action event
*
* @param e the action event
*/
@Override
public void actionPerformed(ActionEvent e) {
@ -70,6 +73,7 @@ public final class CaseOpenAction implements ActionListener {
int retval = fc.showOpenDialog((Component) e.getSource());
if (retval == JFileChooser.APPROVE_OPTION) {
String path = fc.getSelectedFile().getPath();
String dirPath = fc.getSelectedFile().getParent();
@ -89,11 +93,10 @@ public final class CaseOpenAction implements ActionListener {
try {
Case.open(path); // open the case
} catch (CaseActionException ex) {
JOptionPane.showMessageDialog(null, "Error: could not open the case in folder " + path
+ " due to unexpected error"
, "Error", JOptionPane.ERROR_MESSAGE);
JOptionPane.showMessageDialog(null, "Error: could not open the case in folder " + path
+ ": " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
logger.log(Level.WARNING, "Error opening case in folder " + path, ex);
StartupWindow.getInstance().open();
}
}

View File

@ -28,10 +28,11 @@ import javax.swing.filechooser.FileFilter;
*/
public class GeneralFilter extends FileFilter{
List<String> extensions;
String desc;
private List<String> extensions;
private String desc;
public GeneralFilter(List<String> ext, String desc){
super();
this.extensions = ext;
this.desc = desc;
}

View File

@ -179,7 +179,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener{
//fc.setSelectedFile(new File("C:\\Program Files\\"));
//disableTextField(fc); // disable all the text field on the file chooser
int returnValue = fc.showSaveDialog((Component)evt.getSource());
int returnValue = fc.showDialog((Component)evt.getSource(), "Select");
if(returnValue == JFileChooser.APPROVE_OPTION){
String path = fc.getSelectedFile().getPath();
caseParentDirTextField.setText(path); // put the path to the textfield

View File

@ -101,6 +101,14 @@ public class Installer extends ModuleInstall {
}
/**
* Check if JavaFx initialized
* @return false if java fx not initialized (classes coult not load), true if initialized
*/
public boolean isJavaFxInited() {
return this.javaFxInit;
}
private void initJavaFx() {
//initialize java fx if exists
try {
@ -118,7 +126,7 @@ public class Installer extends ModuleInstall {
final String details = " Some features will not be available. "
+ " Check that you have the right JRE installed (Sun JRE > 1.7.10). ";
logger.log(Level.SEVERE, msg
+ details);
+ details, e);
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
@Override

View File

@ -76,8 +76,6 @@ DataContentViewerArtifact.selectAllMenuItem.text=Select All
DataContentViewerArtifact.pageLabel.text=Result:
AdvancedConfigurationDialog.applyButton.text=OK
DataContentViewerMedia.pauseButton.text=\u25ba
DataContentViewerMedia.progressLabel.text=
DataContentViewerString.goToPageLabel.text=Go to Page:
DataContentViewerString.goToPageTextField.text=
DataContentViewerHex.goToPageTextField.text=
@ -104,3 +102,7 @@ AdvancedConfigurationDialog.cancelButton.text=Cancel
DataResultPanel.directoryTablePath.text=directoryPath
DataResultPanel.numberMatchLabel.text=0
DataResultPanel.matchLabel.text=Results
MediaViewVideoPanel.pauseButton.text=\u25ba
MediaViewVideoPanel.progressLabel.text=00:00
DataContentViewerMedia.AccessibleContext.accessibleDescription=
MediaViewVideoPanel.infoLabel.text=info

View File

@ -11,25 +11,10 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="dataContentPanel" alignment="0" pref="585" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="dataContentPanel" alignment="0" pref="435" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="1"/>
</Layout>
<SubComponents>
<Component class="org.sleuthkit.autopsy.corecomponents.DataContentPanel" name="dataContentPanel">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new DataContentPanel(true);"/>
</AuxValues>
</Component>
</SubComponents>
</Form>

View File

@ -47,6 +47,8 @@ public final class DataContentTopComponent extends TopComponent implements DataC
private static DataContentTopComponent defaultInstance;
// set to true if this is the TC that always stays open and is the default place to display content
private boolean isDefault;
// the content panel holding tabs with content viewers
private final DataContentPanel dataContentPanel;
// contains a list of the undocked TCs
private static ArrayList<DataContentTopComponent> newWindowList = new ArrayList<DataContentTopComponent>();
@ -60,6 +62,10 @@ public final class DataContentTopComponent extends TopComponent implements DataC
setToolTipText(TOOLTIP_TEXT);
this.isDefault = isDefault;
dataContentPanel = new DataContentPanel(isDefault);
add(dataContentPanel);
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.valueOf(isDefault)); // prevent option to close compoment in GUI
logger.log(Level.INFO, "Created DataContentTopComponent instance: " + this);
}
@ -90,21 +96,9 @@ public final class DataContentTopComponent extends TopComponent implements DataC
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
dataContentPanel = new DataContentPanel(true);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(dataContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 585, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(dataContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 435, Short.MAX_VALUE)
);
setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.corecomponents.DataContentPanel dataContentPanel;
// End of variables declaration//GEN-END:variables
/**

View File

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleDescription" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerMedia.AccessibleContext.accessibleDescription" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</AccessibilityProperties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -13,80 +18,5 @@
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="videoPanel" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="progressSlider" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="progressLabel" min="-2" pref="104" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="videoPanel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" max="-2" attributes="0">
<Component id="pauseButton" max="32767" attributes="0"/>
<Component id="progressSlider" max="-2" attributes="0"/>
<Component id="progressLabel" alignment="1" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="pauseButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerMedia.pauseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[45, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[45, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[45, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pauseButtonActionPerformed"/>
</Events>
</Component>
<Container class="javax.swing.JPanel" name="videoPanel">
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="242" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Container>
<Component class="javax.swing.JSlider" name="progressSlider">
<Properties>
<Property name="value" type="int" value="0"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="progressLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerMedia.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
</Form>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 Basis Technology Corp.
* Copyright 2011-2013 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,95 +18,62 @@
*/
package org.sleuthkit.autopsy.corecomponents;
import com.sun.javafx.application.PlatformImpl;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javax.imageio.ImageIO;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.BoxLayout;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.gstreamer.*;
import org.gstreamer.elements.PlayBin2;
import org.gstreamer.elements.RGBDataSink;
import org.gstreamer.swing.VideoComponent;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.nodes.Node;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
/**
* Media content viewer for videos, sounds and images. Using gstreamer.
* Media content viewer for videos, sounds and images.
*/
@ServiceProviders(value = {
@ServiceProvider(service = DataContentViewer.class, position = 5),
@ServiceProvider(service = FrameCapture.class)
@ServiceProvider(service = DataContentViewer.class, position = 5)
})
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer, FrameCapture {
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
private String[] IMAGES; // use javafx supported
private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg"};
private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"};
private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"};
private static final int NUM_FRAMES = 12;
private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
private VideoComponent videoComponent;
private PlayBin2 playbin2;
private AbstractFile currentFile;
private long durationMillis = 0;
private boolean autoTracking = false; // true if the slider is moving automatically
private final Object playbinLock = new Object(); // lock for synchronization of playbin2 player
private VideoProgressWorker videoProgressWorker;
private int totalHours, totalMinutes, totalSeconds;
private BufferedImage currentImage = null;
private boolean gstInited = false;
private AbstractFile lastFile;
private boolean inImageMode; //keeps track if already in image mode to minimize UI setup
//UI
private final MediaViewVideoPanel videoPanel;
private final MediaViewImagePanel imagePanel;
private boolean videoPanelInited;
private boolean imagePanelInited;
private static final String IMAGE_VIEWER_LAYER = "IMAGE";
private static final String VIDEO_VIEWER_LAYER = "VIDEO";
/**
* Creates new form DataContentViewerVideo
*/
public DataContentViewerMedia() {
initComponents();
videoPanel = new MediaViewVideoPanel();
imagePanel = new MediaViewImagePanel();
videoPanelInited = videoPanel.isInited();
imagePanelInited = imagePanel.isInited();
customizeComponents();
logger.log(Level.INFO, "Created MediaView instance: " + this);
}
private void customizeComponents() {
inImageMode = false;
logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
//initialize supported image types
//TODO use mime-types instead once we have support
@ -118,47 +85,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
IMAGES[i] = "." + suffix;
}
try {
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing");
Gst.init();
gstInited = true;
} catch (GstException e) {
gstInited = false;
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e);
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. "
+ " Video and audio viewing will be disabled. ",
e.getMessage());
return;
} catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
gstInited = false;
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e);
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. "
+ " Video and audio viewing will be disabled. ",
e.getMessage());
return;
}
add(imagePanel, IMAGE_VIEWER_LAYER);
add(videoPanel, VIDEO_VIEWER_LAYER);
switchPanels(false);
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
progressSlider.addChangeListener(new ChangeListener() {
/**
* Should always try to synchronize any call to
* progressSlider.setValue() to avoid a different thread changing
* playbin while stateChanged() is processing
*/
@Override
public void stateChanged(ChangeEvent e) {
int time = progressSlider.getValue();
synchronized (playbinLock) {
if (playbin2 != null && !autoTracking) {
State orig = playbin2.getState();
playbin2.pause();
playbin2.seek(ClockTime.fromMillis(time));
playbin2.setState(orig);
}
}
}
});
}
/**
@ -170,90 +101,17 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
pauseButton = new javax.swing.JButton();
videoPanel = new javax.swing.JPanel();
progressSlider = new javax.swing.JSlider();
progressLabel = new javax.swing.JLabel();
pauseButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.pauseButton.text")); // NOI18N
pauseButton.setMaximumSize(new java.awt.Dimension(45, 23));
pauseButton.setMinimumSize(new java.awt.Dimension(45, 23));
pauseButton.setPreferredSize(new java.awt.Dimension(45, 23));
pauseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pauseButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
videoPanel.setLayout(videoPanelLayout);
videoPanelLayout.setHorizontalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
videoPanelLayout.setVerticalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 242, Short.MAX_VALUE)
);
progressSlider.setValue(0);
progressLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
progressLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.progressLabel.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(pauseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 104, javax.swing.GroupLayout.PREFERRED_SIZE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(pauseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
);
setLayout(new java.awt.CardLayout());
getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.AccessibleContext.accessibleDescription")); // NOI18N
}// </editor-fold>//GEN-END:initComponents
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
synchronized (playbinLock) {
State state = playbin2.getState();
if (state.equals(State.PLAYING)) {
playbin2.pause();
pauseButton.setText("");
playbin2.setState(State.PAUSED);
} else if (state.equals(State.PAUSED)) {
playbin2.play();
pauseButton.setText("||");
playbin2.setState(State.PLAYING);
} else if (state.equals(State.READY)) {
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute();
em.getExtractedBytes();
}
}
}//GEN-LAST:event_pauseButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton pauseButton;
private javax.swing.JLabel progressLabel;
private javax.swing.JSlider progressSlider;
private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables
@Override
public void setNode(Node selectedNode) {
if (selectedNode == null) {
videoPanel.reset();
return;
}
@ -267,214 +125,34 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
} else {
lastFile = file;
}
reset();
setComponentsVisibility(false);
// get rid of any existing videoProgressWorker thread
if (videoProgressWorker != null) {
videoProgressWorker.cancel(true);
videoProgressWorker = null;
}
currentFile = file;
if (containsExt(file.getName(), IMAGES)) {
showImageFx(file);
} else if (gstInited
videoPanel.reset();
final Dimension dims = DataContentViewerMedia.this.getSize();
if (imagePanelInited && containsExt(file.getName(), IMAGES)) {
imagePanel.showImageFx(file, dims);
this.switchPanels(false);
} else if (videoPanelInited
&& (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) {
inImageMode = false;
setupVideo(file);
videoPanel.setupVideo(file, dims);
this.switchPanels(true);
}
}
/**
* Initialize vars and display the image on the panel.
* switch to visible video or image panel
*
* @param file
* @param showVideo true if video panel, false if image panel
*/
private void showImageFx(final AbstractFile file) {
final String fileName = file.getName();
// load the image
PlatformImpl.runLater(new Runnable() {
@Override
public void run() {
if (!Case.isCaseOpen()) {
//handle in-between condition when case is being closed
//and an image was previously selected
return;
}
Dimension dims = DataContentViewerMedia.this.getSize();
final InputStream inputStream = new ReadContentInputStream(file);
final Image fxImage;
try {
//original input stream
BufferedImage bi = ImageIO.read(inputStream);
//scale image using Scalr
BufferedImage biScaled = ScalrWrapper.resizeHighQuality(bi, (int) dims.getWidth(), (int) dims.getHeight());
//convert from awt imageto fx image
fxImage = SwingFXUtils.toFXImage(biScaled, null);
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not load image file into media view: " + fileName, ex);
return;
} catch (OutOfMemoryError ex) {
logger.log(Level.WARNING, "Could not load image file into media view (too large): " + fileName, ex);
MessageNotifyUtil.Notify.warn("Could not load image file (too large): " + file.getName(), ex.getMessage());
return;
} finally {
try {
inputStream.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not close input stream after loading image in media view: " + fileName, ex);
}
}
if (fxImage == null) {
logger.log(Level.WARNING, "Could not load image file into media view: " + fileName);
return;
}
// simple displays ImageView the image as is
ImageView fxImageView = new ImageView();
fxImageView.setImage(fxImage);
// resizes the image to have width of 100 while preserving the ratio and using
// higher quality filtering method; this ImageView is also cached to
// improve performance
fxImageView.setPreserveRatio(true);
fxImageView.setSmooth(true);
fxImageView.setCache(true);
fxImageView.setFitWidth(dims.getWidth());
fxImageView.setFitHeight(dims.getHeight());
Group fxRoot = new Group();
//Scene fxScene = new Scene(fxRoot, dims.getWidth(), dims.getHeight(), javafx.scene.paint.Color.BLACK);
Scene fxScene = new Scene(fxRoot, javafx.scene.paint.Color.BLACK);
fxRoot.getChildren().add(fxImageView);
if (inImageMode) {
final JFXPanel fxPanel = (JFXPanel) videoPanel.getComponent(0);
fxPanel.setScene(fxScene);
videoPanel.setVisible(true);
} else {
inImageMode = true;
final JFXPanel fxPanel = new JFXPanel();
fxPanel.setScene(fxScene);
//when done, join with the swing panel
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
//remove video panels and recreate image view panel
//TODO use swing layered pane to switch between different modes
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(fxPanel);
videoPanel.setVisible(true);
if (fxImage.isError()) {
logger.log(Level.WARNING, "Could not load image file into media view: " + fileName);
//MessageNotifyUtil.Message.warn("Could not load image file: " + file.getName());
}
}
});
}
}
});
}
/**
* Initialize vars and display the image on the panel.
*
* @param file
* @deprecated using javafx for image display
*/
@Deprecated
private void showImageGst(AbstractFile file) {
if (!gstInited) {
return;
private void switchPanels(boolean showVideo) {
CardLayout layout = (CardLayout)this.getLayout();
if (showVideo) {
layout.show(this, VIDEO_VIEWER_LAYER);
} else {
layout.show(this, IMAGE_VIEWER_LAYER);
}
java.io.File ioFile = getJFile(file);
if (!ioFile.exists()) {
try {
ContentUtils.writeToFile(file, ioFile);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex);
}
}
videoComponent = new VideoComponent();
synchronized (playbinLock) {
if (playbin2 != null) {
playbin2.dispose();
}
playbin2 = new PlayBin2("ImageViewer");
playbin2.setVideoSink(videoComponent.getElement());
}
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(videoComponent);
videoPanel.revalidate();
videoPanel.repaint();
synchronized (playbinLock) {
playbin2.setInputFile(ioFile);
playbin2.play();
}
videoPanel.setVisible(true);
}
/**
* Initialize all the necessary vars to play a video/audio file.
*
* @param file the file to play
*/
private void setupVideo(AbstractFile file) {
java.io.File ioFile = getJFile(file);
pauseButton.setText("");
progressSlider.setValue(0);
videoComponent = new VideoComponent();
synchronized (playbinLock) {
if (playbin2 != null) {
playbin2.dispose();
}
playbin2 = new PlayBin2("VideoPlayer");
playbin2.setVideoSink(videoComponent.getElement());
}
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(videoComponent);
videoPanel.revalidate();
videoPanel.repaint();
synchronized (playbinLock) {
playbin2.setInputFile(ioFile);
playbin2.setState(State.READY);
}
setComponentsVisibility(true);
}
/**
* To set the visibility of specific components in this class.
*
* @param isVisible whether to show or hide the specific components
*/
private void setComponentsVisibility(boolean isVisible) {
pauseButton.setVisible(isVisible);
progressLabel.setVisible(isVisible);
progressSlider.setVisible(isVisible);
videoPanel.setVisible(isVisible);
}
@Override
@ -484,7 +162,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
@Override
public String getToolTip() {
return "Displays supported multimedia files";
return "Displays supported multimedia files (images, videos, audio)";
}
@Override
@ -503,50 +181,20 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
// because we already reset on each selected node
}
private void reset() {
// reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressLabel.setText("");
}
});
if (!gstInited) {
return;
}
synchronized (playbinLock) {
if (playbin2 != null) {
if (playbin2.isPlaying()) {
playbin2.stop();
}
playbin2.setState(State.NULL);
if (playbin2.getState().equals(State.NULL)) {
playbin2.dispose();
}
playbin2 = null;
}
videoComponent = null;
}
}
@Override
public boolean isSupported(Node node) {
if (node == null) {
return false;
}
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
if (file == null) {
return false;
}
//try displaying deleted files if we can read them
//if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
// return false;
//}
if (file.getSize() == 0) {
return false;
@ -554,15 +202,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
String name = file.getName().toLowerCase();
boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC);
if (containsExt(name, IMAGES)) {
if (imagePanelInited && containsExt(name, IMAGES)) {
return true;
} //for gstreamer formats, check if initialized first, then
//support audio formats, and video formats if undeleted file
else if (gstInited
//support audio formats, and video formats
else if (videoPanelInited && videoPanel.isInited()
&& (containsExt(name, AUDIOS)
|| (!deleted && containsExt(name, VIDEOS)))) {
|| (containsExt(name, VIDEOS)))) {
return true;
}
@ -572,7 +218,19 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
@Override
public int isPreferred(Node node, boolean isSupported) {
if (isSupported) {
return 7;
//special case, check if deleted video, then do not make it preferred
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
if (file == null) {
return 0;
}
String name = file.getName().toLowerCase();
boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC);
if (containsExt(name, VIDEOS) && deleted) {
return 0;
} else {
return 7;
}
} else {
return 0;
}
@ -586,274 +244,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
}
return Arrays.asList(exts).contains(ext);
}
private java.io.File getJFile(AbstractFile file) {
// Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory();
String name = file.getName();
int extStart = name.lastIndexOf(".");
String ext = "";
if (extStart != -1) {
ext = name.substring(extStart, name.length()).toLowerCase();
}
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
java.io.File tempFile = new java.io.File(tempPath);
return tempFile;
}
@Override
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) {
List<VideoFrame> frames = new ArrayList<>();
if (!gstInited) {
return frames;
}
RGBDataSink.Listener listener1 = new RGBDataSink.Listener() {
@Override
public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
BufferedImage curImage = new BufferedImage(w, h,
BufferedImage.TYPE_INT_ARGB);
curImage.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
currentImage = curImage;
}
};
// set up a PlayBin2 object
RGBDataSink videoSink = new RGBDataSink("rgb", listener1);
PlayBin2 playbin = new PlayBin2("VideoFrameCapture");
playbin.setInputFile(file);
playbin.setVideoSink(videoSink);
// this is necessary to get a valid duration value
playbin.play();
playbin.pause();
playbin.getState();
// get the duration of the video
TimeUnit unit = TimeUnit.MILLISECONDS;
long myDurationMillis = playbin.queryDuration(unit);
if (myDurationMillis <= 0) {
return frames;
}
// create a list of timestamps at which to get frames
int numFramesToGet = numFrames;
long frameInterval = myDurationMillis / numFrames;
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
numFramesToGet = 1;
}
// for each timeStamp, grap a frame
for (int i = 0; i < numFramesToGet; ++i) {
long timeStamp = i * frameInterval;
playbin.pause();
playbin.getState();
currentImage = null;
if (!playbin.seek(timeStamp, unit)) {
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase());
}
playbin.play();
while (currentImage == null) {
System.out.flush(); // not sure why this is needed
}
playbin.stop();
frames.add(new VideoFrame(currentImage, timeStamp));
}
return frames;
}
/* Thread that extracts and plays a file */
private class ExtractMedia extends SwingWorker<Object, Void> {
private ProgressHandle progress;
boolean success = false;
private AbstractFile sFile;
private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
this.sFile = sFile;
this.jFile = jFile;
}
public long getExtractedBytes() {
return extractedBytes;
}
@Override
protected Object doInBackground() throws Exception {
success = false;
progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() {
@Override
public boolean cancel() {
return ExtractMedia.this.cancel(true);
}
});
progressLabel.setText("Buffering... ");
progress.start();
progress.switchToDeterminate(100);
try {
extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex);
}
success = true;
return null;
}
/* clean up or start the worker threads */
@Override
protected void done() {
try {
super.get(); //block and get all exceptions thrown while doInBackground()
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled.");
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Media buffering was interrupted.");
} catch (Exception ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex);
} finally {
progress.finish();
if (!this.isCancelled()) {
play();
}
}
}
private void play() {
if (jFile == null || !jFile.exists()) {
progressLabel.setText("Error buffering file");
return;
}
ClockTime dur = null;
synchronized (playbinLock) {
playbin2.play(); // must play, then pause and get state to get duration.
playbin2.pause();
playbin2.getState();
dur = playbin2.queryDuration();
}
duration = dur.toString();
durationMillis = dur.toMillis();
// pick out the total hours, minutes, seconds
long durationSeconds = (int) durationMillis / 1000;
totalHours = (int) durationSeconds / 3600;
durationSeconds -= totalHours * 3600;
totalMinutes = (int) durationSeconds / 60;
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0);
synchronized (playbinLock) {
playbin2.play();
}
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
}
});
}
}
private class VideoProgressWorker extends SwingWorker<Object, Object> {
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d ";
private long millisElapsed = 0;
private final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50;
private boolean isPlayBinReady() {
synchronized (playbinLock) {
return playbin2 != null && !playbin2.getState().equals(State.NULL);
}
}
public void resetVideo() {
synchronized (playbinLock) {
if (playbin2 != null) {
playbin2.stop();
playbin2.setState(State.READY); // ready to be played again
}
}
pauseButton.setText("");
progressSlider.setValue(0);
String durationStr = String.format(durationFormat, 0, 0, 0,
totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr);
}
/**
* @return true while millisElapsed is greater than END_TIME_MARGIN_MS
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
*/
private boolean hasNotEnded() {
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
}
@Override
protected Object doInBackground() throws Exception {
// enable the slider
progressSlider.setEnabled(true);
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
ClockTime pos = null;
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
synchronized (playbinLock) {
pos = playbin2.queryPosition();
}
millisElapsed = pos.toMillis();
// pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000;
elapsedHours = (int) secondsElapsed / 3600;
secondsElapsed -= elapsedHours * 3600;
elapsedMinutes = (int) secondsElapsed / 60;
secondsElapsed -= elapsedMinutes * 60;
elapsedSeconds = (int) secondsElapsed;
String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds,
totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr);
autoTracking = true;
progressSlider.setValue((int) millisElapsed);
autoTracking = false;
try {
Thread.sleep(INTER_FRAME_PERIOD_MS);
} catch (InterruptedException ex) {
break;
}
}
// disable the slider
progressSlider.setEnabled(false);
resetVideo();
return null;
}
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="0" type="rgb"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="1"/>
</Layout>
</Form>

View File

@ -0,0 +1,209 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.corecomponents;
import com.sun.javafx.application.PlatformImpl;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.logging.Level;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javax.imageio.ImageIO;
import org.openide.modules.ModuleInstall;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream;
/**
* Container for the image viewer part of media view, on a layered pane. To be
* used with JavaFx image viewer only.
*/
public class MediaViewImagePanel extends javax.swing.JPanel {
private JFXPanel fxPanel;
private ImageView fxImageView;
private static final Logger logger = Logger.getLogger(MediaViewImagePanel.class.getName());
private boolean fxInited = false;
/**
* Creates new form MediaViewImagePanel
*/
public MediaViewImagePanel() {
initComponents();
org.sleuthkit.autopsy.core.Installer coreInstaller =
ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false);
if (coreInstaller != null) {
fxInited = coreInstaller.isJavaFxInited();
}
if (fxInited) {
setupFx();
}
}
public boolean isInited() {
return fxInited;
}
/**
* Setup FX components
*/
private void setupFx() {
// load the image
PlatformImpl.runLater(new Runnable() {
@Override
public void run() {
fxPanel = new JFXPanel();
fxImageView = new ImageView();
// resizes the image to have width of 100 while preserving the ratio and using
// higher quality filtering method; this ImageView is also cached to
// improve performance
fxImageView.setPreserveRatio(true);
fxImageView.setSmooth(true);
fxImageView.setCache(true);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
add(fxPanel);
//TODO
// setVisible(true);
}
});
}
});
}
/**
* Show image
*
* @param file image file to show
* @param dims dimension of the parent window
*/
void showImageFx(final AbstractFile file, final Dimension dims) {
if (!fxInited) {
return;
}
final String fileName = file.getName();
//hide the panel during loading/transformations
fxPanel.setVisible(false);
// load the image
PlatformImpl.runLater(new Runnable() {
@Override
public void run() {
if (!Case.isCaseOpen()) {
//handle in-between condition when case is being closed
//and an image was previously selected
return;
}
final InputStream inputStream = new ReadContentInputStream(file);
final Image fxImage;
try {
//original input stream
BufferedImage bi = ImageIO.read(inputStream);
//scale image using Scalr
BufferedImage biScaled = ScalrWrapper.resizeHighQuality(bi, (int) dims.getWidth(), (int) dims.getHeight());
//convert from awt imageto fx image
fxImage = SwingFXUtils.toFXImage(biScaled, null);
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not load image file into media view: " + fileName, ex);
return;
} catch (OutOfMemoryError ex) {
logger.log(Level.WARNING, "Could not load image file into media view (too large): " + fileName, ex);
MessageNotifyUtil.Notify.warn("Could not load image file (too large): " + file.getName(), ex.getMessage());
return;
} finally {
try {
inputStream.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not close input stream after loading image in media view: " + fileName, ex);
}
}
if (fxImage == null || fxImage.isError()) {
logger.log(Level.WARNING, "Could not load image file into media view: " + fileName);
return;
}
//use border pane to center the image in the scene
BorderPane borderpane = new BorderPane();
borderpane.setCenter(fxImageView);
fxImageView.setImage(fxImage);
fxImageView.setFitWidth(dims.getWidth());
fxImageView.setFitHeight(dims.getHeight());
//Group fxRoot = new Group();
//Scene fxScene = new Scene(fxRoot, dims.getWidth(), dims.getHeight(), javafx.scene.paint.Color.BLACK);
Scene fxScene = new Scene(borderpane, javafx.scene.paint.Color.BLACK);
// borderpane.getChildren().add(fxImageView);
fxPanel.setScene(fxScene);
//show the panel after fully loaded
fxPanel.setVisible(true);
}
});
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setBackground(new java.awt.Color(0, 0, 0));
setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="videoPanel" alignment="1" max="32767" attributes="0"/>
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="videoPanel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="controlPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="videoPanel">
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="188" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Container>
<Container class="javax.swing.JPanel" name="controlPanel">
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="progressSlider" pref="357" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
<Component id="progressSlider" min="-2" max="-2" attributes="0"/>
<Component id="progressLabel" alignment="1" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="pauseButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.pauseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pauseButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JSlider" name="progressSlider">
</Component>
<Component class="javax.swing.JLabel" name="progressLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="infoLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,646 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.corecomponents;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.gstreamer.ClockTime;
import org.gstreamer.Gst;
import org.gstreamer.GstException;
import org.gstreamer.State;
import org.gstreamer.elements.PlayBin2;
import org.gstreamer.elements.RGBDataSink;
import org.gstreamer.swing.VideoComponent;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Video viewer part of the Media View layered pane
*/
@ServiceProviders(value = {
@ServiceProvider(service = FrameCapture.class)
})
public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture {
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
private boolean gstInited;
//frame capture
private BufferedImage currentImage = null;
private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
//playback
private long durationMillis = 0;
private VideoProgressWorker videoProgressWorker;
private int totalHours, totalMinutes, totalSeconds;
private volatile PlayBin2 gstPlaybin2;
private VideoComponent gstVideoComponent;
private boolean autoTracking = false; // true if the slider is moving automatically
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
private AbstractFile currentFile;
/**
* Creates new form MediaViewVideoPanel
*/
public MediaViewVideoPanel() {
initComponents();
customizeComponents();
}
public JButton getPauseButton() {
return pauseButton;
}
public JLabel getProgressLabel() {
return progressLabel;
}
public JSlider getProgressSlider() {
return progressSlider;
}
public JPanel getVideoPanel() {
return videoPanel;
}
public VideoComponent getVideoComponent() {
return gstVideoComponent;
}
public boolean isInited() {
return gstInited;
}
private void customizeComponents() {
initGst();
progressSlider.setEnabled(false); // disable slider; enable after user plays vid
progressSlider.setValue(0);
if (gstInited) {
progressSlider.addChangeListener(new ChangeListener() {
/**
* Should always try to synchronize any call to
* progressSlider.setValue() to avoid a different thread
* changing playbin while stateChanged() is processing
*/
@Override
public void stateChanged(ChangeEvent e) {
int time = progressSlider.getValue();
synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) {
State orig = gstPlaybin2.getState();
gstPlaybin2.pause();
gstPlaybin2.seek(ClockTime.fromMillis(time));
gstPlaybin2.setState(orig);
}
}
}
});
}
}
private boolean initGst() {
try {
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing");
Gst.init();
gstInited = true;
} catch (GstException e) {
gstInited = false;
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e);
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. "
+ " Video and audio viewing will be disabled. ",
e.getMessage());
return false;
} catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
gstInited = false;
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e);
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. "
+ " Video and audio viewing will be disabled. ",
e.getMessage());
return false;
}
return true;
}
/**
* Initialize all the necessary vars to play a video/audio file.
*
* @param file video file to play
* @param dims dimension of the parent window
*/
void setupVideo(final AbstractFile file, final Dimension dims) {
currentFile = file;
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
if (deleted) {
infoLabel.setText("Playback of deleted videos is not supported, use an external player. ");
videoPanel.removeAll();
pauseButton.setEnabled(false);
progressSlider.setEnabled(false);
return;
} else {
try {
String path = file.getUniquePath();
infoLabel.setText(path);
infoLabel.setToolTipText(path);
pauseButton.setEnabled(true);
progressSlider.setEnabled(true);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Cannot get unique path of video file");
}
}
java.io.File ioFile = getJFile(file);
gstVideoComponent = new VideoComponent();
synchronized (playbinLock) {
if (gstPlaybin2 != null) {
gstPlaybin2.dispose();
}
gstPlaybin2 = new PlayBin2("VideoPlayer");
gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(gstVideoComponent);
videoPanel.setVisible(true);
gstPlaybin2.setInputFile(ioFile);
gstPlaybin2.setState(State.READY);
}
}
void reset() {
// reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressLabel.setText("");
// infoLabel.setText("");
}
});
if (!isInited()) {
return;
}
synchronized (playbinLock) {
if (gstPlaybin2 != null) {
if (gstPlaybin2.isPlaying()) {
gstPlaybin2.stop();
}
gstPlaybin2.setState(State.NULL);
if (gstPlaybin2.getState().equals(State.NULL)) {
gstPlaybin2.dispose();
}
gstPlaybin2 = null;
}
gstVideoComponent = null;
//videoComponent.setBackground(Color.BLACK);
//videoComponent.repaint();
//videoPanel.repaint();
}
// get rid of any existing videoProgressWorker thread
if (videoProgressWorker != null) {
videoProgressWorker.cancel(true);
videoProgressWorker = null;
}
currentFile = null;
}
private java.io.File getJFile(AbstractFile file) {
// Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory();
String name = file.getName();
int extStart = name.lastIndexOf(".");
String ext = "";
if (extStart != -1) {
ext = name.substring(extStart, name.length()).toLowerCase();
}
tempPath = tempPath + java.io.File.separator + file.getId() + ext;
java.io.File tempFile = new java.io.File(tempPath);
return tempFile;
}
@Override
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) {
List<VideoFrame> frames = new ArrayList<>();
if (!isInited()) {
return frames;
}
RGBDataSink.Listener listener1 = new RGBDataSink.Listener() {
@Override
public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
BufferedImage curImage = new BufferedImage(w, h,
BufferedImage.TYPE_INT_ARGB);
curImage.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
currentImage = curImage;
}
};
// set up a PlayBin2 object
RGBDataSink videoSink = new RGBDataSink("rgb", listener1);
PlayBin2 playbin = new PlayBin2("VideoFrameCapture");
playbin.setInputFile(file);
playbin.setVideoSink(videoSink);
// this is necessary to get a valid duration value
playbin.play();
playbin.pause();
playbin.getState();
// get the duration of the video
TimeUnit unit = TimeUnit.MILLISECONDS;
long myDurationMillis = playbin.queryDuration(unit);
if (myDurationMillis <= 0) {
return frames;
}
// create a list of timestamps at which to get frames
int numFramesToGet = numFrames;
long frameInterval = myDurationMillis / numFrames;
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
numFramesToGet = 1;
}
// for each timeStamp, grap a frame
for (int i = 0; i < numFramesToGet; ++i) {
long timeStamp = i * frameInterval;
playbin.pause();
playbin.getState();
currentImage = null;
if (!playbin.seek(timeStamp, unit)) {
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase());
}
playbin.play();
while (currentImage == null) {
System.out.flush(); // not sure why this is needed
}
playbin.stop();
frames.add(new VideoFrame(currentImage, timeStamp));
}
return frames;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
videoPanel = new javax.swing.JPanel();
controlPanel = new javax.swing.JPanel();
pauseButton = new javax.swing.JButton();
progressSlider = new javax.swing.JSlider();
progressLabel = new javax.swing.JLabel();
infoLabel = new javax.swing.JLabel();
javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
videoPanel.setLayout(videoPanelLayout);
videoPanelLayout.setHorizontalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
videoPanelLayout.setVerticalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 188, Short.MAX_VALUE)
);
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
pauseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pauseButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
controlPanel.setLayout(controlPanelLayout);
controlPanelLayout.setHorizontalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(infoLabel)
.addGap(0, 0, Short.MAX_VALUE))
);
controlPanelLayout.setVerticalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pauseButton)
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(progressLabel, javax.swing.GroupLayout.Alignment.TRAILING))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(infoLabel))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(videoPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
synchronized (playbinLock) {
State state = gstPlaybin2.getState();
if (state.equals(State.PLAYING)) {
gstPlaybin2.pause();
pauseButton.setText("");
gstPlaybin2.setState(State.PAUSED);
} else if (state.equals(State.PAUSED)) {
gstPlaybin2.play();
pauseButton.setText("||");
gstPlaybin2.setState(State.PLAYING);
} else if (state.equals(State.READY)) {
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute();
em.getExtractedBytes();
}
}
}//GEN-LAST:event_pauseButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel controlPanel;
private javax.swing.JLabel infoLabel;
private javax.swing.JButton pauseButton;
private javax.swing.JLabel progressLabel;
private javax.swing.JSlider progressSlider;
private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables
private class VideoProgressWorker extends SwingWorker<Object, Object> {
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d ";
private long millisElapsed = 0;
private final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50;
private boolean isPlayBinReady() {
synchronized (playbinLock) {
return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
}
}
private void resetVideo() {
synchronized (playbinLock) {
if (gstPlaybin2 != null) {
gstPlaybin2.stop();
gstPlaybin2.setState(State.READY); // ready to be played again
gstPlaybin2.getState(); //NEW
}
}
pauseButton.setText("");
progressSlider.setValue(0);
String durationStr = String.format(durationFormat, 0, 0, 0,
totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr);
}
/**
* @return true while millisElapsed is greater than END_TIME_MARGIN_MS
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
*/
private boolean hasNotEnded() {
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
}
@Override
protected Object doInBackground() throws Exception {
// enable the slider
progressSlider.setEnabled(true);
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
ClockTime pos = null;
while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
synchronized (playbinLock) {
pos = gstPlaybin2.queryPosition();
}
millisElapsed = pos.toMillis();
// pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000;
elapsedHours = (int) secondsElapsed / 3600;
secondsElapsed -= elapsedHours * 3600;
elapsedMinutes = (int) secondsElapsed / 60;
secondsElapsed -= elapsedMinutes * 60;
elapsedSeconds = (int) secondsElapsed;
String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds,
totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr);
autoTracking = true;
progressSlider.setValue((int) millisElapsed);
autoTracking = false;
try {
Thread.sleep(INTER_FRAME_PERIOD_MS);
} catch (InterruptedException ex) {
break;
}
}
// disable the slider
progressSlider.setEnabled(false);
resetVideo();
return null;
}
} //end class progress worker
/* Thread that extracts and plays a file */
private class ExtractMedia extends SwingWorker<Object, Void> {
private ProgressHandle progress;
boolean success = false;
private AbstractFile sFile;
private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
this.sFile = sFile;
this.jFile = jFile;
}
public long getExtractedBytes() {
return extractedBytes;
}
@Override
protected Object doInBackground() throws Exception {
success = false;
progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() {
@Override
public boolean cancel() {
return ExtractMedia.this.cancel(true);
}
});
progressLabel.setText("Buffering... ");
progress.start();
progress.switchToDeterminate(100);
try {
extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex);
}
success = true;
return null;
}
/* clean up or start the worker threads */
@Override
protected void done() {
try {
super.get(); //block and get all exceptions thrown while doInBackground()
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled.");
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Media buffering was interrupted.");
} catch (Exception ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex);
} finally {
progress.finish();
if (!this.isCancelled()) {
playMedia();
}
}
}
void playMedia() {
if (jFile == null || !jFile.exists()) {
progressLabel.setText("Error buffering file");
return;
}
ClockTime dur = null;
synchronized (playbinLock) {
gstPlaybin2.play(); // must play, then pause and get state to get duration.
gstPlaybin2.pause();
State state = gstPlaybin2.getState();
dur = gstPlaybin2.queryDuration();
}
duration = dur.toString();
durationMillis = dur.toMillis();
// pick out the total hours, minutes, seconds
long durationSeconds = (int) durationMillis / 1000;
totalHours = (int) durationSeconds / 3600;
durationSeconds -= totalHours * 3600;
totalMinutes = (int) durationSeconds / 60;
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressSlider.setMaximum((int) durationMillis);
progressSlider.setMinimum(0);
synchronized (playbinLock) {
gstPlaybin2.play();
}
pauseButton.setText("||");
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
}
});
}
}
}

View File

@ -27,7 +27,6 @@ import org.sleuthkit.datamodel.SleuthkitCase;
/**
*
* @author dfickling
*/
public class ExtractedContentChildren extends ChildFactory<BlackboardArtifact.ARTIFACT_TYPE> {
@ -51,6 +50,9 @@ public class ExtractedContentChildren extends ChildFactory<BlackboardArtifact.AR
list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
return true;
}
@Override
protected Node createNodeForKey(BlackboardArtifact.ARTIFACT_TYPE key){

View File

@ -63,6 +63,7 @@ public class TagFileAction extends AbstractAction implements Presenter.Popup {
}
private void refreshDirectoryTree() {
//TODO instead should send event to node children, which will call its refresh() / refreshKeys()
DirectoryTreeTopComponent viewer = DirectoryTreeTopComponent.findInstance();
viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE);
viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT);

View File

@ -78,6 +78,8 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil
private static final Logger logger = Logger.getLogger(ExifParserFileIngestModule.class.getName());
private static ExifParserFileIngestModule defaultInstance = null;
private static int messageId = 0;
private int filesProcessed = 0;
//file ingest modules require a private constructor
//to ensure singleton instances
@ -164,7 +166,10 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil
if(!attributes.isEmpty()) {
BlackboardArtifact bba = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
bba.addAttributes(attributes);
services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF));
++filesProcessed;
if (filesProcessed %100 == 0) {
services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF));
}
}
return IngestModuleAbstractFile.ProcessResult.OK;
@ -243,6 +248,10 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil
public void complete() {
logger.log(Level.INFO, "completed exif parsing " + this.toString());
if (filesProcessed > 0) {
//send the final new data event
services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF));
}
//module specific cleanup due to completion here
}
@ -277,6 +286,8 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil
public void init(IngestModuleInit initContext) {
services = IngestServices.getDefault();
logger.log(Level.INFO, "init() " + this.toString());
filesProcessed = 0;
}
@Override

View File

@ -13,12 +13,10 @@ Improvements:
Bugfixes:
- fixed memory leaks in "Add Image"
- fix bug with duplicate instances of content and result viewers hidden and possibly degrading performance
- The "media view" tab is inactive for deleted files (#165)
- show error message in hex and string viewer if specific offset of a file could not be read.
- file search actions not always enabled when new case is open.
- fixed directory tree history being reset when tree is refreshed.
- The "media view" tab is inactive for deleted files (#165)
---------------- VERSION 3.0.4 --------------

13
Test/script/Setup.txt Normal file
View File

@ -0,0 +1,13 @@
This file explains how to setup regression and continuous testing for Autopsy.
1)Create a folder called 'gold', this is where the test standards will go.
2)Create a folder called 'input', this is where the image, hash, and keyword search files.
3)Put an idx file called NSRL, that contains known bad hashes, in the input folder.
4)Put an idx file called notablehashes, that contains notable hashes, in the input folder.
5)Put an xml file called notablekeywords, that contains keywords, in the input folder.
6)The xml file should be of the format of the notablekeywords-example.xml file.
7)Create a folder called 'output', this is where the output from tests go.
8)The config.xml file is an example of a configuration file and contains all of the available elements.
9)Use the -g command to create a gold standard without testing.
10)Use the -r command to rebuild gold standards with testing.
11)Use the -c command to run testing continuously until interrupted, this will also update all of the sleuthkit and autopsy code from github and will rebuild the code so that it is running the latest version after each test.

View File

@ -35,6 +35,7 @@ It is up to the user to distinguish between the paths when adding to this file.
<image value="X:\path\to\a\different\image\file.E01" />
<image value="This provokes an error." />
<image value="X:\Invalid\Path\Does\Not\Exist.img" />
<build value="X:\Path\To\Build\File.xml">
<email value="address@provider.com"/>
<mail_server value="mail.localhost.com" />
</Properties>

View File

@ -24,6 +24,8 @@ from email.MIMEBase import MIMEBase
from email import Encoders
import urllib2
import re
import zipfile
import zlib
#
# Please read me...
@ -66,6 +68,7 @@ class Args:
self.exception = False
self.exception_string = ""
self.contin = False
self.gold_creation = False
def parse(self):
global nxtproc
@ -103,7 +106,7 @@ class Args:
printout("Ignoring unallocated space.\n")
self.unallocated = True
elif(arg == "-i" or arg == "--ignore"):
printout("Ignoring the ./input directory.\n")
printout("Ignoring the ../input directory.\n")
self.ignore = True
elif(arg == "-k" or arg == "--keep"):
printout("Keeping the Solr index.\n")
@ -128,6 +131,9 @@ class Args:
elif arg == "-c" or arg == "--continuous":
printout("Running until interrupted")
self.contin = True
elif arg == "-g" or arg == "--gold":
printout("Creating gold standards")
self.gold_creation = True
else:
printout(usage())
return False
@ -142,9 +148,9 @@ class Args:
class TestAutopsy:
def __init__(self):
# Paths:
self.input_dir = make_local_path("input")
self.input_dir = make_local_path("..","input")
self.output_dir = ""
self.gold = "gold"
self.gold = make_local_path("..", "output", "gold")
# Logs:
self.antlog_dir = ""
self.common_log = ""
@ -165,6 +171,7 @@ class TestAutopsy:
self.known_bad_path = ""
self.keyword_path = ""
self.nsrl_path = ""
self.build_path = ""
# Case info
self.start_date = ""
self.end_date = ""
@ -300,7 +307,7 @@ class Database:
def generate_autopsy_artifacts(self):
if not self.autopsy_artifacts:
autopsy_db_file = os.path.join("./", case.output_dir, case.image_name,
autopsy_db_file = make_path(case.output_dir, case.image_name,
"AutopsyTestCase", "autopsy.db")
autopsy_con = sqlite3.connect(autopsy_db_file)
autopsy_cur = autopsy_con.cursor()
@ -309,10 +316,16 @@ class Database:
for type_id in range(1, length):
autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id)
self.autopsy_artifacts.append(autopsy_cur.fetchone()[0])
autopsy_cur.execute("SELECT * FROM blackboard_artifacts")
self.autopsy_artifacts_list = []
for row in autopsy_cur.fetchall():
for item in row:
self.autopsy_artifacts_list.append(item)
def generate_autopsy_attributes(self):
if self.autopsy_attributes == 0:
autopsy_db_file = os.path.join("./", case.output_dir, case.image_name,
autopsy_db_file = make_path(case.output_dir, case.image_name,
"AutopsyTestCase", "autopsy.db")
autopsy_con = sqlite3.connect(autopsy_db_file)
autopsy_cur = autopsy_con.cursor()
@ -322,7 +335,7 @@ class Database:
def generate_autopsy_objects(self):
if self.autopsy_objects == 0:
autopsy_db_file = os.path.join("./", case.output_dir, case.image_name,
autopsy_db_file = make_path(case.output_dir, case.image_name,
"AutopsyTestCase", "autopsy.db")
autopsy_con = sqlite3.connect(autopsy_db_file)
autopsy_cur = autopsy_con.cursor()
@ -332,7 +345,7 @@ class Database:
def generate_gold_artifacts(self):
if not self.gold_artifacts:
gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db")
gold_db_file = make_path(case.gold, case.image_name, "autopsy.db")
gold_con = sqlite3.connect(gold_db_file)
gold_cur = gold_con.cursor()
gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types")
@ -340,10 +353,15 @@ class Database:
for type_id in range(1, length):
gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id)
self.gold_artifacts.append(gold_cur.fetchone()[0])
gold_cur.execute("SELECT * FROM blackboard_artifacts")
self.gold_artifacts_list = []
for row in gold_cur.fetchall():
for item in row:
self.gold_artifacts_list.append(item)
def generate_gold_attributes(self):
if self.gold_attributes == 0:
gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db")
gold_db_file = make_path(case.gold, case.image_name, "autopsy.db")
gold_con = sqlite3.connect(gold_db_file)
gold_cur = gold_con.cursor()
gold_cur.execute("SELECT COUNT(*) FROM blackboard_attributes")
@ -351,7 +369,7 @@ class Database:
def generate_gold_objects(self):
if self.gold_objects == 0:
gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db")
gold_db_file = make_path(case.gold, case.image_name, "autopsy.db")
gold_con = sqlite3.connect(gold_db_file)
gold_cur = gold_con.cursor()
gold_cur.execute("SELECT COUNT(*) FROM tsk_objects")
@ -462,6 +480,7 @@ def compile():
# Runs the test on the single given file.
# The path must be guarenteed to be a correct path.
def run_test(image_file, count):
global parsed
if image_type(image_file) == IMGTYPE.UNKNOWN:
printerror("Error: Image type is unrecognized:")
printerror(image_file + "\n")
@ -474,6 +493,18 @@ def run_test(image_file, count):
case.common_log_path = make_local_path(case.output_dir, case.image_name, case.image_name+case.common_log)
case.warning_log = make_local_path(case.output_dir, case.image_name, "AutopsyLogs.txt")
case.antlog_dir = make_local_path(case.output_dir, case.image_name, "antlog.txt")
if(args.list):
element = parsed.getElementsByTagName("build")
if(len(element)<=0):
toval = make_path("..", "build.xml")
else:
element = element[0]
toval = element.getAttribute("value").encode().decode("utf_8")
if(toval==None):
toval = make_path("..", "build.xml")
else:
toval = make_path("..", "build.xml")
case.build_path = toval
case.known_bad_path = make_path(case.input_dir, "notablehashes.txt-md5.idx")
case.keyword_path = make_path(case.input_dir, "notablekeywords.xml")
case.nsrl_path = make_path(case.input_dir, "nsrl.txt-md5.idx")
@ -512,19 +543,31 @@ def run_test(image_file, count):
print_report(exceptions, "EXCEPTION", okay)
# Now test in comparison to the gold standards
compare_to_gold_db()
compare_to_gold_html()
if not args.gold_creation:
try:
gold_path = case.gold
img_gold = make_path(case.gold, case.image_name)
img_archive = make_path(case.gold, case.image_name+"-archive.zip")
extrctr = zipfile.ZipFile(img_archive, 'r', compression=zipfile.ZIP_DEFLATED)
extrctr.extractall(gold_path)
extrctr.close
time.sleep(1)
compare_to_gold_db()
compare_to_gold_html()
compare_errors()
del_dir(img_gold)
except:
print("Tests failed due to an error, try rebuilding or creating gold standards.\n")
# Make the CSV log and the html log viewer
generate_csv(case.csv)
if case.global_csv:
generate_csv(case.global_csv)
generate_html()
# If running in rebuild mode (-r)
if args.rebuild:
if args.rebuild or args.gold_creation:
rebuild()
# Reset the case and return the tests sucessfully finished
clear_dir(make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "ModuleOutput", "keywordsearch"))
clear_dir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "ModuleOutput", "keywordsearch"))
case.reset()
return True
@ -536,12 +579,11 @@ def run_ant():
if dir_exists(test_case_path):
shutil.rmtree(test_case_path)
os.makedirs(test_case_path)
if not dir_exists(make_local_path("gold")):
os.makedirs(make_local_path("gold"))
case.ant = ["ant"]
case.ant.append("-v")
case.ant.append("-f")
case.ant.append(os.path.join("..","build.xml"))
# case.ant.append(case.build_path)
case.ant.append(os.path.join("..","..","Testing","build.xml"))
case.ant.append("regression-test")
case.ant.append("-l")
case.ant.append(case.antlog_dir)
@ -549,7 +591,7 @@ def run_ant():
case.ant.append("-Dknown_bad_path=" + case.known_bad_path)
case.ant.append("-Dkeyword_path=" + case.keyword_path)
case.ant.append("-Dnsrl_path=" + case.nsrl_path)
case.ant.append("-Dgold_path=" + make_local_path(case.gold))
case.ant.append("-Dgold_path=" + make_path(case.gold))
case.ant.append("-Dout_path=" + make_local_path(case.output_dir, case.image_name))
case.ant.append("-Dignore_unalloc=" + "%s" % args.unallocated)
case.ant.append("-Dcontin_mode=" + str(args.contin))
@ -563,7 +605,7 @@ def run_ant():
if SYS is OS.CYGWIN:
subprocess.call(case.ant, stdout=antout)
elif SYS is OS.WIN:
theproc = subprocess.Popen(case.ant, shell = True)
theproc = subprocess.Popen(case.ant, shell = True, stdout=subprocess.PIPE)
theproc.communicate()
antout.close()
@ -597,12 +639,14 @@ def rebuild():
# Errors to print
errors = []
# Delete the current gold standards
gold_dir = make_local_path(case.gold, case.image_name)
gold_dir = make_path(case.gold, case.image_name)
clear_dir(gold_dir)
dbinpth = make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db")
dboutpth = make_local_path(case.gold, case.image_name, "autopsy.db")
dbinpth = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db")
dboutpth = make_path(case.gold, case.image_name, "autopsy.db")
if not os.path.exists(gold_dir):
os.makedirs(gold_dir)
copy_file(dbinpth, dboutpth)
error_pth = make_local_path(case.gold, case.image_name, case.image_name+"SortedErrors.txt")
error_pth = make_path(case.gold, case.image_name, case.image_name+"SortedErrors.txt")
copy_file(case.sorted_log, error_pth)
# Rebuild the HTML report
htmlfolder = ""
@ -611,29 +655,43 @@ def rebuild():
htmlfolder = fs
autopsy_html_path = make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder)
html_path = make_local_path(case.output_dir, case.image_name,
html_path = make_path(case.output_dir, case.image_name,
"AutopsyTestCase", "Reports")
try:
os.makedirs(os.path.join(os.getcwd(), case.gold, case.image_name, htmlfolder))
os.makedirs(os.path.join(case.gold, case.image_name, htmlfolder))
for file in os.listdir(autopsy_html_path):
html_to = make_local_path(case.gold, case.image_name, file.replace("HTML Report", "Report"))
html_to = make_path(case.gold, case.image_name, file.replace("HTML Report", "Report"))
copy_dir(get_file_in_dir(autopsy_html_path, file), html_to)
except FileNotFoundException as e:
errors.append(e.error)
except Exception as e:
errors.append("Error: Unknown fatal error when rebuilding the gold html report.")
errors.append(str(e) + "\n")
oldcwd = os.getcwd()
os.chdir(case.gold)
img_archive = make_path(case.image_name+"-archive.zip")
img_gold = os.path.join(case.image_name)
comprssr = zipfile.ZipFile(img_archive, 'w',compression=zipfile.ZIP_DEFLATED)
zipdir(img_gold, comprssr)
comprssr.close()
del_dir(gold_dir)
os.chdir(oldcwd)
okay = "Sucessfully rebuilt all gold standards."
print_report(errors, "REBUILDING", okay)
def zipdir(path, zip):
for root, dirs, files in os.walk(path):
for file in files:
zip.write(os.path.join(root, file))
# Using the global case's variables, compare the database file made by the
# regression test to the gold standard database file
# Initializes the global database, which stores the information retrieved
# from queries while comparing
def compare_to_gold_db():
# SQLITE needs unix style pathing
gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db")
autopsy_db_file = os.path.join("./", case.output_dir, case.image_name,
gold_db_file = make_path(case.gold, case.image_name, "autopsy.db")
autopsy_db_file = make_path(case.output_dir, case.image_name,
"AutopsyTestCase", "autopsy.db")
# Try to query the databases. Ignore any exceptions, the function will
# return an error later on if these do fail
@ -687,13 +745,12 @@ def compare_to_gold_db():
# Using the global case's variables, compare the html report file made by
# the regression test against the gold standard html report
def compare_to_gold_html():
gold_html_file = make_local_path(case.gold, case.image_name, "Report", "index.html")
gold_html_file = make_path(case.gold, case.image_name, "Report", "index.html")
htmlfolder = ""
for fs in os.listdir(os.path.join(os.getcwd(),case.output_dir, case.image_name, "AutopsyTestCase", "Reports")):
if os.path.isdir(os.path.join(os.getcwd(), case.output_dir, case.image_name, "AutopsyTestCase", "Reports", fs)):
for fs in os.listdir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports")):
if os.path.isdir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", fs)):
htmlfolder = fs
autopsy_html_path = make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder, "HTML Report") #, "AutopsyTestCase", "Reports", htmlfolder)
print(autopsy_html_path)
autopsy_html_path = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder, "HTML Report") #, "AutopsyTestCase", "Reports", htmlfolder)
try:
@ -708,14 +765,14 @@ def compare_to_gold_html():
return
#Find all gold .html files belonging to this case
ListGoldHTML = []
for fs in os.listdir(os.path.join(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder)):
for fs in os.listdir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder)):
if(fs.endswith(".html")):
ListGoldHTML.append(os.path.join(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder, fs))
#Find all new .html files belonging to this case
ListNewHTML = []
for fs in os.listdir(os.path.join(case.gold, case.image_name)):
for fs in os.listdir(make_path(case.gold, case.image_name)):
if (fs.endswith(".html")):
ListNewHTML.append(os.path.join(case.gold, case.image_name, fs))
ListNewHTML.append(make_path(case.gold, case.image_name, fs))
#ensure both reports have the same number of files and are in the same order
if(len(ListGoldHTML) != len(ListNewHTML)):
printerror("The reports did not have the same number of files. One of the reports may have been corrupted")
@ -813,7 +870,6 @@ def generate_common_log():
try:
logs_path = make_local_path(case.output_dir, case.image_name, "logs")
common_log = codecs.open(case.common_log_path, "w", "utf_8")
print(case.common_log_path)
warning_log = codecs.open(case.warning_log, "w", "utf_8")
common_log.write("--------------------------------------------------\n")
common_log.write(case.image_name + "\n")
@ -840,14 +896,13 @@ def generate_common_log():
case.sorted_log = make_local_path(case.output_dir, case.image_name, case.image_name + "SortedErrors.txt")
srtcmdlst = ["sort", case.common_log_path, "-o", case.sorted_log]
subprocess.call(srtcmdlst)
compare_errors()
except Exception as e:
printerror("Error: Unable to generate the common log.")
printerror(str(e) + "\n")
logging.critical(traceback.format_exc())
def compare_errors():
gold_dir = make_local_path(case.gold, case.image_name, case.image_name + "SortedErrors.txt")
gold_dir = make_path(case.gold, case.image_name, case.image_name + "SortedErrors.txt")
common_log = codecs.open(case.sorted_log, "r", "utf_8")
gold_log = codecs.open(gold_dir, "r", "utf_8")
gold_dat = gold_log.read()
@ -890,9 +945,10 @@ def fill_case_data():
start = datetime.datetime.strptime(case.start_date, "%b %d, %Y %I:%M:%S %p")
end = datetime.datetime.strptime(case.end_date, "%a %b %d %H:%M:%S %Y")
case.total_test_time = str(end - start)
try:
# Set Autopsy version, heap space, ingest time, and service times
version_line = search_logs("INFO: Application name: Autopsy, version:")[0]
case.autopsy_version = get_word_at(version_line, 5).rstrip(",")
@ -1169,7 +1225,6 @@ def generate_html():
write_html_head()
try:
global html
print(case.html_log)
html = open(case.html_log, "a")
# The image title
title = "<h1><a name='" + case.image_name + "'>" + case.image_name + " \
@ -1490,7 +1545,7 @@ def wgetcwd():
# Copy the log files from Autopsy's default directory
def copy_logs():
try:
log_dir = os.path.join("..","build","test","qa-functional","work","userdir0","var","log")
log_dir = os.path.join("..", "..", "Testing","build","test","qa-functional","work","userdir0","var","log")
shutil.copytree(log_dir, make_local_path(case.output_dir, case.image_name, "logs"))
except Exception as e:
printerror("Error: Failed to copy the logs.")
@ -1521,6 +1576,7 @@ def del_dir(dir):
def copy_file(ffrom, to):
try :
if not file_exists(ffrom):
print("hi")
raise FileNotFoundException(ffrom)
shutil.copy(ffrom, to)
except:
@ -1604,19 +1660,19 @@ def usage():
Usage: ./regression.py [-f FILE] [OPTIONS]
Run RegressionTest.java, and compare the result with a gold standard.
By default, the script tests every image in ./input
By default, the script tests every image in ../input
When the -f flag is set, this script only tests a single given image.
When the -l flag is set, the script looks for a configuration file,
which may outsource to a new input directory and to individual images.
Expected files:
An NSRL database at: ./input/nsrl.txt-md5.idx
A notable hash database at: ./input/notablehashes.txt-md5.idx
A notable keyword file at: ./input/notablekeywords.xml
An NSRL database at: ../input/nsrl.txt-md5.idx
A notable hash database at: ../input/notablehashes.txt-md5.idx
A notable keyword file at: ../input/notablekeywords.xml
Options:
-r Rebuild the gold standards for the image(s) tested.
-i Ignores the ./input directory and all files within it.
-i Ignores the ../input directory and all files within it.
-u Tells Autopsy not to ingest unallocated space.
-k Keeps each image's Solr index instead of deleting it.
-v Verbose mode; prints all errors to the screen.
@ -1670,7 +1726,7 @@ def execute_test():
global failedbool
global html
global attachl
case.output_dir = make_path("output", time.strftime("%Y.%m.%d-%H.%M.%S"))
case.output_dir = make_path("..", "output", "results", time.strftime("%Y.%m.%d-%H.%M.%S"))
os.makedirs(case.output_dir)
case.common_log = "AutopsyErrors.txt"
case.csv = make_local_path(case.output_dir, "CSV.txt")
@ -1696,7 +1752,7 @@ def execute_test():
return
run_test(args.single_file, 0)
# If user has not selected a single file, and does not want to ignore
# the input directory, continue on to parsing ./input
# the input directory, continue on to parsing ../input
if (not args.single) and (not args.ignore):
for file in os.listdir(case.input_dir):
# Make sure it's not a required hash/keyword file or dir
@ -1720,7 +1776,8 @@ def execute_test():
errorem = ""
errorem += "There were no Errors.\n"
attachl = []
send_email()
if not args.gold_creation:
send_email()
def send_email():

View File

@ -40,6 +40,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JTextField;
import junit.framework.Test;
import junit.framework.TestCase;
@ -51,6 +52,7 @@ import org.netbeans.jemmy.operators.JButtonOperator;
import org.netbeans.jemmy.operators.JCheckBoxOperator;
import org.netbeans.jemmy.operators.JDialogOperator;
import org.netbeans.jemmy.operators.JFileChooserOperator;
import org.netbeans.jemmy.operators.JLabelOperator;
import org.netbeans.jemmy.operators.JTabbedPaneOperator;
import org.netbeans.jemmy.operators.JTableOperator;
import org.netbeans.jemmy.operators.JTextFieldOperator;
@ -296,9 +298,24 @@ public class RegressionTest extends TestCase{
new Timeout("pausing", 1000).sleep();
JButtonOperator jbo1 = new JButtonOperator(reportDialogOperator, "Finish");
jbo1.pushNoBlock();
new Timeout("pausing", 8000).sleep(); // Give it a few seconds to generate
screenshot("Progress");
boolean waiting = true;
new Timeout("pausing", 500).sleep();
long size = 0;
java.io.File rprtfldr = new java.io.File(System.getProperty("out_path")+java.io.File.separator+"AutopsyTestCase"+java.io.File.separator+"Reports"+java.io.File.separator+"AutopsyTestCase "+datenotime+java.io.File.separator+"HTML Report");
JDialog previewDialog = JDialogOperator.waitJDialog("Progress", false, false);
JLabel waiter = JLabelOperator.waitJLabel(previewDialog, "Complete", false, false);
/*while(waiting)
{
new Timeout("pausing", 500).sleep();
long currsize = size;
size = 0;
for(java.io.File elem: rprtfldr.listFiles())
{
size+=elem.getTotalSpace();
}
waiting = size>currsize;
}*/
screenshot("Progress");
JDialogOperator previewDialogOperator = new JDialogOperator(previewDialog);
JButtonOperator jbo2 = new JButtonOperator(previewDialogOperator, "Close");
jbo2.pushNoBlock();

View File

@ -65,11 +65,8 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
@ -78,6 +75,7 @@ import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.modules.InstalledFileLocator;
import org.openide.modules.ModuleInstall;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
@ -141,6 +139,18 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar,
private EventHandler fxMouseEnteredListener;
private EventHandler fxMouseExitedListener;
private SleuthkitCase skCase;
private boolean fxInited = false;
public Timeline() {
super();
org.sleuthkit.autopsy.core.Installer coreInstaller =
ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false);
if (coreInstaller != null) {
fxInited = coreInstaller.isJavaFxInited();
}
}
//Swing components and JavafX components don't play super well together
//Swing components need to be initialized first, in the swing specific thread
@ -165,7 +175,7 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar,
@Override
public void run() {
//Making the main frame *
mainFrame = new TimelineFrame();
mainFrame.setFrameName(Case.getCurrentCase().getName() + " - Autopsy Timeline (Beta)");
@ -1019,7 +1029,7 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar,
@Override
public boolean isEnabled() {
return Case.isCaseOpen();
return Case.isCaseOpen() && this.fxInited;
}
@Override

View File

@ -1,6 +1,6 @@
#Updated by build script
#Fri, 18 Jan 2013 11:06:14 -0500
currentVersion=Autopsy 20130118
#Tue, 26 Mar 2013 10:08:52 -0400
currentVersion=Autopsy 20130326
LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=288
SPLASH_WIDTH=538

View File

@ -1,4 +1,4 @@
#Updated by build script
#Fri, 18 Jan 2013 11:06:14 -0500
CTL_MainWindow_Title=Autopsy 20130118
CTL_MainWindow_Title_No_Project=Autopsy 20130118
#Tue, 26 Mar 2013 10:08:52 -0400
CTL_MainWindow_Title=Autopsy 20130326
CTL_MainWindow_Title_No_Project=Autopsy 20130326

View File

@ -17,7 +17,7 @@
<os family="windows"/>
</condition>
<import file="build-${os.family}.xml"/>
<property name="test-input" location="Testing/input"/>
<property name="thirdparty.dir" value="${basedir}/thirdparty" />
<!-- import ant-contrib tools -->
@ -239,4 +239,18 @@
<target name="build-installer" depends="build-installer-dir" description="Builds Autopsy installer.">
<antcall target="build-installer-${os.family}" />
</target>
<target name="test-get-imgs" description="Get test images and store them in the path represented by the test-input variable.">
<available file="${test-input}/nps-2008-jean.E01" property="img-present"/>
<if>
<equals arg1="${img-present}" arg2="true"/>
<then>
<echo message = "Images already present."/>
</then>
<else>
<mkdir dir="${test-input}" />
<get src="http://digitalcorpora.org/corp/nps/drives/nps-2008-jean/nps-2008-jean.E01" dest="${test-input}"/>
<get src="http://digitalcorpora.org/corp/nps/drives/nps-2008-jean/nps-2008-jean.E02" dest="${test-input}"/>
</else>
</if>
</target>
</project>