diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 218c00c3aa..55d119ec78 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -28,7 +28,6 @@ import org.openide.modules.ModuleInstall; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** * Wrapper over Installers in packages in Core module This is the main @@ -40,56 +39,6 @@ public class Installer extends ModuleInstall { private static final Logger logger = Logger.getLogger(Installer.class.getName()); private volatile boolean javaFxInit = true; - static { - loadDynLibraries(); - } - - private static void loadDynLibraries() { - if (PlatformUtil.isWindowsOS()) { - try { - //on windows force loading ms crt dependencies first - //in case linker can't find them on some systems - //Note: if shipping with a different CRT version, this will only print a warning - //and try to use linker mechanism to find the correct versions of libs. - //We should update this if we officially switch to a new version of CRT/compiler - System.loadLibrary("msvcr100"); - System.loadLibrary("msvcp100"); - logger.log(Level.INFO, "MS CRT libraries loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading ms crt libraries, ", e); - } - } - - try { - System.loadLibrary("zlib"); - logger.log(Level.INFO, "ZLIB library loaded loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading ZLIB library, ", e); - } - - try { - System.loadLibrary("libewf"); - logger.log(Level.INFO, "EWF library loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading EWF library, ", e); - } - - /* We should rename the Windows dll, to remove the lib prefix. - */ - try { - String tskLibName = null; - if (PlatformUtil.isWindowsOS()) { - tskLibName = "libtsk_jni"; - } else { - tskLibName = "tsk_jni"; - } - System.loadLibrary(tskLibName); - logger.log(Level.INFO, "TSK_JNI library loaded"); - } catch (UnsatisfiedLinkError e) { - logger.log(Level.SEVERE, "Error loading tsk_jni library", e); - } - } - public Installer() { javaFxInit = true; packageInstallers = new ArrayList(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 5ecab8489f..bba2eb9100 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,7 @@ import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.JTextPane; -import javax.swing.text.Document; +import javax.swing.SwingWorker; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; import org.openide.nodes.Node; @@ -56,7 +56,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private static int currentPage = 1; private List artifacts; private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName()); - + private final static String WAIT_TEXT = "Preparing display..."; + /** Creates new form DataContentViewerArtifact */ public DataContentViewerArtifact() { initComponents(); @@ -204,12 +205,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed currentPage = currentPage+1; - setDataView(artifacts, currentPage); + new DisplayTask(currentPage).execute(); }//GEN-LAST:event_nextPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed currentPage = currentPage-1; - setDataView(artifacts, currentPage); + new DisplayTask(currentPage).execute(); }//GEN-LAST:event_prevPageButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables @@ -239,20 +240,30 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat Content content = lookup.lookup(Content.class); if (content == null) { resetComponent(); - return; + return; } - + try { - this.setDataView(content.getAllArtifacts(), 1); - } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't get artifacts: ", ex); + artifacts = content.getAllArtifacts(); + } + catch (TskException ex) { + logger.log(Level.WARNING, "Couldn't get artifacts", ex); + resetComponent(); + return; } - + // focus on a specific artifact if it is in the node + int index = 0; BlackboardArtifact artifact = lookup.lookup(BlackboardArtifact.class); if (artifact != null) { - this.setSelectedArtifact(artifact); - } + index = artifacts.indexOf(artifact); + if (index == -1) { + index = 0; + } + } + + // A little bit of cleverness here - add one since setDataView() is also passed page numbers. + new DisplayTask(index + 1).execute(); } @Override @@ -279,7 +290,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat public void resetComponent() { // clear / reset the fields currentPage = 1; - this.artifacts = new ArrayList(); + this.artifacts = new ArrayList<>(); currentPageLabel.setText(""); totalPageLabel.setText(""); outputViewPane.setText(""); @@ -311,12 +322,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat return false; } - ArtifactStringContent artifact = node.getLookup().lookup(ArtifactStringContent.class); - Content content = node.getLookup().lookup(Content.class); - - if(artifact != null) { - return true; - } + Content content = node.getLookup().lookup(Content.class); if(content != null) { try { long size = content.getAllArtifactsCount(); @@ -380,15 +386,14 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat * @param artifacts List of artifacts that could be displayed * @param offset Index into the list for the artifact to display */ - private void setDataView(List artifacts, int offset) { - // change the cursor to "waiting cursor" for this operation + private void setDataView(int offset) { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - + outputViewPane.setText(WAIT_TEXT); + if(artifacts.isEmpty()){ resetComponent(); return; } - this.artifacts = artifacts; StringContent artifactString = new ArtifactStringContent(artifacts.get(offset-1)); String text = artifactString.getString(); @@ -406,16 +411,19 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat setComponentsVisibility(true); outputViewPane.moveCaretPosition(0); this.setCursor(null); - } + } - /** - * Set the displayed artifact to the specified one. - * @param artifact Artifact to display - */ - private void setSelectedArtifact(BlackboardArtifact artifact) { - if(artifacts.contains(artifact)) { - int index = artifacts.indexOf(artifact); - setDataView(artifacts, index+1); + private class DisplayTask extends SwingWorker { + final int pageIndex; + + DisplayTask(final int pageIndex) { + this.pageIndex = pageIndex; } - } + + @Override + public Integer doInBackground() { + setDataView(pageIndex); + return 0; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form index 20985cd415..42f990a713 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form @@ -16,6 +16,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index ab36ed50c8..af1d14d312 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -43,7 +43,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; 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", ".wmv"}; + private static final String[] VIDEOS = new String[]{".swf", ".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); @@ -65,7 +65,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo initComponents(); - videoPanel = new MediaViewVideoPanel(); + // get the right panel for our platform + videoPanel = MediaViewVideoPanel.createVideoPanel(); + imagePanel = new MediaViewImagePanel(); videoPanelInited = videoPanel.isInited(); imagePanelInited = imagePanel.isInited(); @@ -75,14 +77,14 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } private void customizeComponents() { - logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); //initialize supported image types //TODO use mime-types instead once we have support String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes(); IMAGES = new String[fxSupportedImagesSuffixes.length]; + //logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) { String suffix = fxSupportedImagesSuffixes[i]; - logger.log(Level.INFO, "suffix: " + suffix); + //logger.log(Level.INFO, "suffix: " + suffix); IMAGES[i] = "." + suffix; } @@ -110,35 +112,38 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public void setNode(Node selectedNode) { - if (selectedNode == null) { - resetComponent(); - return; - } + try { + if (selectedNode == null) { + resetComponent(); + return; + } - AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class); - if (file == null) { - resetComponent(); - return; - } + AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class); + if (file == null) { + resetComponent(); + return; + } - if (file.equals(lastFile)) { - return; //prevent from loading twice if setNode() called mult. times - } else { + if (file.equals(lastFile)) { + return; //prevent from loading twice if setNode() called mult. times + } + + resetComponent(); + + 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))) { + videoPanel.setupVideo(file, dims); + switchPanels(true); + } lastFile = file; - } - - 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))) { - videoPanel.setupVideo(file, dims); - switchPanels(true); - } + } catch (Exception e) { + logger.log(Level.SEVERE, "Exception while setting node", e); + } } /** @@ -177,12 +182,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public void resetComponent() { - lastFile = null; videoPanel.reset(); + // @@@ Seems like we should also reset the image viewer... + lastFile = null; } - - @Override public boolean isSupported(Node node) { if (node == null) { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java new file mode 100644 index 0000000000..88e8cc3105 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/FXVideoPanel.java @@ -0,0 +1,642 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.corecomponents; + +import com.sun.javafx.application.PlatformImpl; +import java.awt.Component; +import java.awt.Dimension; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.embed.swing.JFXPanel; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.Slider; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.scene.media.Media; +import javafx.scene.media.MediaException; +import javafx.scene.media.MediaPlayer; +import javafx.scene.media.MediaPlayer.Status; +import static javafx.scene.media.MediaPlayer.Status.READY; +import javafx.scene.media.MediaView; +import javafx.util.Duration; +import javax.swing.BoxLayout; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.modules.ModuleInstall; +import org.openide.util.Cancellable; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +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 FXVideoPanel extends MediaViewVideoPanel { + + private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); + private boolean fxInited = false; + // FX Components + private MediaPlayer fxMediaPlayer; + private MediaPane mediaPane; + // Current media content representations + private AbstractFile currentFile; + // FX UI Components + private JFXPanel videoComponent; + + /** + * Creates new form MediaViewVideoPanel + */ + public FXVideoPanel() { + org.sleuthkit.autopsy.core.Installer coreInstaller = + ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false); + if (coreInstaller != null) { + fxInited = coreInstaller.isJavaFxInited(); + } + initComponents(); + customizeComponents(); + } + + public JPanel getVideoPanel() { + return videoPanel; + } + + public Component getVideoComponent() { + return videoComponent; + } + + private void customizeComponents() { + setupFx(); + } + + + @Override + synchronized void setupVideo(final AbstractFile file, final Dimension dims) { + currentFile = file; + final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC); + if (deleted) { + mediaPane.setInfoLabelText("Playback of deleted videos is not supported, use an external player."); + videoPanel.removeAll(); + return; + } + + String path = ""; + try { + path = file.getUniquePath(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Cannot get unique path of video file"); + } + mediaPane.setInfoLabelText(path); + mediaPane.setInfoLabelToolTipText(path); + + ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); + em.execute(); + } + + synchronized void setupFx() { + if(!fxInited) { + return; + } + logger.log(Level.INFO, "In Setup FX"); + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + mediaPane = new MediaPane(); + logger.log(Level.INFO, "Created MediaPane"); + Scene fxScene = new Scene(mediaPane); + videoComponent = new JFXPanel(); + videoComponent.setScene(fxScene); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + // Configure VideoPanel + videoPanel.removeAll(); + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(videoComponent); + videoPanel.setVisible(true); + } + }); + } + }); + } + + + @Override + void reset() { + + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + if (fxMediaPlayer != null) { + if (fxMediaPlayer.getStatus() == MediaPlayer.Status.PLAYING ) { + fxMediaPlayer.stop(); + } + fxMediaPlayer = null; + } + + if (videoComponent != null) { + videoComponent = 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; + } + + /** + * 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") + // + private void initComponents() { + + videoPanel = new javax.swing.JPanel(); + + javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); + videoPanel.setLayout(videoPanelLayout); + videoPanelLayout.setHorizontalGroup( + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 448, Short.MAX_VALUE) + ); + videoPanelLayout.setVerticalGroup( + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 248, Short.MAX_VALUE) + ); + + 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) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + }// + + // Variables declaration - do not modify + private javax.swing.JPanel videoPanel; + // End of variables declaration + + @Override + public boolean isInited() { + return fxInited; + } + + /** + * Thread that extracts Media from a Sleuthkit file representation to a + * Java file representation that the Media Player can take as input. + */ + private class ExtractMedia extends SwingWorker { + + private ProgressHandle progress; + boolean success = false; + private AbstractFile sFile; + private java.io.File jFile; + private long extractedBytes; + + ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { + this.sFile = sFile; + this.jFile = jFile; + } + + public long getExtractedBytes() { + return extractedBytes; + } + + public Media getMedia() { + return new Media(Paths.get(jFile.getAbsolutePath()).toUri().toString()); + } + + @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); + } + }); + mediaPane.setProgressLabelText("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); + } + logger.log(Level.INFO, "Done buffering: " + jFile.getName()); + 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()) { + logger.log(Level.INFO, "ExtractMedia in done: " + jFile.getName()); + try { + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + fxMediaPlayer = new MediaPlayer(getMedia()); + logger.log(Level.INFO, "Fx Media Player null? " + (fxMediaPlayer == null)); + logger.log(Level.INFO, "Media Tools null? " + (mediaPane == null)); + mediaPane.setMediaPlayer(fxMediaPlayer); + } + }); + } catch(MediaException e) { + logger.log(Level.WARNING, "something went wrong with javafx", e); + reset(); + mediaPane.setInfoLabelText(e.getMessage()); + return; + } + } + } + } + } + + private class MediaPane extends BorderPane { + private MediaPlayer mediaPlayer; + private MediaView mediaView; + private Duration duration; + private HBox mediaTools; + private HBox mediaViewPane; + private Slider progressSlider; + private Button pauseButton; + private Label progressLabel; + private Label infoLabel; + private int totalHours; + private int totalMinutes; + private int totalSeconds; + private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; + + public MediaPane() { + // Video Display + mediaViewPane = new HBox(); + mediaViewPane.setStyle("-fx-background-color: black"); + mediaViewPane.setAlignment(Pos.CENTER); + mediaView = new MediaView(); + mediaViewPane.getChildren().add(mediaView); + setAlignment(mediaViewPane, Pos.CENTER); + setCenter(mediaViewPane); + + // Media Controls + VBox controlPanel = new VBox(); + mediaTools = new HBox(); + mediaTools.setAlignment(Pos.CENTER); + mediaTools.setPadding(new Insets(5, 10, 5, 10)); + + pauseButton = new Button("►"); + mediaTools.getChildren().add(pauseButton); + mediaTools.getChildren().add(new Label(" ")); + progressSlider = new Slider(); + HBox.setHgrow(progressSlider,Priority.ALWAYS); + progressSlider.setMinWidth(50); + progressSlider.setMaxWidth(Double.MAX_VALUE); + mediaTools.getChildren().add(progressSlider); + progressLabel = new Label(); + progressLabel.setPrefWidth(130); + progressLabel.setMinWidth(50); + mediaTools.getChildren().add(progressLabel); + + controlPanel.getChildren().add(mediaTools); + controlPanel.setStyle("-fx-background-color: white"); + infoLabel = new Label(""); + controlPanel.getChildren().add(infoLabel); + setBottom(controlPanel); + setProgressActionListeners(); + } + + public void setInfoLabelText(final String text) { + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + infoLabel.setText(text); + } + }); + } + + public void setMediaPlayer(MediaPlayer mp) { + pauseButton.setDisable(true); + mediaPlayer = mp; + mediaView.setMediaPlayer(mp); + pauseButton.setDisable(false); + + setMediaActionListeners(); + } + + private void setMediaActionListeners() { + mediaPlayer.setOnReady(new Runnable() { + @Override + public void run() { + duration = mediaPlayer.getMedia().getDuration(); + long durationInMillis = (long) fxMediaPlayer.getMedia().getDuration().toMillis(); + + // pick out the total hours, minutes, seconds + long durationSeconds = (int) durationInMillis / 1000; + totalHours = (int) durationSeconds / 3600; + durationSeconds -= totalHours * 3600; + totalMinutes = (int) durationSeconds / 60; + durationSeconds -= totalMinutes * 60; + totalSeconds = (int) durationSeconds; + updateProgress(); + } + }); + + mediaPlayer.setOnEndOfMedia(new Runnable() { + @Override + public void run() { + Duration beginning = mediaPlayer.getStartTime(); + mediaPlayer.stop(); + mediaPlayer.pause(); + pauseButton.setText("►"); + updateSlider(beginning); + updateTime(beginning); + } + }); + + mediaPlayer.currentTimeProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Duration oldValue, Duration newValue) { + updateSlider(newValue); + updateTime(newValue); + } + }); + } + + private void setProgressActionListeners() { + pauseButton.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent e) { + Status status = mediaPlayer.getStatus(); + + switch (status) { + // If playing, pause + case PLAYING: + pauseButton.setText("►"); + mediaPlayer.pause(); + break; + // If ready, paused or stopped, continue playing + case READY: + case PAUSED: + case STOPPED: + pauseButton.setText("||"); + mediaPlayer.play(); + break; + default: + break; + } + } + }); + + progressSlider.valueProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable o) { + if (progressSlider.isValueChanging()) { + mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0)); + } + } + }); + } + + private void updateProgress() { + Duration currentTime = mediaPlayer.getCurrentTime(); + updateSlider(currentTime); + updateTime(currentTime); + } + + private void updateSlider(Duration currentTime) { + if (progressSlider != null) { + progressSlider.setDisable(duration.isUnknown()); + if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO) + && !progressSlider.isValueChanging()) { + progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0); + } + } + } + + private void updateTime(Duration currentTime) { + long millisElapsed = (long) currentTime.toMillis(); + + long elapsedHours, elapsedMinutes, elapsedSeconds; + // 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); + } + + private void setProgressLabelText(final String text) { + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + progressLabel.setText(text); + } + }); + } + + private void setInfoLabelToolTipText(final String text) { + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + infoLabel.setTooltip(new Tooltip(text)); + } + }); + } + } + + /** + * @param file a video file from which to capture frames + * @param numFrames the number of frames to capture. These frames will be + * captured at successive intervals given by durationOfVideo/numFrames. If + * this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one + * frame will be captured and returned. + * @return a List of VideoFrames representing the captured frames. + */ + @Override + public List captureFrames(java.io.File file, int numFrames) throws Exception { +// +// try { +// List frames = new ArrayList<>(); +// +// FrameCapturer fc = new FrameCapturer(file); +// logger.log(Level.INFO, "Fc is null? " + (fc == null)); +// frames = fc.getFrames(numFrames); +// +// return frames; +// } +// catch (NullPointerException e) { +// e.printStackTrace(); +// return null; +// } + return null; + } + +// private class FrameCapturer { +// +// private MediaPlayer mediaPlayer; +// private JFXPanel panel; +// private boolean isReady = false; +// +// FrameCapturer(java.io.File file) { +// initFx(file); +// } +// +// boolean isReady() { +// return isReady; +// } +// +// private void initFx(final java.io.File file) { +// PlatformImpl.runAndWait(new Runnable() { +// @Override +// public void run() { +// logger.log(Level.INFO, "In initFX."); +// // Create Media Player with no video output +// Media media = new Media(Paths.get(file.getAbsolutePath()).toUri().toString()); +// mediaPlayer = new MediaPlayer(media); +// MediaView mediaView = new MediaView(mediaPlayer); +// mediaView.setStyle("-fx-background-color: black"); +// Pane mediaViewPane = new Pane(); +// mediaViewPane.getChildren().add(mediaView); +// Scene scene = new Scene(mediaViewPane); +// panel = new JFXPanel(); +// panel.setScene(scene); +// isReady = true; +// } +// }); +// } +// +// List getFrames(int numFrames) { +// logger.log(Level.INFO, "in get frames"); +// List frames = new ArrayList(0); +// +// if (mediaPlayer.getStatus() != Status.READY) { +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { +// return frames; +// } +// } +// +// // get the duration of the video +// long myDurationMillis = (long) mediaPlayer.getMedia().getDuration().toMillis(); +// if (myDurationMillis <= 0) { +// return frames; +// } +// +// // calculate the frame interval +// int numFramesToGet = numFrames; +// long frameInterval = (myDurationMillis - INTER_FRAME_PERIOD_MS) / numFrames; +// if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { +// numFramesToGet = 1; +// } +// +// final Object frameLock = new Object(); +// BufferedImage frame; +// final int width = (int) panel.getSize().getWidth(); +// final int height = (int) panel.getSize().getHeight(); +// // for each timeStamp, grap a frame +// for (int i = 0; i < numFramesToGet; ++i) { +// frame = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); +// logger.log(Level.INFO, "Grabbing a frame..."); +// final long timeStamp = i * frameInterval + INTER_FRAME_PERIOD_MS; +// +// // PlatformImpl.runLater(new Runnable() { +// // @Override +// // public void run() { +// // synchronized (frameLock) { +// logger.log(Level.INFO, "seeking."); +// mediaPlayer.seek(new Duration(timeStamp)); +// // } +// // } +// // }); +// +// synchronized (frameLock) { +// panel.paint(frame.createGraphics()); +// logger.log(Level.INFO, "Adding image to frames"); +// } +// frames.add(new VideoFrame(frame, timeStamp)); +// } +// return frames; +// } +// } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java new file mode 100644 index 0000000000..1eea6fb84d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GstVideoPanel.java @@ -0,0 +1,770 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.corecomponents; + +import java.awt.Dimension; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +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.StateChangeReturn; +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.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.casemodule.Case; +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; + + +@ServiceProviders(value = { + @ServiceProvider(service = FrameCapture.class) +}) +public class GstVideoPanel extends MediaViewVideoPanel { + + private static final Logger logger = Logger.getLogger(GstVideoPanel.class.getName()); + private boolean gstInited; + private static final long MIN_FRAME_INTERVAL_MILLIS = 500; + private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000; + private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file."; + //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; + private Set badVideoFiles = Collections.synchronizedSet(new HashSet()); + + /** + * Creates new form MediaViewVideoPanel + */ + public GstVideoPanel() { + 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; + } + + @Override + public boolean isInited() { + return gstInited; + } + + private void customizeComponents() { + if (!initGst()) { + return; + } + + progressSlider.setEnabled(false); // disable slider; enable after user plays vid + progressSlider.setValue(0); + + 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(); + if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + 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; + } + + @Override + void setupVideo(final AbstractFile file, final Dimension dims) { + infoLabel.setText(""); + 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; + } + + String path = ""; + try { + path = file.getUniquePath(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Cannot get unique path of video file"); + } + infoLabel.setText(path); + infoLabel.setToolTipText(path); + pauseButton.setEnabled(true); + progressSlider.setEnabled(true); + + 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); + + if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + } + } + + } + + @Override + void reset() { + + // reset the progress label text on the event dispatch thread + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressLabel.setText(""); + } + }); + + if (!isInited()) { + return; + } + + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + if (gstPlaybin2.isPlaying()) { + if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + } + if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + if (gstPlaybin2.getState().equals(State.NULL)) { + gstPlaybin2.dispose(); + } + gstPlaybin2 = null; + } + gstVideoComponent = null; + } + + // 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; + } + + /** + * @param file a video file from which to capture frames + * @param numFrames the number of frames to capture. These frames will be + * captured at successive intervals given by durationOfVideo/numFrames. If + * this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one + * frame will be captured and returned. + * @return a List of VideoFrames representing the captured frames. + */ + @Override + public List captureFrames(java.io.File file, int numFrames) throws Exception { + + List frames = new ArrayList<>(); + + Object lock = new Object(); + FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock); + + if (!isInited()) { + return frames; + } + + // throw exception if this file is known to be problematic + if (badVideoFiles.contains(file.getName())) { + throw new Exception("Cannot capture frames from this file (" + file.getName() + ")."); + } + + // set up a PlayBin2 object + RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener); + PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); + playbin.setInputFile(file); + playbin.setVideoSink(videoSink); + + // this is necessary to get a valid duration value + StateChangeReturn ret = playbin.play(); + if (ret == StateChangeReturn.FAILURE) { + // add this file to the set of known bad ones + badVideoFiles.add(file.getName()); + throw new Exception("Problem with video file; problem when attempting to play while obtaining duration."); + } + ret = playbin.pause(); + if (ret == StateChangeReturn.FAILURE) { + // add this file to the set of known bad ones + badVideoFiles.add(file.getName()); + throw new Exception("Problem with video file; problem when attempting to pause while obtaining duration."); + } + playbin.getState(); + + // get the duration of the video + TimeUnit unit = TimeUnit.MILLISECONDS; + long myDurationMillis = playbin.queryDuration(unit); + if (myDurationMillis <= 0) { + return frames; + } + + // calculate the number of frames to capture + 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; + + ret = playbin.pause(); + if (ret == StateChangeReturn.FAILURE) { + // add this file to the set of known bad ones + badVideoFiles.add(file.getName()); + throw new Exception("Problem with video file; problem when attempting to pause while capturing a frame."); + } + playbin.getState(); + + //System.out.println("Seeking to " + timeStamp + "milliseconds."); + if (!playbin.seek(timeStamp, unit)) { + logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); + } + + ret = playbin.play(); + if (ret == StateChangeReturn.FAILURE) { + // add this file to the set of known bad ones + badVideoFiles.add(file.getName()); + throw new Exception("Problem with video file; problem when attempting to play while capturing a frame."); + } + + // wait for FrameCaptureRGBListener to finish + synchronized(lock) { + try { + lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e); + } + } + Image image = rgbListener.getImage(); + + ret = playbin.stop(); + if (ret == StateChangeReturn.FAILURE) { + // add this file to the set of known bad ones + badVideoFiles.add(file.getName()); + throw new Exception("Problem with video file; problem when attempting to stop while capturing a frame."); + } + + if (image == null) { + logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName()); + badVideoFiles.add(file.getName()); + break; + } + + frames.add(new VideoFrame(image, timeStamp)); + } + + return frames; + } + + private class FrameCaptureRGBListener implements RGBDataSink.Listener { + + public FrameCaptureRGBListener(Object waiter) { + this.waiter = waiter; + } + + private BufferedImage bi; + private final Object waiter; + + @Override + public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) { + synchronized (waiter) { + bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w); + waiter.notify(); + } + } + + public Image getImage() { + synchronized (waiter) { + Image image = bi; + bi = null; + return image; + } + } + + } + + /** + * 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") + // + 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()) + ); + }// + + private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) { + synchronized (playbinLock) { + if (gstPlaybin2 == null) { + infoLabel.setText("Error: Playbin is null"); + return; + } + State state = gstPlaybin2.getState(); + if (state.equals(State.PLAYING)) { + if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + pauseButton.setText("►"); + // Is this call necessary considering we just called gstPlaybin2.pause()? + if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + } else if (state.equals(State.PAUSED)) { + if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + pauseButton.setText("||"); + // Is this call necessary considering we just called gstPlaybin2.play()? + if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + } else if (state.equals(State.READY)) { + ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); + em.execute(); + em.getExtractedBytes(); + } + } + } + // Variables declaration - do not modify + 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 + + private class VideoProgressWorker extends SwingWorker { + + 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 hadError = false; + + private boolean isPlayBinReady() { + synchronized (playbinLock) { + return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL); + } + } + + private void resetVideo() throws Exception { + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + } + // ready to be played again + if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + } + 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 { + + 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) { + // must play, then pause and get state to get duration. + if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + return; + } + 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) { + if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { + logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); + infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); + } + } + pauseButton.setText("||"); + videoProgressWorker = new VideoProgressWorker(); + videoProgressWorker.execute(); + } + }); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form deleted file mode 100644 index ceab29b1e8..0000000000 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form +++ /dev/null @@ -1,113 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index da1ca268d5..b76724c08b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -19,753 +19,100 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Dimension; -import java.awt.Image; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.IntBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; +import java.util.Arrays; import java.util.logging.Level; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; +import java.util.logging.Logger; 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.StateChangeReturn; -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.lookup.ServiceProvider; -import org.openide.util.lookup.ServiceProviders; -import org.sleuthkit.autopsy.casemodule.Case; -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 + * Video viewer part of the Media View layered pane. + * Uses different engines depending on platform. */ -@ServiceProviders(value = { - @ServiceProvider(service = FrameCapture.class) -}) -public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { - +public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture { + private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); - private boolean gstInited; - private static final long MIN_FRAME_INTERVAL_MILLIS = 500; - private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000; - private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file."; - //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; - private Set badVideoFiles = Collections.synchronizedSet(new HashSet()); + + // 64 bit architectures + private static final String[] ARCH64 = new String[]{"amd64", "x86_64"}; + + // 32 bit architectures + private static final String[] ARCH32 = new String[]{"x86"}; + + // A Gstreamer implementation of MediaViewVideoPanel + private static GstVideoPanel gstVideoPanel = null; + + // A JavaFX implmentation of MediaViewVideoPanel + private static FXVideoPanel fxVideoPanel = null; + + /** + * Factory Method to create a MediaViewVideoPanel. + * + * Implementation is dependent on the architecture of the JVM. + * + * @return a MediaViewVideoPanel instance. + */ + public static MediaViewVideoPanel createVideoPanel() { + if (is64BitJVM()) { + logger.log(Level.INFO, "64 bit JVM detected. Creating JavaFX Video Player."); + return getFXImpl(); + } else { + logger.log(Level.INFO, "32 bit JVM detected. Creating GStreamer Video Player."); + return getGstImpl(); + } + } + + /** + * Is the JVM architecture 64 bit? + * + * @return + */ + private static boolean is64BitJVM() { + String arch = System.getProperty("os.arch"); + return Arrays.asList(ARCH64).contains(arch); + } /** - * Creates new form MediaViewVideoPanel + * Get a GStreamer video player implementation. + * + * @return a GstVideoPanel */ - 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() { - if (!initGst()) { - return; + private static MediaViewVideoPanel getGstImpl() { + if (gstVideoPanel == null) { + gstVideoPanel = new GstVideoPanel(); } - - progressSlider.setEnabled(false); // disable slider; enable after user plays vid - progressSlider.setValue(0); - - 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(); - if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - gstPlaybin2.setState(orig); - } - } - } - }); + return gstVideoPanel; } - 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; + /** + * Get a JavaFX video player implementation. + * + * @return a FXVideoPanel + */ + private static MediaViewVideoPanel getFXImpl() { + if (fxVideoPanel == null) { + fxVideoPanel = new FXVideoPanel(); } - - return true; + return fxVideoPanel; } - + + /** + * Has this MediaViewVideoPanel been initialized correctly? + * + * @return + */ + public abstract boolean isInited(); + + /** + * Prepare this MediaViewVideoPanel to accept a different media file. + */ + abstract void reset(); + /** * 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) { - infoLabel.setText(""); - 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; - } - - String path = ""; - try { - path = file.getUniquePath(); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Cannot get unique path of video file"); - } - infoLabel.setText(path); - infoLabel.setToolTipText(path); - pauseButton.setEnabled(true); - progressSlider.setEnabled(true); - - 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); - - if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } - } - - } - - void reset() { - - // reset the progress label text on the event dispatch thread - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressLabel.setText(""); - } - }); - - if (!isInited()) { - return; - } - - synchronized (playbinLock) { - if (gstPlaybin2 != null) { - if (gstPlaybin2.isPlaying()) { - if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - } - if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - if (gstPlaybin2.getState().equals(State.NULL)) { - gstPlaybin2.dispose(); - } - gstPlaybin2 = null; - } - gstVideoComponent = null; - } - - // 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; - } - - /** - * @param file a video file from which to capture frames - * @param numFrames the number of frames to capture. These frames will be - * captured at successive intervals given by durationOfVideo/numFrames. If - * this frame interval is less than MIN_FRAME_INTERVAL_MILLIS, then only one - * frame will be captured and returned. - * @return a List of VideoFrames representing the captured frames. - */ - @Override - public List captureFrames(java.io.File file, int numFrames) throws Exception { - - List frames = new ArrayList<>(); - - Object lock = new Object(); - FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock); - - if (!isInited()) { - return frames; - } - - // throw exception if this file is known to be problematic - if (badVideoFiles.contains(file.getName())) { - throw new Exception("Cannot capture frames from this file (" + file.getName() + ")."); - } - - // set up a PlayBin2 object - RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener); - PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); - playbin.setInputFile(file); - playbin.setVideoSink(videoSink); - - // this is necessary to get a valid duration value - StateChangeReturn ret = playbin.play(); - if (ret == StateChangeReturn.FAILURE) { - // add this file to the set of known bad ones - badVideoFiles.add(file.getName()); - throw new Exception("Problem with video file; problem when attempting to play while obtaining duration."); - } - ret = playbin.pause(); - if (ret == StateChangeReturn.FAILURE) { - // add this file to the set of known bad ones - badVideoFiles.add(file.getName()); - throw new Exception("Problem with video file; problem when attempting to pause while obtaining duration."); - } - playbin.getState(); - - // get the duration of the video - TimeUnit unit = TimeUnit.MILLISECONDS; - long myDurationMillis = playbin.queryDuration(unit); - if (myDurationMillis <= 0) { - return frames; - } - - // calculate the number of frames to capture - 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; - - ret = playbin.pause(); - if (ret == StateChangeReturn.FAILURE) { - // add this file to the set of known bad ones - badVideoFiles.add(file.getName()); - throw new Exception("Problem with video file; problem when attempting to pause while capturing a frame."); - } - playbin.getState(); - - //System.out.println("Seeking to " + timeStamp + "milliseconds."); - if (!playbin.seek(timeStamp, unit)) { - logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); - } - - ret = playbin.play(); - if (ret == StateChangeReturn.FAILURE) { - // add this file to the set of known bad ones - badVideoFiles.add(file.getName()); - throw new Exception("Problem with video file; problem when attempting to play while capturing a frame."); - } - - // wait for FrameCaptureRGBListener to finish - synchronized(lock) { - try { - lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS); - } catch (InterruptedException e) { - logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e); - } - } - Image image = rgbListener.getImage(); - - ret = playbin.stop(); - if (ret == StateChangeReturn.FAILURE) { - // add this file to the set of known bad ones - badVideoFiles.add(file.getName()); - throw new Exception("Problem with video file; problem when attempting to stop while capturing a frame."); - } - - if (image == null) { - logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName()); - badVideoFiles.add(file.getName()); - break; - } - - frames.add(new VideoFrame(image, timeStamp)); - } - - return frames; - } - - private class FrameCaptureRGBListener implements RGBDataSink.Listener { - - public FrameCaptureRGBListener(Object waiter) { - this.waiter = waiter; - } - - private BufferedImage bi; - private final Object waiter; - - @Override - public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) { - synchronized (waiter) { - bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); - bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w); - waiter.notify(); - } - } - - public Image getImage() { - synchronized (waiter) { - Image image = bi; - bi = null; - return image; - } - } - - } - - /** - * 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") - // //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()) - ); - }// //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)) { - if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - pauseButton.setText("►"); - // Is this call necessary considering we just called gstPlaybin2.pause()? - if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - } else if (state.equals(State.PAUSED)) { - if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - pauseButton.setText("||"); - // Is this call necessary considering we just called gstPlaybin2.play()? - if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - } 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 { - - 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 hadError = false; - - private boolean isPlayBinReady() { - synchronized (playbinLock) { - return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL); - } - } - - private void resetVideo() throws Exception { - synchronized (playbinLock) { - if (gstPlaybin2 != null) { - if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } - // ready to be played again - if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } - 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 { - - 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) { - // must play, then pause and get state to get duration. - if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - return; - } - 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) { - if (gstPlaybin2.play() == StateChangeReturn.FAILURE) { - logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); - infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); - } - } - pauseButton.setText("||"); - videoProgressWorker = new VideoProgressWorker(); - videoProgressWorker.execute(); - } - }); - } - } + abstract void setupVideo(final AbstractFile file, final Dimension dims); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java index 8fc05094df..1cfdae3774 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractKeywordSearchPerformer.java @@ -83,13 +83,22 @@ abstract class AbstractKeywordSearchPerformer extends javax.swing.JPanel impleme @Override public void search() { + boolean isRunning = IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault()); + if (filesIndexed == 0) { - KeywordSearchUtil.displayDialog("Keyword Search Error", "No files are indexed, please index an image before searching", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + if (isRunning) { + KeywordSearchUtil.displayDialog("Keyword Search Error", "No files are in index yet.
" + + "Try again later. Index is updated every " + KeywordSearchSettings.getUpdateFrequency().getTime() + " minutes.", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + } + else { + KeywordSearchUtil.displayDialog("Keyword Search Error", "No files were indexed.
" + + "Re-ingest the image with the Keyword Search Module enabled. ", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + } return; } //check if keyword search module ingest is running (indexing, etc) - if (IngestManager.getDefault().isModuleRunning(KeywordSearchIngestModule.getDefault())) { + if (isRunning) { if (KeywordSearchUtil.displayConfirmDialog("Keyword Search Ingest in Progress", "Keyword Search Ingest is currently running.
" + "Not all files have been indexed and this search might yield incomplete results.
" diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java index 42576cba74..1f77cdcc47 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java @@ -43,7 +43,7 @@ import org.apache.solr.client.solrj.SolrServerException; import org.sleuthkit.autopsy.casemodule.Case; /** - * Keyword search toolbar which allows to search for single terms or phrases + * Keyword search toolbar (in upper right, by default) which allows to search for single terms or phrases * * The toolbar uses a different font from the rest of the application, Monospaced 14, * due to the necessity to find a font that displays both Arabic and Asian fonts at an acceptable size. diff --git a/NEWS.txt b/NEWS.txt index 178b8136fc..bae082a63e 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,784 +1,794 @@ ----------------- VERSION Current (development) -------------- - -New features: - - -Improvements: - - -Bugfixes: - - - ----------------- VERSION 3.0.6 -------------- - -New features: -- Logical files and folders support -- New file views in directory tree to view: deleted, executable, archive files and files by size -- ext4 and yaffs2 support (via TSK 4.1.0) - -Improvements: -- Improvements to tagging of files and keyword search results -- Any file and folder can be selectively ingested using the directory tree view - -Bugfixes: -- Keyword Search: fix when Solr does not cleanly shutdown -- fix for "Process Unallocated Space" option doesn't do anything -- fixed result viewer for "File Search by MD5 Hash" -- fix Solr, Timeline and RecentActivity issues with java 7.0.21 -- Views->Recent Files showing inconsistent results when clicked many times -- reduced memory usage in Timeline - - ----------------- VERSION 3.0.5 -------------- - -New features: -- Archive extractor ingest module (uses 7zip) -- Timeline (Beta) - -Improvements: -- Sleuthkit-4.0.2 and libewf-20130128 -- improved image loading in Media View and Thumbnail View (faster loading, handles large files better) -- improve Keyword Search file indexing (use detected mime-type instead of file extension) -- exif module - better jpeg detection using signature and not only file extension. -- show children counts in directory tree -- Ingest Message Inbox showing which messages are new better - -Bugfixes: -- fixed memory leaks in "Add Image" -- 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. - ----------------- VERSION 3.0.4 -------------- - -New features: -- Results and files can be tagged with custom tags and reported on them. -- New notification area for error reporting (bottom right). - -Improvements: -- Tweaked memory settings to eliminate out-of-memory errors. -- Faster application launch time. -- Netbeans RCP upgrade from 7.2.1 to 7.3 -- Upgrade from Java 6 to Java 7 - -Bugfixes: -- fixed DLL dependency version issue causing Autopsy not to launch on some systems -- fixed bug when keyword search ingest would search also images previously ingested, creating duplicate results -- fixed crash and hang in html and excel report generation, due to special characters present -- fixed cancellation when creating file or result bookmark -- fixed text not being extracted and searched from all MS Office documents (such as docx, xlsx and pptx extensions) -- fixed Exif meta-data extraction in Exif ingest module - - ----------------- VERSION 3.0.3 -------------- - -*Note: Due to major changes in Keyword search module indexing this release is not fully backward compatible. -As a workaround, you will need to rebuild index by re-running Keyword Search ingest on Cases created with previous versions. - -Improvements: -- Upgrade to Solr4.0 / Tika 1.2: Improved performance and highlighting -- Remake of reporting UI and functionality -- Significant increase in reporting speed -- New option to keep the most specific file viewer (default) or the lastly used viewer active. - - -Bugfixes: -- Fixed bug that caused the ends of large amounts of text to not be indexed (occurs mostly in unallocated space). -- Fix scrolling to first keyword hit when Text View is first loaded -- Imported keyword lists are now always enabled for ingest by default - - ----------------- VERSION 3.0.2 -------------- - -New features: -- Extraction of all unallocated blocks as a single file -- Results bookmarks with comments and basic bookmark reporting -- Hashkeeper hash database support - -Improvements: -- File Ingest: minimized file queuing time and memory usage, also improving ingest stability -- Jump to arbitrary page in Thumbnail View -- Add Image Wizard - better work-flow, better device size reporting, info on currently processed directory -- Reporting: reorganized columns, sorted by 1st column, added logo, better styling - -Bugfixes: -- fixed periodic keyword search during ingest, when it would only search max. 2 times -- fixed Downloads "target" in Recent Activity -- fixed missing hash and keyword search hits in reports -- fixed deselecting NSRL database for hash ingest - - ----------------- VERSION 3.0.1 -------------- - -New features: -- Physical and logical disk devices discovery in Add image wizard - -Improvements: -- Significant performance improvements when adding images. -- Slight improvements in UI performance for large number of results. -- Improved stability when running ingest on multiple images. -- Removed limit on number of results displayed. -- Thumbnail viewer - added paging and removed limit of images. -- Better HTML report navigation, handling large reports better. -- Netbeans RCP upgrade from 7.2 to 7.2.1 -- Build scripts enhancements to include module version tracking. - -Bugfixes: -- Fixed reading content from multiple file attributes (NTFS, HFS). -- Add Extract action to Unalloc content file nodes (per file). -- Fixes bugs with case re-opening. -- UI fix for keyword search box when case is changed. -- Enable user to select any image file extension when opening image. -- Thunderbird parser module fixes. -- Reporting fixes: added missing artifacts (keyword search, hash hits, file bookmarks). - - ----------------- VERSION 3.0.0 -------------- -New features: -- Using Sleuthkit 4.0.0 -- Integrated plugin installer. -- New options menu to globally access module options. -- Added custom ingest module loader and ingest module auto-discovery - -Improvements: -- Updated ingest framework APIs. -- Merged the main modules into Autopsy-Core and Autopsy-CoreLibs. -- Improved logging infrastructure. -- Improved configuration infrastructure. -- Keyword search: upgraded Lucene from 34 to 36. -- Build system improvements. -- Updated documentation. - -Bugfixes: -- UI selection fix in Content and Result viewer -- UI fixes in Hash Database and Keyword Search options. -- Excel report export produced corrupt files sometimes. -- Fix for Keyword Search sometimes not property initializing when application starts. - -3.0.0b5 (September 12, 2012) -New features: -- Added international string extraction from unknown file types. -- Removed size limitations of large files for keyword searching. -- Added full html parsing and extraction (including comments, scripts, meta tags, etc). -- Added support for indexing and searching disk images that have no volume and file system. -- Solr (3.6.1) and Tika (1.0) upgrade. -- Search a file by hash GUI feature and search other files with same hash. -- Web search query text extraction from popular search engines. -- Exif metadata extraction from jpeg files. -- Netbeans RCP platform upgrade (7.2). -- Basic file bookmarks support. -- Body file report. -- Improved UI. -- Updated Ingest Module API. - -Bugfixes: -- Keyword search memory usage improvements. -- Directory tree now shows which directories have no children before user clicks. -- Fixed bug when recent cases would not get updated. -- Fixed a bug when sometimes a case would get deleted. -- Fixed occasional Media View crashes. - -3.0.0b4 (June 29, 2012) -Funded by US Army Intelligence Center of Excellence (USAICoE): -New Features: -- MBOX parsing -- Better lnk file parsing -Bug Fixes: -- Included needed jar file for Recent Activity (Issue #52). -- Fixed error handling from ingest (Issue #53). - -3.0.0b3 (June 12, 2012) -New Features (Funded by US Army Intelligence Center of Excellence (USAICoE)): -- Ingest manager runs triage/ingest task after disk is added. -- Basic keyword search (indexed via SOLR) -- Recent activity extract (web artifacts, recent documents, devices, etc.) -- Improved UI - -3.0.0b2 (Nov 9, 2011) -New Features: -- New database design -- Hashlookup / calculation -- Minor overall improvements -- NOTE: Cases created with b1 are not supported in b2 (different DB) - -3.0.0b1 (Aug 16, 2011) -- Initial release -- Windows only -- Directory tree -- File Search -- Table and thumbnail viewer - ---------------------------- Version 2.24 -------------------------------- -3/22/10: Bug Fix: resolved issue 2950986 to support HFS directories. - ---------------------------- Version 2.23 -------------------------------- -2/12/10: bug fix: resolved issue 2950693 where previous searches -were not shown if they used quotes. - -2/12/10: bug fix: resolved issue 2932385 where wrong flag was being used -to do only doing category searching" - -2/12/10: bug fix: resolved issue 2779244 where wrong sorter path was -being used. - ---------------------------- Version 2.22 -------------------------------- -10/27/09: Update: Change istat to use -B instead of -b (new change in TSK). - -11/19/09: Update: Improved configure script process and error message for -FILE_EXE check. - -11/25/09: Fixed MD5 exe bug when building live CD - -12/30/09: Fixed issue 2923857 re: cookie errors for the icon and css file -links when cookies are used. - ---------------------------- Version 2.21 -------------------------------- -11/7/08: Bug Fix: Changed case management code to not error when 'dls ...' -line was encountered. - -11/14/08: Bug Fix: Fixed bug 2288406 (parsing of new fls -l format when file name searching and deleted file listing) - ---------------------------- Version 2.20 -------------------------------- -7/1/08: Update: Updated FAT sizes based on new "special" files. - -7/9/08: Update: Updated NTFS processing for orphan files / removed -ifind -p etc. - -7/9/08: Update: Updated mactime and time formats to ISO formats. - -9/13/08: Update: Changed usage to new TSK d* to blk* names. - -9/26/08: Bug Fix: Input check on host was printing invalid host values -w/out encoding HTML entities. Reported by Russ McRee. - -10/01/08: Update: HFS support is enabled if TSK was compiled with -support for it. - -10/08/08: Bug Fix: Added some more HTML entity escaping to case management -values (such as description). Reported by Daniel Medianero. - -10/13/08: Update: Added perl version check back into configure, but used -perl $] variable to do checking. Based on patch by Joerg Friedrich. - ---------------------------- Version 2.10 -------------------------------- -2/20/08: Bug Fix: Added 'tsk' to the path for sorter to find the 'images' -config file. Reported by Russell Reynolds. - -3/2/08: Update: Modified the adding of disk image process to save a -call to mmls (reported by Pope). - -3/2/08: Update: Added more basic control char filtering back into Print(). - ---------------------------- Version 2.09 -------------------------------- -2/4/07: Update: Bind only to localhost network if remote addr is local. -Suggested by Markus Waldeck. - -4/19/07: Bug Fix: Event sequencer notes for file did not have clock skew -in the times. Reported by Len CulBreath. - -12/21/07: Update: updated configure and install process for TSK 2.50 - -1/28/08: Update: Added NSRL support back in. - ---------------------------- Version 2.08 -------------------------------- - -8/23/06: Bug Fix: The configure script did not like TSK directory names -with a space in them. - -8/23/06: Update: The PATH variable is not entirely cleared anymore. -Instead, it is replaced by the basic bin directories (this was causing -some problems with Cygwin). - -8/31/06: Update: If Autopsy is running under Cygwin, then it will set -the PATH to contain the basic bin directories. Otherwise, it is clear -(original behavior). - - ---------------------------- Version 2.07 -------------------------------- -3/15/06: Bug Fix: Caseman.pm had DATA_DIR instead of DATADIR and a -concatenation error message occurred. Reported by Jason DePriest. - -5/3/06: Update: Added support for ISO9660 file systems. - -5/3/06: Update: Added support for AFF and AFD image formats. - -5/03/06: Update: Added image format type to image details screen. - -5/3/06: Update: Added hexdump view for file analysis and reports (initial -patch by Patrick Knight). - -5/3/06: Update: Changed number of dashes in reports to 70 instead of 62. - -5/4/06: Update: Integrity checking disabled for non-raw image files -until a specialized tool exists in TSK to abstract the embedded hash -calculation. - -5/8/06: Update: Added support for AFM files. - - - ---------------------------- Version 2.06 -------------------------------- -05/02/05: Fix: Typo in timeline creation window (reported by Surago Jones). - -06/15/05: Update: Added css style sheet and changed some formatting. - -08/13/05: Update: Added "utf-8" as HTML type so that TSK unicode -output will be properly dispayed. - -10/13/05: Update: Removed print_output() function contents because -it broke the Unicode chars. - -10/13/05: Update: Require 5.8 version of Perl now (in config and -in source) because it has best Unicode support. - - - ---------------------------- Version 2.05 -------------------------------- -03/16/05: Update: Image name is given in the Image Details window -when adding a new image file. (Suggested by Surago Jones). - -03/17/05: Bug Fix: swap and raw host config entries could not be -read after the conversion because of a regular expression bug in -the read code. (Reported by Surago Jones) (BUG: 1165235) - -03/21/05: Bug Fix: When a new host was added to a case with no -investigator names, then it would prompt you to select a name from -an empty list. (BUG: 1167970). - -03/25/05: Update: Check return status of rename functions and print -error if failed. - -04/04/05: Bug Fix: A missing volume type message was reported when -adding a disk image. The flow of add_img_prep was modified to -ensure that it was set. (Reported by Bradley Bitzkowski) (BUG: -1177042) - -04/08/05: Update: A thumbnail of images is shown when selected in the File -mode. Suggested by and patch by Guy Voncken. - - ---------------------------- Version 2.04 -------------------------------- -10/22/04: Update: Changed the way that NTFS lists directory contents. No - longer lists the deleted entries from 'fls', only from 'ifind'. Reduces - the inaccurate information. - -02/XX/05: Update: Incorporated new TSK 2 features: - - Disk images (split and raw) - - new config file formats - - moved images and output md5.txt file into one - -03/01/05: Update: Changed behavior of some links that created new -Autopsy Windows - -03/05/05: Update: timeline output can be in comma delimited format - -03/05/05: Update: Added SSN and credit card seach patterns from - Jerry Shenk. - -03/05/05: Update: Added temporal data when a note is creaed. - -03/11/05: Update: Changed to new TSK names for srch_strings and img_stat - -03/15/05: Update: improved handling of white space around investigator -names and image names (suggested by Brian Baskin). - - ---------------------------- Version 2.03 -------------------------------- -08/24/04: Update: Added SHA-1 hash to the metadata view. - -09/01/04: Update: Added sstrings instead of local version of strings. - -09/05/04: Update: Added more help text. - -09/06/04: Update: Use the local version of file if TSK version is -not found. - -09/06/04: Update: Added links to the notes and events page after a -note or event has been created. - -09/06/04: Update: Added Unicode extract and search functionality using -the 'sstrings' tool from TSK. - - ---------------------------- Version 2.02 -------------------------------- -07/19/04: Bug Fix: print_err message in Caseman.lib did not have correct -Print:: package, which caused an error (BUG: 994199). - -07/29/04: Update: Added support for NTFS 'ifind -p' option to find deleted -files that do not have a name in the parent directory. - -07/29/04: Update: Added a filter to remove duplicate entries from a file -listing. Duplicate names with the same name and meta address are -removed. - -07/29/04: Update: OS X no longer needs the strings script, Autopsy -will adjust for the different flags. - -07/29/04: Update: When a deleted file name is entered into the find -directory box, the recover bit is set so the full contents are shown. - - ---------------------------- Version 2.01 -------------------------------- -03/29/04: Update: Changed text for the data integrity option when -adding a new image. - -04/20/04: Bug Fix: Fixed error that occured when data browsing with -a raw or swap image. The TSK usage for these file system types was -inconsistent and it was fixed in version 1.69. (BUG: 925382). -(Reported by Harald Katzer) - -05/03/04: Update: Changed regular expression in META so that the -new recovery listing in FAT istat will not show up as a hyperlink. - -05/03/04: Update: Removed usage of '-H' with 'icat' in File.PM. - -05/20/04: Bug Fix: Fixed the incorrect error message that was printed -when installing autopsy with a newer version of TSK than 1.68. -(BUG: 938909) - -05/20/04: Update: Added new feature that allows perl regular -expressions to be used to find file names. - -05/20/04: Update: Added file recovery features to File.pm, Meta.pm, -and Appview.pm. - -05/27/04: Update: Added a space to $REG_ZONE2 so that CYGWIN would -work if no zone was given (Marcus Muller). - -/05/27/04: Update: Added 'p' as an option for the type of a file in the -'fls' output and made the $::REG_MTYPE global for the pattern. - -05/28/04: Update: Cleaned up code so that commands and directories -do not have double slashes (//) sometimes. This caused problems -with CYGWIN (reported by Marcus Muller). - -05/28/04: Bug Fix: Keyword search of unallocated space would link to -incorrect data unit (although the address was correct). (Reported by -Jorge Ortiz, David Perez, Raul Siles). (BUG: 962410). - -05/28/04: Update: Updated dcat usage and syntax to reflect changes to -TSK. - -05/28/04: Update: Changed the messages printed when multiple data units -were displayed. Now the number of units or range are given instead of -number of bytes. - - ---------------------------- Version 2.00 -------------------------------- -11/25/03: Update: made evidence locker directory names constant (define.pl) -11/25/03: Update: Started process of re-architecture -12/2/03: Update: Replaced logo.jpg with Hash the Hound -12/7/03: Update: Added favicon.ico with Hash -01/06/04: Update: Changed command line arguments -01/24/04: Update: made it only a warning if cookie file can't be opened -02/15/04: Update: Timezone is now optional. Defaults to local if not given. -02/15/04: Update: Timezone value optional in () in file listing (prevents - parsing errors if incorrect timezone is given -03/16/04: Bug Fix: Fixed zombie problem by ignoring child signal -(BUG: 860186) Reported by Angus Marshall. -03/18/04: Update: New layout for adding cases, hosts, and images. -03/18/04: Update: changed HTML to use lowercase values instead of all caps. -03/18/04: Update: New windows are no longer opened when changing modes. -03/19/04: Release: Big release with a new redesign and a few other - changes (live analysis) - ---------------------------- Version 1.75 -------------------------------- -09/22/03: Update: Changed the internal 'get_' functions that parse the - URL arguments to error instead of just return 0 when a problem occurs. -10/22/03: Bug Fix: Check for an investigator name before trying to log - to the exec log. This is a problem when indexing a hash database, an - error message is printed because of the null string. reported by - Brian Baskin. -11/10/03: Update: Improved error message when strings can't be parsed. - (Bug: 823081) -11/15/03: Update: Improved messages in installation script -11/15/03: Bug Fix: Added 'defined' checks to command output to prevent - string errors when command fails. (BUG 842824) -11/15/03: Update: Added 'HEIGHT' value to HTML images to make images - align better and load faster and with the right size -11/15/03: Update: Added a timer so that a char is printed every 5 seconds - during keyword searching, file type sorting, and MD5 for images. - ---------------------------- Version 1.74 -------------------------------- -08/03/03: Bug Fix: Notes could not be added for some files because - the HTML code was missing a closing bracket. -08/18/03: Bug Fix: added POSIX:settz() because some versions of Perl do - not use the most recent ENV{TZ} variable when running 'localtime'. This - cause some incorrect times for events in the sequencer. -08/19/03: Update: NSRL is no longer used with 'sorter' until it is - easier to identify which files in the NSRL are known good and which - are known bad. -08/20/03: Update: Added support for swap and raw images for searching - and data unit analysis. -08/20/03: Update: Added the unit size to the display of the Data Unit - mode. -08/20/03: Update: Search for perl5.6.0 first during install -08/21/03: Update: Changed use of backticks to pipes for executing commands -08/21/03: ?: Added a 'sleep(1)' to the pipe to prevent the loss of data - that can be seen with perl5.8.0 in the buffer. This should be fixed - in a better way though. -08/21/03: Update: The exact command executed is now saved to the log - directory. -08/21/03: Update: Changed 'date' regexp to make year optional. -08/22/03: Update: Added warning if Perl 5.8 is used because of the buffer - problem. -08/22/03: Bug Fix: Fixed some keyword escape values in the search mode. -08/22/03: Update: Added a new help page on the limitations of keyword - searching. -08/22/03: Update: Moved the unallocated space and strings file creation - to the Image Details view instead of the keyword search window - (suggested by: Paul Bakker) -08/25/03: Update: improved wording of the Add Image window to better - explain the mounting point. -08/26/03: Update: When adding sequencer notes in manually, the time - is set to the last note entered to make it easier to add notes from - logs and external sources. -08/26/03: Update: The keyword search display has a final clause that - prints the results even if they are not found in the 'index' method. - This prevents any hits from being lost during the analysis of the - output. -08/26/03: Bug Fix: strings less than 4 chars would not be found before - because 'strings' only shows strings that are 4 or more in length -08/28/03: Update: if more than 1000 keyword hits are found, then a message - is reported and the user must choose a new keyword. This prevents the - browser from hanging from a huge HTML table. -08/28/03: Update: A '.' is printed during the keyword search for each - 100 hits as a status update. - - ---------------------------- Version 1.73 -------------------------------- -06/10/03: Bug Fix: The '-i day' was not added to the mactime code and - caused an error (reported by Cathy Buckman) - ---------------------------- Version 1.72 --------------------------------- -04/09/03: Bug Fix: The Java Script check on the main page broke in 1.71 - because the document.write was on multiple lines -04/11/03: Bug Fix: Keyword Search False Hit code had a bug that it - would be printed in error and message was improved -04/22/03: Update: Added examples to case management help file -05/06/03: Bug Fix: calc_md5 did not need 'o' tag on end of regular - expression because it would not work if the method was called more - than once. (Paul Bakker) -06/01/03: Bug Fix: Some keyword searches with $ in it were failing -06/01/03: Update: Keyword searches are now saved to a file and can be - found in the keyword seach main menu -06/01/03: Update: Changed the format a little of the keyword search - menu -06/01/03: Update: Added grep cheat sheet -06/03/03: Update: Tables now have alternating colors for file listing - and timeline viewing -06/03/03: Update: Sequencer mode added -06/03/03: Update: Sequencer help file added -06/04/03: Bug Fix: Added 'LANG=C LC_ALL=C' to sorter & mactime to prevent - UTF-8 errors (Debugging help from Daniel Schwartzer) -06/04/03: Bug Fix: The regular expression for viewing timelines did not - allow multiple users to have the same UID (reported by Cathy Buckman) -06/05/03: Update: Added button for Event Sequencer and added tables to - the standard notes reading window -06/09/03: Update: Added '-i day' flag to mactime for new feature in - The Sleuth Kit - ---------------------------- Version 1.71 --------------------------------- -02/27/03: Bug Fix: Regular expression searches w/out a strings file had - problems because the '-n' value was being incorrectly calculated. -03/17/03: Update: Added more logging to investigator log -03/17/03: Bug Fix: The case opening was not being logged in the case log -03/17/03: Update: The current 'mode' tab is also a hyperlink now -03/17/03: Bug Fix: Fixed bug that did not allow the path for a strings - file to have a space in it. -03/17/03: Update: When no port and remote address are given on the - command line, port 9999 and localhost are used. Documents also - updated to reflect new syntax. -03/18/03: Update: Use the 'x' repetition operator for ASCII reports - instead of a row of dashes. -03/18/03: Update: Added tag to MAIN_FR and incorporated more - '<<EOF' HTML code. -03/19/03: Update: Added $FIL_NAME function that translates a name to - a meta data address using 'ifind -n' -03/19/03: Update: A directory name can be entered in the $FIL_DIR - frame now to jump to a directory or file -03/19/03: Update: The directory path in $FIL_LIST was changed to have - hyperlinks that allow one to jump to a previous directory (using - $FILE_NAME) -03/19/03: Update: Cleaned up HTML code in $FIL_LIST -03/20/03: Update: passwd and group files are now imported in timelines - by selecting the image - no more inode values -03/20/03: Update: Cleaned up HTML code in timeline section -03/21/03: Update: Added '-z' flag to usage of 'file' so that comressed - files are opened. -03/21/03: Bug Fix: Some special values needed to be escaped in the - grep keyword search (for non regular expressions) (\.]^$"-). -03/24/03: Update: Changed how images are added (symlinks, copies, - or moves). -03/24/03: Update: Added a file system sanity check when adding one -03/27/03: Update: Added a check to the 'File Type' mode that extracts - just graphic images and makes thumbnails. -03/27/03: Update: Added '-i' flag when 'mactime' is run to create the - summary file for timelines. -03/27/03: Update: Added link to summary page with hyper links to actual - month for timelines -03/27/03: Update: Added more HTML table columns for date in timeline view -03/27/03: Update: Made the 'ifind' process optional in Data Unit and key - word searching mode (makes browsing faster) -03/27/03: Update: Evidence Locker now contains entries for when a case - is created or opened. -03/30/03: Update: Improved the help file for time lines. -03/31/03: Update: Changed addresses to sleuthkit.org - - - ---------------------------- Version 1.70 --------------------------------- -Interface Changes: - - Too many to note individually - - New windows are created when modes or images are changed - - Improved error messages - - Can load the unallocated image in the Data Unit Mode - - Case management - -12/10/02: Update: Help is now a directory and contents can be viewed at - any time. -01/02/03: Update: Added support for sorter and hfind tools in TASK -01/02/03: Update: NSRL now requested at startup -01/02/03: Update: Alert and exclude hash databases are options when making - a new host now -01/09/03: Update: Carriage Returns are now sent if it is a Windows client -01/09/03: Update: Improved the pre-defined IP keyword search expression -01/10/03: Update: Changed use of "_new" as target to "_blank" -01/28/03: Update: Installation and other system directories can now - have spaces and other symbols in them (Dave Goldsmith) - - ---------------------------- Version 1.62 --------------------------------- -10/07/02: Update: Added File Type to block mode -10/07/02: Update: Can now add notes to 'dls' image blocks -10/07/02: Update: One can now view as many consecutive data units as they - want in data mode. Many other changes and updates were done with this - as well. (inspired by the Honeynet sotm) -10/07/02: Update: The File System details view for FAT now has hyperlinks - to view the run and follow to the next run. -10/09/02: Bug Fix: Removed use of 'use integer' so that large blocks do - no turn into '-1' when doing a keyword search (Michael Stone - Loyola) - - ---------------------------- Version 1.61 --------------------------------- -08/28/02: Update: White space is allowed at the begining of the morgue file -08/28/02: Bug Fix: No error is generated if md5.txt does not exist from - main menu -08/28/02: Update: Improved error messages -08/28/02: Update: Added code to Main Menu to check for Java Script turned on -09/19/02: Update: fsmorgue can be a symlink in the morgue directory - - ---------------------------- Version 1.60 --------------------------------- -- Changed NTFS c-time to Changed from Created (5/20/02) -- Fixed a couple little bugs with parsing NTFS output (5/20/02) -- Improved sorting (name is case insensitive and name is used as - secondary sorting index) (5/20/02) -- Improved error messages of invalid input to inode & block mode -- Added ability to import password and group files when making a time line - (5/28/02) -- Fixed bug that did not allow IP addresses to be used for the ACL when - DNS was not available (5/30/02) -- Fixed some issues to make Internet Explorer not complain so much (05/30/02) -- Improved the logging so that one can retrace their actions (05/31/02) -- Moved autopsy.log to logs directory (05/31/02) -- Added ability to write Notes about a given block, inode, or file (06/04/02) - (suggestion by Dave Dittrich) -- Set default investigators name (an error was generated if no name was given) - (06/04/02) -- Added links in the help page to the window help pages (06/05/02) -- Updated timeline to reflect new format in new TASK (06/19/02) -- Added '-C' flag to turn off cookies on command line (06/20/02) -- Added new main menu (06/20/02) -- Made MD5 generation 'opt-out' (06/22/02) -- New code to remove duplicate entries in md5.txt and fsmorgue -- fsmorgue can have whitespace at end of line (7/6/02) -- An error is generated if an image in fsmorgue does not exist (7/6/02) -- updated automatic date search (7/9/02) -- New feature allows one to save the MD5 values of all files in a directory, - which makes the Solaris Finger Print Database easier (7/12) - - ---------------------------- Version 1.50 --------------------------------- -- Modified to support TASK instead of TCT and TCTUTILs (8/25/01) -- Removed chmod 'bug' for the cookie file (8/25/01) -- Fixed number of hits bug in Search mode (off by one) (8/25/01) -- Added ftype support (8/28/01) -- Added ftype field to reports (8/28/01) -- Encoded dir arg in FIL_DEL -- Filter option holds for usage of next and rev in block mode -- If using fat, a separate option is given to run find_inode due to how - slow it runs -- removed use of zoneinfo in favor of the new timezone value in fsmorgue. -- strings now uses '-a' flag to show all strings -- When doing a search, the length of the string is given as the '-n' - flag to strings to speed up the search -- Allow user to "force" blocks when an inode size is 0 (the istat -b flag) -- use the md5 that comes with TCT/TASK -- multiple images with the same mounting point can now exist -- Added the morgue directory to the MENU to make it easier to manage - multiple hosts -- Files are sorted by name by default -- can import strings files and create them if needed -- Run files through 'file' to get data type -- case insensitive searches -- MAC headers correspond to file system type (create vs change) -- Deleted files are displayed in red -- Correct address name used (fragment, sector etc.) -- Support for NTFS attributes -- parse bad tags from HTML when viewing it (send sterile pict) -- cookie file has port number to aid in scripting -- cookie files are deleted upon closing -- log messages are printed for each request -- added integrity checker -- renamed aux directory to base to make Windows happy -- added time line support -- added fsstat support -- Added built-in search values in search.pl - - -May 29, 2001 1.01 released -- Fixed Hex link when in search mode (3/23/01) -- Corrected heading of ctime (Addam Schroll, Purdue University) (4/24/01) -- Parses output of new istat correctly (5/1/01) -- When viewing 'inode as a file', the image and inode are sent as the dir - name (5/1/01) -- Added wait() to collect zombies in Linux (5/22/01) -- Added auto-flush to prevent repeat log entries (5/22/01) -- Added a 'save as' option to file and inode browsing (Addam Schroll) - (5/22/01) -- Added option for unrm block numbers (due to blockcalc) (5/22/01) -- Improved side menu for inode, block, and search (5/22/01) -- Added "Content-Disposition" so that reports and "save as" have a - unique default filename. (5/23/01) -- Organization changes to Main Menu (5/24/01) -- Automated installation process (5/24/01) - -March 19, 2001 1.0 released -- Added man page for autopsy (3/10/01) -- Directory entries in config files no longer require an / at the end -- Morgue file names can have a '.' in them (but still not '/') (3/10) -- autopsy first checks for /dev/urandom for random cookie (3/10/01) -- morgue directory is a command line option to autopsy (3/10/01) -- the lib variable in autopsy is no longer set to './' so that it - can be run outside of /usr/local/autopsy (3/10/01) -- changed all references of device to image (3/11/01) -- changed all reports to print full image path (3/11/01) -- Investigator is a command line option to autopsy (3/11/01) -- CGI support removed. Only autopsy is supported (3/16/01) -- renamed autopsyd to autopsy (3/16/01) -- Fixed UID and GID heading (3/16/01) -- Run image through strings before grep to prevent memory errors (3/16/01) -- output of find_file and find_inode is prepended with rdir (3/16/01) - - -Feb 27, 2001 0.2b released -- Added stand alone server, autopsyd (as suggested by Dan Farmer) -- Reorganized files due to new program -- Changed names of some executables that changed in TCTUTILs - -Feb 19, 2001 0.1b released - ------------------------------------------------------------------------- +---------------- VERSION Current (development) -------------- + +New features: +- 64-bit support (JavaFX for video) +- Multi-select +- different sized thumbnails +- Custom tags persist accross runs of the app +- RegRipper is run on each hive and raw output is available. + + +Improvements: +- EXIF module uses only signatures +- File size view does not show unalloc files +- Tagged files in report show more data +- Updated test scripts + + +Bugfixes: +- Several -> Didn't keep good track in this file. + + + +---------------- VERSION 3.0.6 -------------- + +New features: +- Logical files and folders support +- New file views in directory tree to view: deleted, executable, archive files and files by size +- ext4 and yaffs2 support (via TSK 4.1.0) + +Improvements: +- Improvements to tagging of files and keyword search results +- Any file and folder can be selectively ingested using the directory tree view + +Bugfixes: +- Keyword Search: fix when Solr does not cleanly shutdown +- fix for "Process Unallocated Space" option doesn't do anything +- fixed result viewer for "File Search by MD5 Hash" +- fix Solr, Timeline and RecentActivity issues with java 7.0.21 +- Views->Recent Files showing inconsistent results when clicked many times +- reduced memory usage in Timeline + + +---------------- VERSION 3.0.5 -------------- + +New features: +- Archive extractor ingest module (uses 7zip) +- Timeline (Beta) + +Improvements: +- Sleuthkit-4.0.2 and libewf-20130128 +- improved image loading in Media View and Thumbnail View (faster loading, handles large files better) +- improve Keyword Search file indexing (use detected mime-type instead of file extension) +- exif module - better jpeg detection using signature and not only file extension. +- show children counts in directory tree +- Ingest Message Inbox showing which messages are new better + +Bugfixes: +- fixed memory leaks in "Add Image" +- 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. + +---------------- VERSION 3.0.4 -------------- + +New features: +- Results and files can be tagged with custom tags and reported on them. +- New notification area for error reporting (bottom right). + +Improvements: +- Tweaked memory settings to eliminate out-of-memory errors. +- Faster application launch time. +- Netbeans RCP upgrade from 7.2.1 to 7.3 +- Upgrade from Java 6 to Java 7 + +Bugfixes: +- fixed DLL dependency version issue causing Autopsy not to launch on some systems +- fixed bug when keyword search ingest would search also images previously ingested, creating duplicate results +- fixed crash and hang in html and excel report generation, due to special characters present +- fixed cancellation when creating file or result bookmark +- fixed text not being extracted and searched from all MS Office documents (such as docx, xlsx and pptx extensions) +- fixed Exif meta-data extraction in Exif ingest module + + +---------------- VERSION 3.0.3 -------------- + +*Note: Due to major changes in Keyword search module indexing this release is not fully backward compatible. +As a workaround, you will need to rebuild index by re-running Keyword Search ingest on Cases created with previous versions. + +Improvements: +- Upgrade to Solr4.0 / Tika 1.2: Improved performance and highlighting +- Remake of reporting UI and functionality +- Significant increase in reporting speed +- New option to keep the most specific file viewer (default) or the lastly used viewer active. + + +Bugfixes: +- Fixed bug that caused the ends of large amounts of text to not be indexed (occurs mostly in unallocated space). +- Fix scrolling to first keyword hit when Text View is first loaded +- Imported keyword lists are now always enabled for ingest by default + + +---------------- VERSION 3.0.2 -------------- + +New features: +- Extraction of all unallocated blocks as a single file +- Results bookmarks with comments and basic bookmark reporting +- Hashkeeper hash database support + +Improvements: +- File Ingest: minimized file queuing time and memory usage, also improving ingest stability +- Jump to arbitrary page in Thumbnail View +- Add Image Wizard - better work-flow, better device size reporting, info on currently processed directory +- Reporting: reorganized columns, sorted by 1st column, added logo, better styling + +Bugfixes: +- fixed periodic keyword search during ingest, when it would only search max. 2 times +- fixed Downloads "target" in Recent Activity +- fixed missing hash and keyword search hits in reports +- fixed deselecting NSRL database for hash ingest + + +---------------- VERSION 3.0.1 -------------- + +New features: +- Physical and logical disk devices discovery in Add image wizard + +Improvements: +- Significant performance improvements when adding images. +- Slight improvements in UI performance for large number of results. +- Improved stability when running ingest on multiple images. +- Removed limit on number of results displayed. +- Thumbnail viewer - added paging and removed limit of images. +- Better HTML report navigation, handling large reports better. +- Netbeans RCP upgrade from 7.2 to 7.2.1 +- Build scripts enhancements to include module version tracking. + +Bugfixes: +- Fixed reading content from multiple file attributes (NTFS, HFS). +- Add Extract action to Unalloc content file nodes (per file). +- Fixes bugs with case re-opening. +- UI fix for keyword search box when case is changed. +- Enable user to select any image file extension when opening image. +- Thunderbird parser module fixes. +- Reporting fixes: added missing artifacts (keyword search, hash hits, file bookmarks). + + +---------------- VERSION 3.0.0 -------------- +New features: +- Using Sleuthkit 4.0.0 +- Integrated plugin installer. +- New options menu to globally access module options. +- Added custom ingest module loader and ingest module auto-discovery + +Improvements: +- Updated ingest framework APIs. +- Merged the main modules into Autopsy-Core and Autopsy-CoreLibs. +- Improved logging infrastructure. +- Improved configuration infrastructure. +- Keyword search: upgraded Lucene from 34 to 36. +- Build system improvements. +- Updated documentation. + +Bugfixes: +- UI selection fix in Content and Result viewer +- UI fixes in Hash Database and Keyword Search options. +- Excel report export produced corrupt files sometimes. +- Fix for Keyword Search sometimes not property initializing when application starts. + +3.0.0b5 (September 12, 2012) +New features: +- Added international string extraction from unknown file types. +- Removed size limitations of large files for keyword searching. +- Added full html parsing and extraction (including comments, scripts, meta tags, etc). +- Added support for indexing and searching disk images that have no volume and file system. +- Solr (3.6.1) and Tika (1.0) upgrade. +- Search a file by hash GUI feature and search other files with same hash. +- Web search query text extraction from popular search engines. +- Exif metadata extraction from jpeg files. +- Netbeans RCP platform upgrade (7.2). +- Basic file bookmarks support. +- Body file report. +- Improved UI. +- Updated Ingest Module API. + +Bugfixes: +- Keyword search memory usage improvements. +- Directory tree now shows which directories have no children before user clicks. +- Fixed bug when recent cases would not get updated. +- Fixed a bug when sometimes a case would get deleted. +- Fixed occasional Media View crashes. + +3.0.0b4 (June 29, 2012) +Funded by US Army Intelligence Center of Excellence (USAICoE): +New Features: +- MBOX parsing +- Better lnk file parsing +Bug Fixes: +- Included needed jar file for Recent Activity (Issue #52). +- Fixed error handling from ingest (Issue #53). + +3.0.0b3 (June 12, 2012) +New Features (Funded by US Army Intelligence Center of Excellence (USAICoE)): +- Ingest manager runs triage/ingest task after disk is added. +- Basic keyword search (indexed via SOLR) +- Recent activity extract (web artifacts, recent documents, devices, etc.) +- Improved UI + +3.0.0b2 (Nov 9, 2011) +New Features: +- New database design +- Hashlookup / calculation +- Minor overall improvements +- NOTE: Cases created with b1 are not supported in b2 (different DB) + +3.0.0b1 (Aug 16, 2011) +- Initial release +- Windows only +- Directory tree +- File Search +- Table and thumbnail viewer + +--------------------------- Version 2.24 -------------------------------- +3/22/10: Bug Fix: resolved issue 2950986 to support HFS directories. + +--------------------------- Version 2.23 -------------------------------- +2/12/10: bug fix: resolved issue 2950693 where previous searches +were not shown if they used quotes. + +2/12/10: bug fix: resolved issue 2932385 where wrong flag was being used +to do only doing category searching" + +2/12/10: bug fix: resolved issue 2779244 where wrong sorter path was +being used. + +--------------------------- Version 2.22 -------------------------------- +10/27/09: Update: Change istat to use -B instead of -b (new change in TSK). + +11/19/09: Update: Improved configure script process and error message for +FILE_EXE check. + +11/25/09: Fixed MD5 exe bug when building live CD + +12/30/09: Fixed issue 2923857 re: cookie errors for the icon and css file +links when cookies are used. + +--------------------------- Version 2.21 -------------------------------- +11/7/08: Bug Fix: Changed case management code to not error when 'dls ...' +line was encountered. + +11/14/08: Bug Fix: Fixed bug 2288406 (parsing of new fls -l format when file name searching and deleted file listing) + +--------------------------- Version 2.20 -------------------------------- +7/1/08: Update: Updated FAT sizes based on new "special" files. + +7/9/08: Update: Updated NTFS processing for orphan files / removed +ifind -p etc. + +7/9/08: Update: Updated mactime and time formats to ISO formats. + +9/13/08: Update: Changed usage to new TSK d* to blk* names. + +9/26/08: Bug Fix: Input check on host was printing invalid host values +w/out encoding HTML entities. Reported by Russ McRee. + +10/01/08: Update: HFS support is enabled if TSK was compiled with +support for it. + +10/08/08: Bug Fix: Added some more HTML entity escaping to case management +values (such as description). Reported by Daniel Medianero. + +10/13/08: Update: Added perl version check back into configure, but used +perl $] variable to do checking. Based on patch by Joerg Friedrich. + +--------------------------- Version 2.10 -------------------------------- +2/20/08: Bug Fix: Added 'tsk' to the path for sorter to find the 'images' +config file. Reported by Russell Reynolds. + +3/2/08: Update: Modified the adding of disk image process to save a +call to mmls (reported by Pope). + +3/2/08: Update: Added more basic control char filtering back into Print(). + +--------------------------- Version 2.09 -------------------------------- +2/4/07: Update: Bind only to localhost network if remote addr is local. +Suggested by Markus Waldeck. + +4/19/07: Bug Fix: Event sequencer notes for file did not have clock skew +in the times. Reported by Len CulBreath. + +12/21/07: Update: updated configure and install process for TSK 2.50 + +1/28/08: Update: Added NSRL support back in. + +--------------------------- Version 2.08 -------------------------------- + +8/23/06: Bug Fix: The configure script did not like TSK directory names +with a space in them. + +8/23/06: Update: The PATH variable is not entirely cleared anymore. +Instead, it is replaced by the basic bin directories (this was causing +some problems with Cygwin). + +8/31/06: Update: If Autopsy is running under Cygwin, then it will set +the PATH to contain the basic bin directories. Otherwise, it is clear +(original behavior). + + +--------------------------- Version 2.07 -------------------------------- +3/15/06: Bug Fix: Caseman.pm had DATA_DIR instead of DATADIR and a +concatenation error message occurred. Reported by Jason DePriest. + +5/3/06: Update: Added support for ISO9660 file systems. + +5/3/06: Update: Added support for AFF and AFD image formats. + +5/03/06: Update: Added image format type to image details screen. + +5/3/06: Update: Added hexdump view for file analysis and reports (initial +patch by Patrick Knight). + +5/3/06: Update: Changed number of dashes in reports to 70 instead of 62. + +5/4/06: Update: Integrity checking disabled for non-raw image files +until a specialized tool exists in TSK to abstract the embedded hash +calculation. + +5/8/06: Update: Added support for AFM files. + + + +--------------------------- Version 2.06 -------------------------------- +05/02/05: Fix: Typo in timeline creation window (reported by Surago Jones). + +06/15/05: Update: Added css style sheet and changed some formatting. + +08/13/05: Update: Added "utf-8" as HTML type so that TSK unicode +output will be properly dispayed. + +10/13/05: Update: Removed print_output() function contents because +it broke the Unicode chars. + +10/13/05: Update: Require 5.8 version of Perl now (in config and +in source) because it has best Unicode support. + + + +--------------------------- Version 2.05 -------------------------------- +03/16/05: Update: Image name is given in the Image Details window +when adding a new image file. (Suggested by Surago Jones). + +03/17/05: Bug Fix: swap and raw host config entries could not be +read after the conversion because of a regular expression bug in +the read code. (Reported by Surago Jones) (BUG: 1165235) + +03/21/05: Bug Fix: When a new host was added to a case with no +investigator names, then it would prompt you to select a name from +an empty list. (BUG: 1167970). + +03/25/05: Update: Check return status of rename functions and print +error if failed. + +04/04/05: Bug Fix: A missing volume type message was reported when +adding a disk image. The flow of add_img_prep was modified to +ensure that it was set. (Reported by Bradley Bitzkowski) (BUG: +1177042) + +04/08/05: Update: A thumbnail of images is shown when selected in the File +mode. Suggested by and patch by Guy Voncken. + + +--------------------------- Version 2.04 -------------------------------- +10/22/04: Update: Changed the way that NTFS lists directory contents. No + longer lists the deleted entries from 'fls', only from 'ifind'. Reduces + the inaccurate information. + +02/XX/05: Update: Incorporated new TSK 2 features: + - Disk images (split and raw) + - new config file formats + - moved images and output md5.txt file into one + +03/01/05: Update: Changed behavior of some links that created new +Autopsy Windows + +03/05/05: Update: timeline output can be in comma delimited format + +03/05/05: Update: Added SSN and credit card seach patterns from + Jerry Shenk. + +03/05/05: Update: Added temporal data when a note is creaed. + +03/11/05: Update: Changed to new TSK names for srch_strings and img_stat + +03/15/05: Update: improved handling of white space around investigator +names and image names (suggested by Brian Baskin). + + +--------------------------- Version 2.03 -------------------------------- +08/24/04: Update: Added SHA-1 hash to the metadata view. + +09/01/04: Update: Added sstrings instead of local version of strings. + +09/05/04: Update: Added more help text. + +09/06/04: Update: Use the local version of file if TSK version is +not found. + +09/06/04: Update: Added links to the notes and events page after a +note or event has been created. + +09/06/04: Update: Added Unicode extract and search functionality using +the 'sstrings' tool from TSK. + + +--------------------------- Version 2.02 -------------------------------- +07/19/04: Bug Fix: print_err message in Caseman.lib did not have correct +Print:: package, which caused an error (BUG: 994199). + +07/29/04: Update: Added support for NTFS 'ifind -p' option to find deleted +files that do not have a name in the parent directory. + +07/29/04: Update: Added a filter to remove duplicate entries from a file +listing. Duplicate names with the same name and meta address are +removed. + +07/29/04: Update: OS X no longer needs the strings script, Autopsy +will adjust for the different flags. + +07/29/04: Update: When a deleted file name is entered into the find +directory box, the recover bit is set so the full contents are shown. + + +--------------------------- Version 2.01 -------------------------------- +03/29/04: Update: Changed text for the data integrity option when +adding a new image. + +04/20/04: Bug Fix: Fixed error that occured when data browsing with +a raw or swap image. The TSK usage for these file system types was +inconsistent and it was fixed in version 1.69. (BUG: 925382). +(Reported by Harald Katzer) + +05/03/04: Update: Changed regular expression in META so that the +new recovery listing in FAT istat will not show up as a hyperlink. + +05/03/04: Update: Removed usage of '-H' with 'icat' in File.PM. + +05/20/04: Bug Fix: Fixed the incorrect error message that was printed +when installing autopsy with a newer version of TSK than 1.68. +(BUG: 938909) + +05/20/04: Update: Added new feature that allows perl regular +expressions to be used to find file names. + +05/20/04: Update: Added file recovery features to File.pm, Meta.pm, +and Appview.pm. + +05/27/04: Update: Added a space to $REG_ZONE2 so that CYGWIN would +work if no zone was given (Marcus Muller). + +/05/27/04: Update: Added 'p' as an option for the type of a file in the +'fls' output and made the $::REG_MTYPE global for the pattern. + +05/28/04: Update: Cleaned up code so that commands and directories +do not have double slashes (//) sometimes. This caused problems +with CYGWIN (reported by Marcus Muller). + +05/28/04: Bug Fix: Keyword search of unallocated space would link to +incorrect data unit (although the address was correct). (Reported by +Jorge Ortiz, David Perez, Raul Siles). (BUG: 962410). + +05/28/04: Update: Updated dcat usage and syntax to reflect changes to +TSK. + +05/28/04: Update: Changed the messages printed when multiple data units +were displayed. Now the number of units or range are given instead of +number of bytes. + + +--------------------------- Version 2.00 -------------------------------- +11/25/03: Update: made evidence locker directory names constant (define.pl) +11/25/03: Update: Started process of re-architecture +12/2/03: Update: Replaced logo.jpg with Hash the Hound +12/7/03: Update: Added favicon.ico with Hash +01/06/04: Update: Changed command line arguments +01/24/04: Update: made it only a warning if cookie file can't be opened +02/15/04: Update: Timezone is now optional. Defaults to local if not given. +02/15/04: Update: Timezone value optional in () in file listing (prevents + parsing errors if incorrect timezone is given +03/16/04: Bug Fix: Fixed zombie problem by ignoring child signal +(BUG: 860186) Reported by Angus Marshall. +03/18/04: Update: New layout for adding cases, hosts, and images. +03/18/04: Update: changed HTML to use lowercase values instead of all caps. +03/18/04: Update: New windows are no longer opened when changing modes. +03/19/04: Release: Big release with a new redesign and a few other + changes (live analysis) + +--------------------------- Version 1.75 -------------------------------- +09/22/03: Update: Changed the internal 'get_' functions that parse the + URL arguments to error instead of just return 0 when a problem occurs. +10/22/03: Bug Fix: Check for an investigator name before trying to log + to the exec log. This is a problem when indexing a hash database, an + error message is printed because of the null string. reported by + Brian Baskin. +11/10/03: Update: Improved error message when strings can't be parsed. + (Bug: 823081) +11/15/03: Update: Improved messages in installation script +11/15/03: Bug Fix: Added 'defined' checks to command output to prevent + string errors when command fails. (BUG 842824) +11/15/03: Update: Added 'HEIGHT' value to HTML images to make images + align better and load faster and with the right size +11/15/03: Update: Added a timer so that a char is printed every 5 seconds + during keyword searching, file type sorting, and MD5 for images. + +--------------------------- Version 1.74 -------------------------------- +08/03/03: Bug Fix: Notes could not be added for some files because + the HTML code was missing a closing bracket. +08/18/03: Bug Fix: added POSIX:settz() because some versions of Perl do + not use the most recent ENV{TZ} variable when running 'localtime'. This + cause some incorrect times for events in the sequencer. +08/19/03: Update: NSRL is no longer used with 'sorter' until it is + easier to identify which files in the NSRL are known good and which + are known bad. +08/20/03: Update: Added support for swap and raw images for searching + and data unit analysis. +08/20/03: Update: Added the unit size to the display of the Data Unit + mode. +08/20/03: Update: Search for perl5.6.0 first during install +08/21/03: Update: Changed use of backticks to pipes for executing commands +08/21/03: ?: Added a 'sleep(1)' to the pipe to prevent the loss of data + that can be seen with perl5.8.0 in the buffer. This should be fixed + in a better way though. +08/21/03: Update: The exact command executed is now saved to the log + directory. +08/21/03: Update: Changed 'date' regexp to make year optional. +08/22/03: Update: Added warning if Perl 5.8 is used because of the buffer + problem. +08/22/03: Bug Fix: Fixed some keyword escape values in the search mode. +08/22/03: Update: Added a new help page on the limitations of keyword + searching. +08/22/03: Update: Moved the unallocated space and strings file creation + to the Image Details view instead of the keyword search window + (suggested by: Paul Bakker) +08/25/03: Update: improved wording of the Add Image window to better + explain the mounting point. +08/26/03: Update: When adding sequencer notes in manually, the time + is set to the last note entered to make it easier to add notes from + logs and external sources. +08/26/03: Update: The keyword search display has a final clause that + prints the results even if they are not found in the 'index' method. + This prevents any hits from being lost during the analysis of the + output. +08/26/03: Bug Fix: strings less than 4 chars would not be found before + because 'strings' only shows strings that are 4 or more in length +08/28/03: Update: if more than 1000 keyword hits are found, then a message + is reported and the user must choose a new keyword. This prevents the + browser from hanging from a huge HTML table. +08/28/03: Update: A '.' is printed during the keyword search for each + 100 hits as a status update. + + +--------------------------- Version 1.73 -------------------------------- +06/10/03: Bug Fix: The '-i day' was not added to the mactime code and + caused an error (reported by Cathy Buckman) + +--------------------------- Version 1.72 --------------------------------- +04/09/03: Bug Fix: The Java Script check on the main page broke in 1.71 + because the document.write was on multiple lines +04/11/03: Bug Fix: Keyword Search False Hit code had a bug that it + would be printed in error and message was improved +04/22/03: Update: Added examples to case management help file +05/06/03: Bug Fix: calc_md5 did not need 'o' tag on end of regular + expression because it would not work if the method was called more + than once. (Paul Bakker) +06/01/03: Bug Fix: Some keyword searches with $ in it were failing +06/01/03: Update: Keyword searches are now saved to a file and can be + found in the keyword seach main menu +06/01/03: Update: Changed the format a little of the keyword search + menu +06/01/03: Update: Added grep cheat sheet +06/03/03: Update: Tables now have alternating colors for file listing + and timeline viewing +06/03/03: Update: Sequencer mode added +06/03/03: Update: Sequencer help file added +06/04/03: Bug Fix: Added 'LANG=C LC_ALL=C' to sorter & mactime to prevent + UTF-8 errors (Debugging help from Daniel Schwartzer) +06/04/03: Bug Fix: The regular expression for viewing timelines did not + allow multiple users to have the same UID (reported by Cathy Buckman) +06/05/03: Update: Added button for Event Sequencer and added tables to + the standard notes reading window +06/09/03: Update: Added '-i day' flag to mactime for new feature in + The Sleuth Kit + +--------------------------- Version 1.71 --------------------------------- +02/27/03: Bug Fix: Regular expression searches w/out a strings file had + problems because the '-n' value was being incorrectly calculated. +03/17/03: Update: Added more logging to investigator log +03/17/03: Bug Fix: The case opening was not being logged in the case log +03/17/03: Update: The current 'mode' tab is also a hyperlink now +03/17/03: Bug Fix: Fixed bug that did not allow the path for a strings + file to have a space in it. +03/17/03: Update: When no port and remote address are given on the + command line, port 9999 and localhost are used. Documents also + updated to reflect new syntax. +03/18/03: Update: Use the 'x' repetition operator for ASCII reports + instead of a row of dashes. +03/18/03: Update: Added <NOFRAMES> tag to MAIN_FR and incorporated more + '<<EOF' HTML code. +03/19/03: Update: Added $FIL_NAME function that translates a name to + a meta data address using 'ifind -n' +03/19/03: Update: A directory name can be entered in the $FIL_DIR + frame now to jump to a directory or file +03/19/03: Update: The directory path in $FIL_LIST was changed to have + hyperlinks that allow one to jump to a previous directory (using + $FILE_NAME) +03/19/03: Update: Cleaned up HTML code in $FIL_LIST +03/20/03: Update: passwd and group files are now imported in timelines + by selecting the image - no more inode values +03/20/03: Update: Cleaned up HTML code in timeline section +03/21/03: Update: Added '-z' flag to usage of 'file' so that comressed + files are opened. +03/21/03: Bug Fix: Some special values needed to be escaped in the + grep keyword search (for non regular expressions) (\.]^$"-). +03/24/03: Update: Changed how images are added (symlinks, copies, + or moves). +03/24/03: Update: Added a file system sanity check when adding one +03/27/03: Update: Added a check to the 'File Type' mode that extracts + just graphic images and makes thumbnails. +03/27/03: Update: Added '-i' flag when 'mactime' is run to create the + summary file for timelines. +03/27/03: Update: Added link to summary page with hyper links to actual + month for timelines +03/27/03: Update: Added more HTML table columns for date in timeline view +03/27/03: Update: Made the 'ifind' process optional in Data Unit and key + word searching mode (makes browsing faster) +03/27/03: Update: Evidence Locker now contains entries for when a case + is created or opened. +03/30/03: Update: Improved the help file for time lines. +03/31/03: Update: Changed addresses to sleuthkit.org + + + +--------------------------- Version 1.70 --------------------------------- +Interface Changes: + - Too many to note individually + - New windows are created when modes or images are changed + - Improved error messages + - Can load the unallocated image in the Data Unit Mode + - Case management + +12/10/02: Update: Help is now a directory and contents can be viewed at + any time. +01/02/03: Update: Added support for sorter and hfind tools in TASK +01/02/03: Update: NSRL now requested at startup +01/02/03: Update: Alert and exclude hash databases are options when making + a new host now +01/09/03: Update: Carriage Returns are now sent if it is a Windows client +01/09/03: Update: Improved the pre-defined IP keyword search expression +01/10/03: Update: Changed use of "_new" as target to "_blank" +01/28/03: Update: Installation and other system directories can now + have spaces and other symbols in them (Dave Goldsmith) + + +--------------------------- Version 1.62 --------------------------------- +10/07/02: Update: Added File Type to block mode +10/07/02: Update: Can now add notes to 'dls' image blocks +10/07/02: Update: One can now view as many consecutive data units as they + want in data mode. Many other changes and updates were done with this + as well. (inspired by the Honeynet sotm) +10/07/02: Update: The File System details view for FAT now has hyperlinks + to view the run and follow to the next run. +10/09/02: Bug Fix: Removed use of 'use integer' so that large blocks do + no turn into '-1' when doing a keyword search (Michael Stone - Loyola) + + +--------------------------- Version 1.61 --------------------------------- +08/28/02: Update: White space is allowed at the begining of the morgue file +08/28/02: Bug Fix: No error is generated if md5.txt does not exist from + main menu +08/28/02: Update: Improved error messages +08/28/02: Update: Added code to Main Menu to check for Java Script turned on +09/19/02: Update: fsmorgue can be a symlink in the morgue directory + + +--------------------------- Version 1.60 --------------------------------- +- Changed NTFS c-time to Changed from Created (5/20/02) +- Fixed a couple little bugs with parsing NTFS output (5/20/02) +- Improved sorting (name is case insensitive and name is used as + secondary sorting index) (5/20/02) +- Improved error messages of invalid input to inode & block mode +- Added ability to import password and group files when making a time line + (5/28/02) +- Fixed bug that did not allow IP addresses to be used for the ACL when + DNS was not available (5/30/02) +- Fixed some issues to make Internet Explorer not complain so much (05/30/02) +- Improved the logging so that one can retrace their actions (05/31/02) +- Moved autopsy.log to logs directory (05/31/02) +- Added ability to write Notes about a given block, inode, or file (06/04/02) + (suggestion by Dave Dittrich) +- Set default investigators name (an error was generated if no name was given) + (06/04/02) +- Added links in the help page to the window help pages (06/05/02) +- Updated timeline to reflect new format in new TASK (06/19/02) +- Added '-C' flag to turn off cookies on command line (06/20/02) +- Added new main menu (06/20/02) +- Made MD5 generation 'opt-out' (06/22/02) +- New code to remove duplicate entries in md5.txt and fsmorgue +- fsmorgue can have whitespace at end of line (7/6/02) +- An error is generated if an image in fsmorgue does not exist (7/6/02) +- updated automatic date search (7/9/02) +- New feature allows one to save the MD5 values of all files in a directory, + which makes the Solaris Finger Print Database easier (7/12) + + +--------------------------- Version 1.50 --------------------------------- +- Modified to support TASK instead of TCT and TCTUTILs (8/25/01) +- Removed chmod 'bug' for the cookie file (8/25/01) +- Fixed number of hits bug in Search mode (off by one) (8/25/01) +- Added ftype support (8/28/01) +- Added ftype field to reports (8/28/01) +- Encoded dir arg in FIL_DEL +- Filter option holds for usage of next and rev in block mode +- If using fat, a separate option is given to run find_inode due to how + slow it runs +- removed use of zoneinfo in favor of the new timezone value in fsmorgue. +- strings now uses '-a' flag to show all strings +- When doing a search, the length of the string is given as the '-n' + flag to strings to speed up the search +- Allow user to "force" blocks when an inode size is 0 (the istat -b flag) +- use the md5 that comes with TCT/TASK +- multiple images with the same mounting point can now exist +- Added the morgue directory to the MENU to make it easier to manage + multiple hosts +- Files are sorted by name by default +- can import strings files and create them if needed +- Run files through 'file' to get data type +- case insensitive searches +- MAC headers correspond to file system type (create vs change) +- Deleted files are displayed in red +- Correct address name used (fragment, sector etc.) +- Support for NTFS attributes +- parse bad tags from HTML when viewing it (send sterile pict) +- cookie file has port number to aid in scripting +- cookie files are deleted upon closing +- log messages are printed for each request +- added integrity checker +- renamed aux directory to base to make Windows happy +- added time line support +- added fsstat support +- Added built-in search values in search.pl + + +May 29, 2001 1.01 released +- Fixed Hex link when in search mode (3/23/01) +- Corrected heading of ctime (Addam Schroll, Purdue University) (4/24/01) +- Parses output of new istat correctly (5/1/01) +- When viewing 'inode as a file', the image and inode are sent as the dir + name (5/1/01) +- Added wait() to collect zombies in Linux (5/22/01) +- Added auto-flush to prevent repeat log entries (5/22/01) +- Added a 'save as' option to file and inode browsing (Addam Schroll) + (5/22/01) +- Added option for unrm block numbers (due to blockcalc) (5/22/01) +- Improved side menu for inode, block, and search (5/22/01) +- Added "Content-Disposition" so that reports and "save as" have a + unique default filename. (5/23/01) +- Organization changes to Main Menu (5/24/01) +- Automated installation process (5/24/01) + +March 19, 2001 1.0 released +- Added man page for autopsy (3/10/01) +- Directory entries in config files no longer require an / at the end +- Morgue file names can have a '.' in them (but still not '/') (3/10) +- autopsy first checks for /dev/urandom for random cookie (3/10/01) +- morgue directory is a command line option to autopsy (3/10/01) +- the lib variable in autopsy is no longer set to './' so that it + can be run outside of /usr/local/autopsy (3/10/01) +- changed all references of device to image (3/11/01) +- changed all reports to print full image path (3/11/01) +- Investigator is a command line option to autopsy (3/11/01) +- CGI support removed. Only autopsy is supported (3/16/01) +- renamed autopsyd to autopsy (3/16/01) +- Fixed UID and GID heading (3/16/01) +- Run image through strings before grep to prevent memory errors (3/16/01) +- output of find_file and find_inode is prepended with rdir (3/16/01) + + +Feb 27, 2001 0.2b released +- Added stand alone server, autopsyd (as suggested by Dan Farmer) +- Reorganized files due to new program +- Changed names of some executables that changed in TCTUTILs + +Feb 19, 2001 0.1b released + +------------------------------------------------------------------------ diff --git a/build-windows.xml b/build-windows.xml index 487da5568a..d44420d0e8 100644 --- a/build-windows.xml +++ b/build-windows.xml @@ -3,72 +3,51 @@ <!-- Need a way to specify TSK Debug versus Release --> <property name="TSK_BUILD_TYPE">Release</property> - <target name="copyTSKLibsToRelease"> - <property environment="env"/> - <condition property="ewfFound"> - <isset property="env.LIBEWF_HOME"/> - </condition> - <fail unless="ewfFound" message="LIBEWF_HOME must be set as an environment variable."/> + + <target name="build-installer-windows" depends="init-advanced-installer" + description="Makes an installer from the opened ZIP file"> + <antcall target="run-advanced-installer" /> + <!--<delete dir="${nbdist.dir}/${app.name}-installer"/>--> + <delete dir="${nbdist.dir}/installer_${app.name}-cache"/> + <move file="${nbdist.dir}/installer_${app.name}-SetupFiles/installer_${app.name}.msi" tofile="${nbdist.dir}/installer_${app.name}-${app.version}.msi" /> + </target> - <copy file="${env.TSK_HOME}/win32/${TSK_BUILD_TYPE}/libtsk_jni.dll" tofile="${basedir}/Core/release/modules/lib/libtsk_jni.dll"/> - <copy file="${env.LIBEWF_HOME}/msvscpp/Release/libewf.dll" tofile="${basedir}/Core/release/modules/lib/libewf.dll"/> - <copy file="${env.LIBEWF_HOME}/msvscpp/Release/zlib.dll" tofile="${basedir}/Core/release/modules/lib/zlib.dll"/> + <target name="init-advanced-installer" depends="autoAIPath,inputAIPath" + description="Find AdvancedInstaller executable."> + <fail unless="ai-exe-path" message="Could not locate Advanced Installer."/> + <!-- Copy the template file to add details to --> + <copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${nbdist.dir}/installer_${app.name}.aip" overwrite="true"/> + <property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/> + <property name="aip-path" value="${nbdist.dir}\installer_${app.name}.aip"/> + <echo message="${ai-exe-path}" /> </target> - <target name="copyExternalLibsToZip"> - - <!-- Find CRT version we linked against from libtsk_jni manifest --> - <!-- disable auto-detection for CRT100 - <property name="libtsk.manifest.path">${env.TSK_HOME}/win32/tsk_jni/${TSK_BUILD_TYPE}/libtsk_jni.dll.intermediate.manifest</property> - <loadfile property="libtsk.manifest" srcFile="${libtsk.manifest.path}" /> - <propertyregex property="CRT.version" - input="${libtsk.manifest}" - regexp=".*Microsoft\.VC90.*?version\s*?=\s*?'(.*?)'" - select="\1" - casesensitive="false" /> - <echo>Found CRT.version linked against: ${CRT.version}</echo> - --> - <!-- <property name="CRT.version">10.0.40219.1</property> --> - <!-- <property name="CRT.version">10.0.40219.325</property> --> - <property name="CRT.version">10.0.30319.1</property> - - - <!-- Get C++ Runtime dlls --> - <property environment="env"/> - <condition property="crtDetected"> - <isset property="CRT.version"/> - </condition> - <fail unless="crtDetected" message="CRT version not detected, check libtsk_jni manifest."/> - - <property name="CRT.path">${thirdparty.dir}/crt/x86-32/${CRT.version}/crt.zip</property> - <available file="${CRT.path}" property="crtFound"/> - - <fail unless="crtFound" message="Detected CRT version ${CRT.version} not found in the thirdparty repo in path: ${CRT.path}"/> - - <!-- unzip from thirdparty repo to modules/lib in staged dir --> - <!-- <unzip src="${CRT.path}" dest="${zip-tmp}/${app.name}/bin"/> --> - <unzip src="${CRT.path}" dest="${zip-tmp}/${app.name}/${app.name}/modules/lib"/> - - <!-- delete 64 bit exe for now --> - <delete file="${zip-tmp}/${app.name}/bin/${app.name}64.exe" /> - </target> - - <target name="autoAIPath" > - <property name="AI.path">C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com</property> + <target name="autoAIPath" description="Attempt to find the AI path based on standard installation location"> + <property name="AI.path" value="C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com" /> <available file="${AI.path}" property="ai-exe-path" value="${AI.path}"/> + <echo message="${ai-exe-path}" /> </target> - - <target name="inputAIPath" unless="ai-exe-path"> + + <target name="inputAIPath" unless="ai-exe-path" description="Have the user input the path to the AI executable"> <input addProperty="ai-exe-path" message="Enter the location of AdvancedInstaller.com"/> </target> - <target name="run-advanced-installer" depends="autoAIPath,inputAIPath"> - <fail unless="ai-exe-path" message="Could not locate Advanced Installer."/> - <!-- Copy the template file to add details to --> - <copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${nbdist.dir}/installer_${app.name}.aip" overwrite="true"/> + <target name="run-advanced-installer" depends="add-ai-productinfo,add-ai-files,add-ai-shortcuts,add-ai-env"> + <!-- Leaving this commented out bit for documentation purposes. Not sure what its supposed to do. --> + <!-- Need to find a way to deal with beta version --> + <!--<echo message="Setting ${app.name} version to ${app.version}..."/> + <exec executable="${ai-exe-path}"> + <arg line="/edit ${aip-path} /SetVersion ${app.version}"/> + </exec>--> + + + <!--<delete file="${aip-path}"/>--> + </target> + + <target name="add-ai-productinfo" description="Add product information to the aip file"> <scriptdef name="generateguid" language="javascript"> <attribute name="property" /> <![CDATA[ @@ -78,8 +57,6 @@ ]]> </scriptdef> <generateguid property="guid1" /> - <property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/> - <property name="aip-path" value="${nbdist.dir}\installer_${app.name}.aip"/> <!-- automatically replace version name and productcode in the .aip file --> <echo>Product Code: ${guid1}</echo> <echo>Product Version: ${app.version}</echo> @@ -92,55 +69,36 @@ <replaceregexp file="${aip-path}" match="ProductVersion&quot; Value=&quot;\d+\.{1}\d+\.{1}\d+" replace="ProductVersion&quot; Value=&quot;${app.version}" /> - - <!-- Use Advanced Installer to configure files to add --> - <echo message="Adding files to installer..."/> + </target> + + <target name="add-ai-files" description="Add the files in the installation path to the installer file"> + <foreach target="add-file-or-dir" param="theFile" inheritall="true" inheritrefs="true"> + <path> + <fileset dir="${inst-path}"> + <include name="*" /> + </fileset> + <dirset dir="${inst-path}"> + <include name="*"/> + </dirset> + </path> + </foreach> + </target> + + <target name="add-file-or-dir" depends="is-file-or-folder"> + <echo message="${ai-exe-path}" /> + <echo message="Adding ${file-or-folder} to installer: ${theFile}"/> <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\bin"/> + <arg line="/edit ${aip-path} /Add${file-or-folder} APPDIR ${theFile}" /> </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\etc"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\gstreamer"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\harness"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\java"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\jre"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\platform"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFolder APPDIR ${inst-path}\${app.name}"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\icon.ico"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\KNOWN_ISSUES.txt"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\LICENSE-2.0.txt"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\NEWS.txt"/> - </exec> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /AddFile APPDIR ${inst-path}\README.txt"/> - </exec> - - <!-- Need to find a way to deal with beta version --> - <!--<echo message="Setting ${app.name} version to ${app.version}..."/> - <exec executable="${ai-exe-path}"> - <arg line="/edit ${aip-path} /SetVersion ${app.version}"/> - </exec>--> - + </target> + + <target name="is-file-or-folder"> + <condition property="file-or-folder" value="File" else="Folder"> + <available file="${theFile}" type="file" /> + </condition> + </target> + + <target name="add-ai-shortcuts" description="Add shortcuts to the aip file"> <echo message="Adding desktop/menu shortcuts..."/> <exec executable="${ai-exe-path}"> <arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir DesktopFolder -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/> @@ -148,7 +106,10 @@ <exec executable="${ai-exe-path}"> <arg line="/edit ${aip-path} /NewShortcut -name ${app.title} -dir SHORTCUTDIR -target APPDIR\bin\${app.name}.exe -icon ${inst-path}\icon.ico"/> </exec> - + </target> + + <!-- TODO: does this always need to be done, or just for 64 bit files? --> + <target name="add-ai-env" description="Add the enviornment variables to the aip file"> <echo message="Setting environment variables..."/> <exec executable="${ai-exe-path}"> <arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/> @@ -162,14 +123,6 @@ <exec executable="${ai-exe-path}"> <arg line="/build ${aip-path}"/> </exec> - <!--<delete file="${aip-path}"/>--> - </target> - - <!-- Makes an installer from the opened ZIP file --> - <target name="build-installer-windows"> - <antcall target="run-advanced-installer" /> - <!--<delete dir="${nbdist.dir}/${app.name}-installer"/>--> - <delete dir="${nbdist.dir}/installer_${app.name}-cache"/> - <move file="${nbdist.dir}/installer_${app.name}-SetupFiles/installer_${app.name}.msi" tofile="${nbdist.dir}/installer_${app.name}-${app.version}.msi" /> </target> + </project> diff --git a/build.xml b/build.xml index 8cd8a0d109..dbd3c005ca 100644 --- a/build.xml +++ b/build.xml @@ -54,10 +54,6 @@ </condition> <fail unless="jreFound" message="JRE_HOME must be set as an environment variable."/> </target> - - <target name="getExternals" depends="findTSK"> - <antcall target="copyTSKLibsToRelease" /> - </target> <target name="getJunit"> <property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" /> @@ -87,9 +83,7 @@ <copy todir="${zip-tmp}/${app.name}/jre"> <fileset dir="${env.JRE_HOME}"/> </copy> - <copy file="${basedir}/branding_${app.name}/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/> - <antcall target="copyExternalLibsToZip"></antcall> <property name="app.property.file" value="${zip-tmp}/${app.name}/etc/${app.name}.conf" /> <property name="jvm.options" value="&quot;--branding ${app.name} -J-Xms24m -J-Xmx512m -J-XX:MaxPermSize=128M -J-Xverify:none&quot;" /> @@ -101,6 +95,18 @@ <!-- workaround for ant escaping : and = when setting properties --> <replace file="${app.property.file}" token="@JVM_OPTIONS" value="${jvm.options}" /> + <!-- We want to remove the dlls in autopsy/modules/lib because they will + shadow the files in the autopsy/modules/lib/ARCHITECTURE folder in the JAR. + These files are legacy from when we used to copy the dlls to this location. + This check should do away in the future. Added Sept '13--> + <delete failonerror="false"> + <fileset dir="${zip-tmp}/${app.name}/autopsy/modules/lib"> + <include name="libtsk_jni.dll" /> + <include name="libewf.dll" /> + <include name="zlib.dll" /> + </fileset> + </delete> + <!-- step (4) zip again, but with the version numbers in the dir --> <zip destfile="${nbdist.dir}/${app.name}-${app.version}.zip"> <zipfileset dir="${zip-tmp}/${app.name}"/> @@ -133,7 +139,7 @@ <property name="app.version" value="${DSTAMP}"/> </target> - <target name="-init" depends="-taskdefs,-convert-old-project,getExternals,getProps,getJunit"> + <target name="-init" depends="-taskdefs,-convert-old-project,getProps,getJunit"> <convertclusterpath from="${cluster.path.evaluated}" to="cluster.path.final" id="cluster.path.id"/> <sortsuitemodules unsortedmodules="${modules}" sortedmodulesproperty="modules.sorted"/> <property name="cluster" location="build/cluster"/> @@ -231,9 +237,8 @@ </exec> </target> - <target name="versioning-script" depends="check-release"/> <!--, versioning-script-if-release, versioning-script-if-not-release"/>--> + <target name="versioning-script" depends="check-release, versioning-script-if-release, versioning-script-if-not-release"/> - <!-- opens the zip file into a folder in dist --> <target name="build-installer-dir" depends="getProps, versioning-script, build-zip" description="Builds Autopsy with branding and all dependencies" > <unzip src="${nbdist.dir}/${app.name}-${app.version}.zip" dest="${nbdist.dir}/${app.name}-installer"/> </target> @@ -241,7 +246,6 @@ <target name="build-installer" depends="build-installer-dir" description="Builds Autopsy installer."> <antcall target="build-installer-${os.family}" /> </target> - <target name="test-download-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-1"/> <available file="${test-input}/nps-2008-jean.E02" property="img-present-2"/> diff --git a/update_versions.py b/update_versions.py index 3fa12cd3dd..2883021c9f 100644 --- a/update_versions.py +++ b/update_versions.py @@ -23,9 +23,9 @@ # # http://wiki.sleuthkit.org/index.php?title=Autopsy_3_Module_Versions # -# The basic idea is that this script uses javadoc/jdiff to -# compare the current state of the source code to the last -# tag and identifies if APIs were removed, added, etc. +# The basic idea is that this script uses javadoc/jdiff to +# compare the current state of the source code to the last +# tag and identifies if APIs were removed, added, etc. # # When run from the Autopsy build script, this script will: # - Clone Autopsy and checkout to the previous release tag @@ -61,6 +61,12 @@ from shutil import move from tempfile import mkstemp from xml.dom.minidom import parse, parseString +# Jdiff return codes. Described in more detail further on +NO_CHANGES = 100 +COMPATIBLE = 101 +NON_COMPATIBLE = 102 +ERROR = 1 + # An Autopsy module object class Module: # Initialize it with a name, return code, and version numbers @@ -218,11 +224,11 @@ def compare_xml(module, apiname_tag, apiname_cur): log.close() code = jdiff.returncode print("Compared XML for " + module.name) - if code == 100: + if code == NO_CHANGES: print(" No API changes") - elif code == 101: + elif code == COMPATIBLE: print(" API Changes are backwards compatible") - elif code == 102: + elif code == NON_COMPATIBLE: print(" API Changes are not backwards compatible") else: print(" *Error in XML, most likely an empty module") @@ -564,18 +570,18 @@ def update_versions(modules, source): if manifest == None or project == None: print(" Error finding manifeset and project properties files") return - if module.ret == 101: + if module.ret == COMPATIBLE: versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]] set_specification(project, manifest, versions[0]) set_implementation(manifest, versions[1]) module.set_versions(versions) - elif module.ret == 102: + elif module.ret == NON_COMPATIBLE: versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1] set_specification(project, manifest, versions[0]) set_implementation(manifest, versions[1]) set_release(manifest, versions[2]) module.set_versions(versions) - elif module.ret == 100: + elif module.ret == NO_CHANGES: versions = [versions[0], versions[1] + 1, versions[2]] set_implementation(manifest, versions[1]) module.set_versions(versions) @@ -624,48 +630,40 @@ def print_version_updates(modules): f = open("gen_version.txt", "a") for module in modules: versions = module.versions - if module.ret == 101: + if module.ret == COMPATIBLE: output = (module.name + ":\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Updated Specification version:\t" + str(versions[0].increment()) + "\n") - output += ("\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") + output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].increment()) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n") + output += ("\tRelease:\tNo Change.\n") output += ("\n") print(output) sys.stdout.flush() f.write(output) - elif module.ret == 102: + elif module.ret == NON_COMPATIBLE: output = (module.name + ":\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Updated Specification version:\t" + str(versions[0].overflow()) + "\n") - output += ("\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") - output += ("\n") - output += (" Current Release version:\t\t" + str(versions[2]) + "\n") - output += (" Updated Release version:\t\t" + str(versions[2] + 1) + "\n") + output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].overflow()) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n") + output += ("\tRelease:\t" + str(versions[2]) + "\t->\t" + str(versions[2] + 1) + "\n") output += ("\n") print(output) sys.stdout.flush() f.write(output) - elif module.ret == 1: + elif module.ret == ERROR: output = (module.name + ":\n") - output += (" *Unable to detect necessary changes\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Current Release version:\t\t" + str(versions[2]) + "\n") + output += ("\t*Unable to detect necessary changes\n") + output += ("\tSpecification:\t" + str(versions[0]) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\n") + output += ("\tRelease:\t\t" + str(versions[2]) + "\n") output += ("\n") print(output) f.write(output) sys.stdout.flush() - elif module.ret == 100: + elif module.ret == NO_CHANGES: output = (module.name + ":\n") if versions[1] is None: - output += (" No Implementation version.\n") + output += ("\tImplementation: None\n") else: - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") + output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n") output += ("\n") print(output) sys.stdout.flush() @@ -673,16 +671,13 @@ def print_version_updates(modules): elif module.ret is None: output = ("Added " + module.name + ":\n") if module.spec() != "1.0" and module.spec() != "0.0": - output += (" Current Specification version:\t" + str(module.spec()) + "\n") - output += (" Updated Specification version:\t1.0\n") + output += ("\tSpecification:\t" + str(module.spec()) + "\t->\t" + "1.0\n") output += ("\n") if module.impl() != 1: - output += (" Current Implementation version:\t" + str(module.impl()) + "\n") - output += (" Updated Implementation version:\t1\n") + output += ("\tImplementation:\t" + str(module.impl()) + "\t->\t" + "1\n") output += ("\n") if module.release() != 1 and module.release() != 0: - output += (" Current Release version:\t\t" + str(module.release()) + "\n") - output += (" Updated Release version:\t\t1\n") + output += ("Release:\t\t" + str(module.release()) + "\t->\t" + "1\n") output += ("\n") print(output) sys.stdout.flush()