move ReadImageTask to ImageUtils, refactoring in IG to use readImageTask

This commit is contained in:
jmillman
2015-12-31 14:33:14 -05:00
parent b93360e80e
commit 8dc7208876
14 changed files with 629 additions and 393 deletions
@@ -21,23 +21,16 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static java.util.Objects.nonNull;
import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
@@ -49,13 +42,7 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javax.annotation.Nullable;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.stream.ImageInputStream;
import javax.swing.JPanel;
import org.controlsfx.control.MaskerPane;
import org.openide.util.NbBundle;
@@ -66,8 +53,6 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Image viewer part of the Media View layered pane. Uses JavaFX to display the
@@ -113,7 +98,7 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
.map("."::concat) //NOI18N
.collect(Collectors.toList());
private ReadImageTask readImageTask;
private Task<Image> readImageTask;
/**
* Creates new form MediaViewImagePanel
@@ -186,36 +171,31 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
if (readImageTask != null) {
readImageTask.cancel();
}
readImageTask = new ReadImageTask(file);
readImageTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent event) {
//Note that all error conditions are allready logged in readImageTask.succeeded()
if (!Case.isCaseOpen()) {
/*
* handle in-between condition when case is being closed
* and an image was previously selected
*/
reset();
return;
}
try {
Image fxImage = readImageTask.get();
if (nonNull(fxImage)) {
//we have non-null image show it
fxImageView.setImage(fxImage);
borderpane.setCenter(fxImageView);
} else {
showErrorNode(file);
}
} catch (InterruptedException | ExecutionException ex) {
showErrorNode(file);
}
borderpane.setCursor(Cursor.DEFAULT);
readImageTask = ImageUtils.newReadImageTask(file);
readImageTask.setOnSucceeded((WorkerStateEvent event) -> {
//Note that all error conditions are allready logged in readImageTask.succeeded()
if (!Case.isCaseOpen()) {
/*
* handle in-between condition when case is being closed and
* an image was previously selected
*/
reset();
return;
}
try {
Image fxImage = readImageTask.get();
if (nonNull(fxImage)) {
//we have non-null image show it
fxImageView.setImage(fxImage);
borderpane.setCenter(fxImageView);
} else {
showErrorNode(file);
}
} catch (InterruptedException | ExecutionException ex) {
showErrorNode(file);
}
borderpane.setCursor(Cursor.DEFAULT);
});
readImageTask.setOnFailed(new EventHandler<WorkerStateEvent>() {
@@ -230,11 +210,7 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
return;
}
externalViewerButton.setOnAction(actionEvent -> //fx ActionEvent
new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file))
.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")) //Swing ActionEvent //NOI18N
);
borderpane.setCenter(errorNode);
showErrorNode(file);
borderpane.setCursor(Cursor.DEFAULT);
}
});
@@ -295,136 +271,4 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
static private class ReadImageTask extends Task<Image> implements IIOReadProgressListener {
private final AbstractFile file;
volatile private BufferedImage bufferedImage = null;
ReadImageTask(AbstractFile file) {
this.file = file;
}
@Override
@NbBundle.Messages({
"# {0} - file name",
"LoadImageTask.mesageText=Reading image: {0}"})
protected Image call() throws Exception {
updateMessage(Bundle.LoadImageTask_mesageText(file.getName()));
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
if (ImageUtils.isGIF(file)) {
//directly read GIF to preserve potential animation,
Image image = new Image(new BufferedInputStream(inputStream));
if (image.isError() == false) {
return image;
}
//fall through to default iamge reading code if there was an error
}
ImageInputStream input = ImageIO.createImageInputStream(inputStream);
if (input == null) {
throw new IIOException("Could not create ImageInputStream."); //NOI18N
}
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.addIIOReadProgressListener(this);
reader.setInput(input);
/*
* This is the important part, get or create a ReadParam,
* create a destination image to hold the decoded result,
* then pass that image with the param.
*/
ImageReadParam param = reader.getDefaultReadParam();
bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
param.setDestination(bufferedImage);
try {
reader.read(0, param);
} catch (IOException iOException) {
// Ignore this exception or display a warning or similar, for exceptions happening during decoding
logError(iOException);
}
reader.removeIIOReadProgressListener(this);
return SwingFXUtils.toFXImage(bufferedImage, null);
} else {
throw new IIOException("No ImageReader found for file."); //NOI18N
}
}
}
public void logError(@Nullable Throwable e) {
String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N
try {
LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getName(), message}); //NOI18N
LOGGER.log(Level.SEVERE, "Failed to get unique path for file", tskCoreException); //NOI18N
}
}
@Override
protected void failed() {
super.failed();
logError(getException());
}
@Override
protected void succeeded() {
super.succeeded();
try {
Image fxImage = get();
if (fxImage == null) {
logError(null);
} else {
if (fxImage.isError()) {
//if there was somekind of error, log it
logError(fxImage.getException());
}
}
} catch (InterruptedException | ExecutionException ex) {
logError(ex.getCause());
}
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
//update this task with the progress reported by ImageReader.read
updateProgress(percentageDone, 100);
}
@Override
public void sequenceStarted(ImageReader source, int minIndex) {
}
@Override
public void sequenceComplete(ImageReader source) {
}
@Override
public void imageStarted(ImageReader source, int imageIndex) {
}
@Override
public void imageComplete(ImageReader source) {
}
@Override
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
}
@Override
public void thumbnailProgress(ImageReader source, float percentageDone) {
}
@Override
public void thumbnailComplete(ImageReader source) {
}
@Override
public void readAborted(ImageReader source) {
}
}
}
@@ -27,28 +27,37 @@ import com.google.common.io.Files;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.stream.ImageInputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.opencv.core.Core;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
@@ -141,8 +150,8 @@ public class ImageUtils {
/**
* thread that saves generated thumbnails to disk in the background
*/
private static final Executor imageSaver =
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
private static final Executor imageSaver
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern("icon saver-%d").build());
public static List<String> getSupportedImageExtensions() {
@@ -319,7 +328,7 @@ public class ImageUtils {
* @return a thumbnail for the given image or a default one if there was a
* problem making a thumbnail.
*/
public static Image getThumbnail(Content content, int iconSize) {
public static BufferedImage getThumbnail(Content content, int iconSize) {
if (content instanceof AbstractFile) {
AbstractFile file = (AbstractFile) content;
// If a thumbnail file is already saved locally
@@ -333,7 +342,7 @@ public class ImageUtils {
return thumbnail;
}
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error while reading image: " + content.getName(), ex); //NON-NLS
LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{content.getName(), ex.getLocalizedMessage()}); //NON-NLS
return generateAndSaveThumbnail(file, iconSize, cacheFile);
}
} else {
@@ -490,7 +499,7 @@ public class ImageUtils {
*
* @return Generated icon or null on error
*/
private static Image generateAndSaveThumbnail(AbstractFile file, int iconSize, File cacheFile) {
private static BufferedImage generateAndSaveThumbnail(AbstractFile file, int iconSize, File cacheFile) {
BufferedImage thumbnail = null;
try {
if (VideoUtils.isVideoThumbnailSupported(file)) {
@@ -537,13 +546,15 @@ public class ImageUtils {
* there was a problem.
*/
@Nullable
private static BufferedImage generateImageThumbnail(Content content, int iconSize) {
private static BufferedImage generateImageThumbnail(AbstractFile content, int iconSize) {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(content));) {
BufferedImage bi = ImageIO.read(inputStream);
try {
final ReadImageTask readImageTask = new ReadImageTask(content);
readImageTask.run();
BufferedImage bi = SwingFXUtils.fromFXImage(readImageTask.get(), null);
if (bi == null) {
LOGGER.log(Level.WARNING, "No image reader for file: {0}", content.getName()); //NON-NLS
return null;
}
try {
@@ -554,13 +565,191 @@ public class ImageUtils {
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
}
} catch (OutOfMemoryError e) {
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS
} catch (EOFException e) {
LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName() + ": " + e.getLocalizedMessage()); //NON-NLS
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.getLocalizedMessage()); //NON-NLS
}
return null;
}
static public int getWidth(AbstractFile file) throws IIOException, IOException {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
if (input == null) {
throw new IIOException("Could not create ImageInputStream."); //NOI18N
}
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.setInput(input);
return reader.getWidth(0);
} else {
throw new IIOException("No ImageReader found for file." + file.getName()); //NOI18N
}
}
}
}
static public int getHeight(AbstractFile file) throws IIOException, IOException {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
if (input == null) {
throw new IIOException("Could not create ImageInputStream."); //NOI18N
}
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.setInput(input);
return reader.getHeight(0);
} else {
throw new IIOException("No ImageReader found for file." + file.getName()); //NOI18N
}
}
}
}
public static Task<javafx.scene.image.Image> newReadImageTask(AbstractFile file) {
return new ReadImageTask(file);
}
static private class ReadImageTask extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
private final AbstractFile file;
volatile private BufferedImage bufferedImage = null;
ReadImageTask(AbstractFile file) {
this.file = file;
}
@Override
@NbBundle.Messages({
"# {0} - file name",
"LoadImageTask.mesageText=Reading image: {0}"})
protected javafx.scene.image.Image call() throws Exception {
updateMessage(Bundle.LoadImageTask_mesageText(file.getName()));
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
if (ImageUtils.isGIF(file)) {
//directly read GIF to preserve potential animation,
javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(inputStream));
if (image.isError() == false) {
return image;
}
//fall through to default iamge reading code if there was an error
}
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
if (input == null) {
throw new IIOException("Could not create ImageInputStream."); //NOI18N
}
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.addIIOReadProgressListener(this);
reader.setInput(input);
/*
* This is the important part, get or create a
* ReadParam, create a destination image to hold the
* decoded result, then pass that image with the param.
*/
ImageReadParam param = reader.getDefaultReadParam();
bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
param.setDestination(bufferedImage);
try {
reader.read(0, param);
} catch (IOException iOException) {
// Ignore this exception or display a warning or similar, for exceptions happening during decoding
logError(iOException);
}
reader.removeIIOReadProgressListener(this);
return SwingFXUtils.toFXImage(bufferedImage, null);
} else {
throw new IIOException("No ImageReader found for file."); //NOI18N
}
}
}
}
public void logError(@Nullable Throwable e) {
String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N
try {
LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getName(), message}); //NOI18N
LOGGER.log(Level.SEVERE, "Failed to get unique path for file", tskCoreException); //NOI18N
}
}
@Override
protected void failed() {
super.failed();
logError(getException());
}
@Override
protected void succeeded() {
super.succeeded();
try {
javafx.scene.image.Image fxImage = get();
if (fxImage == null) {
logError(null);
} else {
if (fxImage.isError()) {
//if there was somekind of error, log it
logError(fxImage.getException());
}
}
} catch (InterruptedException | ExecutionException ex) {
logError(ex.getCause());
}
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
//update this task with the progress reported by ImageReader.read
updateProgress(percentageDone, 100);
}
@Override
public void imageStarted(ImageReader source, int imageIndex) {
}
@Override
public void imageComplete(ImageReader source) {
updateProgress(100, 100);
}
@Override
public void sequenceStarted(ImageReader source, int minIndex) {
}
@Override
public void sequenceComplete(ImageReader source) {
}
@Override
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
}
@Override
public void thumbnailProgress(ImageReader source, float percentageDone) {
}
@Override
public void thumbnailComplete(ImageReader source) {
}
@Override
public void readAborted(ImageReader source) {
}
}
}
@@ -391,6 +391,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
"DetailViewPane.loggedTask.queryDb=Retreiving event data",
"DetailViewPane.loggedTask.name=Updating Details View",
"DetailViewPane.loggedTask.updateUI=Populating visualization",
"# {0} - the numner of events",
"DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?"})
private class DetailsUpdateTask extends VisualizationUpdateTask<Interval> {
@@ -30,7 +30,6 @@ import java.net.MalformedURLException;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.embed.swing.SwingFXUtils;
@@ -99,7 +98,7 @@ public enum ThumbnailCache {
try {
return cache.get(file.getId(), () -> load(file)).orElse(null);
} catch (UncheckedExecutionException | CacheLoader.InvalidCacheLoadException | ExecutionException ex) {
LOGGER.log(Level.WARNING, "failed to load icon for file: " + file.getName(), ex.getCause());
LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + file.getName(), ex.getCause());
return null;
}
}
@@ -109,7 +108,7 @@ public enum ThumbnailCache {
try {
return get(ImageGalleryController.getDefault().getFileFromId(fileID));
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed to load icon for file id : " + fileID, ex.getCause());
LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause());
return null;
}
}
@@ -130,27 +129,24 @@ public enum ThumbnailCache {
return Optional.of(new Image(new BufferedInputStream(new ReadContentInputStream(file.getAbstractFile())), MAX_THUMBNAIL_SIZE, MAX_THUMBNAIL_SIZE, true, true));
}
BufferedImage thumbnail = getCacheFile(file).map(new Function<File, BufferedImage>() {
@Override
public BufferedImage apply(File cachFile) {
if (cachFile.exists()) {
// If a thumbnail file is already saved locally, load it
try {
BufferedImage cachedThumbnail = ImageIO.read(cachFile);
BufferedImage thumbnail = getCacheFile(file).map((File cachFile) -> {
if (cachFile.exists()) {
// If a thumbnail file is already saved locally, load it
try {
BufferedImage cachedThumbnail = ImageIO.read(cachFile);
if (cachedThumbnail.getWidth() < MAX_THUMBNAIL_SIZE) {
return cachedThumbnail;
}
} catch (MalformedURLException ex) {
LOGGER.log(Level.WARNING, "Unable to parse cache file path: " + cachFile.getPath(), ex);
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Unable to read cache file " + cachFile.getPath(), ex);
if (cachedThumbnail.getWidth() < MAX_THUMBNAIL_SIZE) {
return cachedThumbnail;
}
} catch (MalformedURLException ex) {
LOGGER.log(Level.WARNING, "Unable to parse cache file path: " + cachFile.getPath(), ex);
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Unable to read cache file " + cachFile.getPath(), ex);
}
return null;
}
return null;
}).orElseGet(() -> {
return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE);
return ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE);
});
WritableImage jfxthumbnail;
@@ -176,7 +172,7 @@ public enum ThumbnailCache {
try {
return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_THUMBNAIL_SIZE));
} catch (IllegalStateException e) {
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Failed to create cache file.{0}", e.getLocalizedMessage());
return Optional.empty();
}
@@ -0,0 +1,56 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import java.awt.event.ActionEvent;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javax.swing.SwingUtilities;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Wraps {@link ExternalViewerAction} in a ControlsFX {@link Action} with
* appropriate text and graphic
*/
@NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer"})
public class OpenExternalViewerAction extends Action {
private static final Image EXTERNAL = new Image(OpenExternalViewerAction.class.getResource("/org/sleuthkit/autopsy/imagegallery/images/external.png").toExternalForm());
private static final ActionEvent ACTION_EVENT = new ActionEvent(OpenExternalViewerAction.class, ActionEvent.ACTION_PERFORMED, ""); //Swing ActionEvent //NOI18N
public OpenExternalViewerAction(AbstractFile file) {
super("External Viewer");
/**
* TODO: why is the name passed to the action? it means we duplicate
* this string all over the place -jm
*/
ExternalViewerAction externalViewerAction = new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file));
setLongText(Bundle.MediaViewImagePanel_externalViewerButton_text());
setEventHandler(actionEvent -> //fx ActionEvent
SwingUtilities.invokeLater(() -> externalViewerAction.actionPerformed(ACTION_EVENT))
);
setGraphic(new ImageView(EXTERNAL));
}
}
@@ -25,10 +25,12 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
import javafx.util.Pair;
import javax.annotation.Nonnull;
@@ -91,12 +93,36 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
private String model;
protected DrawableFile(T file, Boolean analyzed) {
/* @TODO: the two 'new Integer(0).shortValue()' values and null are
/*
* @TODO: the two 'new Integer(0).shortValue()' values and null are
* placeholders because the super constructor expects values i can't get
* easily at the moment. I assume this is related to why
* ReadContentInputStream can't read from DrawableFiles. */
* ReadContentInputStream can't read from DrawableFiles.
*/
super(file.getSleuthkitCase(), file.getId(), file.getAttrType(), file.getAttrId(), file.getName(), file.getType(), file.getMetaAddr(), (int) file.getMetaSeq(), file.getDirType(), file.getMetaType(), null, new Integer(0).shortValue(), file.getSize(), file.getCtime(), file.getCrtime(), file.getAtime(), file.getMtime(), new Integer(0).shortValue(), file.getUid(), file.getGid(), file.getMd5Hash(), file.getKnown(), file.getParentPath());
super(file.getSleuthkitCase(),
file.getId(),
file.getAttrType(),
file.getAttrId(),
file.getName(),
file.getType(),
file.getMetaAddr(),
(int) file.getMetaSeq(),
file.getDirType(),
file.getMetaType(),
null,
new Integer(0).shortValue(),
file.getSize(),
file.getCtime(),
file.getCrtime(),
file.getAtime(),
file.getMtime(),
new Integer(0).shortValue(),
file.getUid(),
file.getGid(),
file.getMd5Hash(),
file.getKnown(),
file.getParentPath());
this.analyzed = new SimpleBooleanProperty(analyzed);
this.file = file;
}
@@ -213,7 +239,9 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
return category;
}
/** set the category property to the most severe one found */
/**
* set the category property to the most severe one found
*/
private void updateCategory() {
try {
category.set(getSleuthkitCase().getContentTagsByContent(this).stream()
@@ -224,7 +252,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
.orElse(Category.ZERO)
);
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "problem looking up category for file " + this.getName(), ex);
LOGGER.log(Level.WARNING, "problem looking up category for file " + this.getName() + ex.getLocalizedMessage());
} catch (IllegalStateException ex) {
// We get here many times if the case is closed during ingest, so don't print out a ton of warnings.
}
@@ -234,7 +262,17 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
return ThumbnailCache.getDefault().get(this);
}
public abstract Image getFullSizeImage();
@Deprecated
public Image getFullSizeImage() {
try {
return getReadFullSizeImageTask().get();
} catch (InterruptedException | ExecutionException ex) {
return null;
}
}
public abstract Task<Image> getReadFullSizeImageTask();
public void setAnalyzed(Boolean analyzed) {
this.analyzed.set(analyzed);
@@ -18,17 +18,17 @@
*/
package org.sleuthkit.autopsy.imagegallery.datamodel;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils;
import javafx.beans.Observable;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
import javax.imageio.ImageIO;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream;
/**
* ImageGallery data model object that represents an image file. It is a
@@ -49,43 +49,53 @@ public class ImageFile<T extends AbstractFile> extends DrawableFile<T> {
}
@Override
public Image getFullSizeImage() {
public Task<Image> getReadFullSizeImageTask() {
Image image = (imageRef != null) ? imageRef.get() : null;
if (image == null || image.isError()) {
if (FileTypeUtils.isGIF(getAbstractFile())) {
//directly read gif to preserve potential animation,
image = new Image(new BufferedInputStream(new ReadContentInputStream(getAbstractFile())));
}
final Task<Image> newReadImageTask = ImageUtils.newReadImageTask(this.getAbstractFile());
newReadImageTask.stateProperty().addListener((Observable observable) -> {
switch (newReadImageTask.getState()) {
case CANCELLED:
break;
case FAILED:
break;
case SUCCEEDED:
try {
imageRef = new SoftReference<>(newReadImageTask.get());
} catch (InterruptedException | ExecutionException interruptedException) {
}
break;
}
});
return newReadImageTask;
} else {
return new Task<Image>() {
@Override
protected Image call() throws Exception {
return image;
}
};
}
if (image == null || image.isError()) {
try (BufferedInputStream readContentInputStream = new BufferedInputStream(new ReadContentInputStream(this.getAbstractFile()))) {
BufferedImage read = ImageIO.read(readContentInputStream);
image = SwingFXUtils.toFXImage(read, null);
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "unable to read file " + getName(), ex.getMessage());
return null;
}
}
imageRef = new SoftReference<>(image);
return image;
}
@Override
Double getWidth() {
final Image fullSizeImage = getFullSizeImage();
if (fullSizeImage != null) {
return fullSizeImage.getWidth();
try {
return (double) ImageUtils.getWidth(this.getAbstractFile());
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "getWidth", ex);
return -1.0;
}
return -1.0;
}
@Override
Double getHeight() {
final Image fullSizeImage = getFullSizeImage();
if (fullSizeImage != null) {
return fullSizeImage.getHeight();
try {
return (double) ImageUtils.getHeight(this.getAbstractFile());
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "getHeight", ex);
return -1.0;
}
return -1.0;
}
@Override
@@ -25,7 +25,10 @@ import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.beans.Observable;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import javafx.scene.media.Media;
@@ -51,16 +54,43 @@ public class VideoFile<T extends AbstractFile> extends DrawableFile<T> {
}
@Override
public Image getFullSizeImage() {
Image image = (null == imageRef) ? null : imageRef.get();
public Task<Image> getReadFullSizeImageTask() {
Image image = (imageRef != null) ? imageRef.get() : null;
if (image == null || image.isError()) {
Task<Image> newReadImageTask = new Task<Image>() {
if (image == null) {
final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024);
image = (bufferedImage == ImageUtils.getDefaultThumbnail()) ? null : SwingFXUtils.toFXImage(bufferedImage, null);
imageRef = new SoftReference<>(image);
@Override
protected Image call() throws Exception {
final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024);
return (bufferedImage == ImageUtils.getDefaultThumbnail())
? null
: SwingFXUtils.toFXImage(bufferedImage, null);
}
};
newReadImageTask.stateProperty().addListener((Observable observable) -> {
switch (newReadImageTask.getState()) {
case CANCELLED:
break;
case FAILED:
break;
case SUCCEEDED:
try {
imageRef = new SoftReference<>(newReadImageTask.get());
} catch (InterruptedException | ExecutionException interruptedException) {
}
break;
}
});
return newReadImageTask;
} else {
return new Task<Image>() {
@Override
protected Image call() throws Exception {
return image;
}
};
}
return image;
}
private SoftReference<Media> mediaRef;
@@ -90,7 +90,7 @@ public class DrawableTile extends DrawableTileBase {
}
@Override
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
CachedLoaderTask<Image, DrawableFile<?>> newReadImageTask(DrawableFile<?> file) {
return new ThumbnailLoaderTask(file);
}
@@ -99,4 +99,6 @@ public class DrawableTile extends DrawableTileBase {
return getFile().map(AbstractContent::getName).orElse("");
}
}
@@ -47,6 +47,7 @@ import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.actions.Presenter;
@@ -58,7 +59,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
@@ -67,6 +67,7 @@ import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@@ -221,15 +222,10 @@ public abstract class DrawableTileBase extends DrawableUIBase {
});
menuItems.add(contentViewer);
MenuItem externalViewer = new MenuItem(Bundle.DrawableTileBase_externalViewerAction_text());
final ExternalViewerAction externalViewerAction = new ExternalViewerAction(Bundle.DrawableTileBase_externalViewerAction_text(), new FileNode(file.getAbstractFile()));
externalViewer.setDisable(externalViewerAction.isEnabled() == false);
externalViewer.setOnAction((ActionEvent t) -> {
SwingUtilities.invokeLater(() -> {
externalViewerAction.actionPerformed(null);
});
});
OpenExternalViewerAction openExternalViewerAction = new OpenExternalViewerAction(file.getAbstractFile());
MenuItem externalViewer = ActionUtils.createMenuItem(openExternalViewerAction);
externalViewer.textProperty().unbind();
externalViewer.textProperty().bind(openExternalViewerAction.longTextProperty());
menuItems.add(externalViewer);
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
@@ -311,12 +307,12 @@ public abstract class DrawableTileBase extends DrawableUIBase {
updateSelectionState();
updateCategory();
updateFollowUpIcon();
updateUI();
updateContent();
updateMetaData();
}
}
private void updateUI() {
private void updateMetaData() {
getFile().ifPresent(file -> {
final boolean isVideo = file.isVideo();
final boolean hasHashSetHits = hasHashHit();
@@ -18,32 +18,46 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.lang.ref.SoftReference;
import java.util.Objects;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
*/
@NbBundle.Messages({"MediaViewImagePanel.errorLabel.text=Could not load file."})
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
static final Executor exec = Executors.newWorkStealingPool();
private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName());
@FXML
@@ -56,9 +70,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
private Optional<DrawableFile<?>> fileOpt = Optional.empty();
private Optional<Long> fileIDOpt = Optional.empty();
private Task<Image> imageTask;
private SoftReference<Image> imageCache;
private ProgressIndicator progressIndicator;
protected volatile Task<Image> imageTask;
public DrawableUIBase(ImageGalleryController controller) {
this.controller = controller;
@@ -107,20 +119,41 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
synchronized public void setFile(Long newFileID) {
if (getFileID().isPresent()) {
if (Objects.equals(newFileID, getFileID().get()) == false) {
if (Objects.nonNull(newFileID)) {
setFileHelper(newFileID);
}
// if (Objects.nonNull(newFileID)) {
setFileHelper(newFileID);
// }
}
} else if (Objects.nonNull(newFileID)) {
} else {//if (Objects.nonNull(newFileID)) {
setFileHelper(newFileID);
}
}
synchronized protected void updateContent() {
Node content = getContentNode();
Platform.runLater(() -> {
imageBorder.setCenter(content);
if (getFile().isPresent() == false) {
Platform.runLater(() -> imageBorder.setCenter(null));
} else {
DrawableFile<?> file = getFile().get();
//is an image
doReadImageTask(file);
}
}
synchronized Node doReadImageTask(DrawableFile<?> file) {
disposeContent();
final Task<Image> myTask = newReadImageTask(file);
imageTask = myTask;
Node progressNode = newProgressIndicator(myTask);
Platform.runLater(() -> imageBorder.setCenter(progressNode));
imageTask.setOnSucceeded((WorkerStateEvent event) -> {
showImage(file, myTask);//on fx thread already
});
imageTask.setOnFailed((WorkerStateEvent event) -> {
showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);//on fx thread already
});
exec.execute(myTask);
return progressNode;
}
synchronized protected void disposeContent() {
@@ -128,50 +161,52 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
imageTask.cancel(true);
}
imageTask = null;
imageCache = null;
Platform.runLater(() -> imageView.setImage(null));
}
ProgressIndicator getLoadingProgressIndicator() {
if (progressIndicator == null) {
progressIndicator = new ProgressIndicator();
}
return progressIndicator;
/**
*
* @param file the value of file
* @param imageTask the value of imageTask
*/
Node newProgressIndicator(final Task<?> imageTask) {
ProgressIndicator loadingProgressIndicator = new ProgressIndicator(-1);
loadingProgressIndicator.progressProperty().bind(imageTask.progressProperty());
return loadingProgressIndicator;
}
Node getContentNode() {
if (getFile().isPresent() == false) {
imageCache = null;
Platform.runLater(() -> {
if (imageView != null) {
imageView.setImage(null);
}
});
return null;
} else {
Image thumbnail = isNull(imageCache) ? null : imageCache.get();
if (nonNull(thumbnail)) {
Platform.runLater(() -> {
if (imageView != null) {
imageView.setImage(thumbnail);
}
});
return imageView;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void showImage(DrawableFile<?> file, Task<Image> imageTask) {
//Note that all error conditions are allready logged in readImageTask.succeeded()
try {
Image fxImage = imageTask.get();
if (nonNull(fxImage)) {
//we have non-null image show it
imageView.setImage(fxImage);
imageBorder.setCenter(imageView);
} else {
DrawableFile<?> file = getFile().get();
if (isNull(imageTask)) {
imageTask = getNewImageLoadTask(file);
new Thread(imageTask).start();
} else if (imageTask.isDone()) {
return null;
}
return getLoadingProgressIndicator();
showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
}
} catch (CancellationException ex) {
} catch (InterruptedException | ExecutionException ex) {
showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
}
}
abstract CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file);
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void showErrorNode(String errorMessage, AbstractFile file) {
Button createButton = ActionUtils.createButton(new OpenExternalViewerAction(file));
VBox vBox = new VBox(10,
new Label(errorMessage), createButton);
vBox.setAlignment(Pos.CENTER);
imageBorder.setCenter(vBox);
}
abstract Task<Image> newReadImageTask(DrawableFile<?> file);
abstract class CachedLoaderTask<X, Y extends DrawableFile<?>> extends Task<X> {
@@ -186,7 +221,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
return (isCancelled() == false) ? load() : null;
}
abstract X load();
abstract X load() throws Exception;
@Override
protected void succeeded() {
@@ -194,7 +229,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
if (isCancelled() == false) {
try {
saveToCache(get());
updateContent();
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.WARNING, "Failed to cache content for" + file.getName(), ex);
}
@@ -210,23 +244,9 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
abstract void saveToCache(X result);
}
abstract class ImageLoaderTask extends CachedLoaderTask<Image, DrawableFile<?>> {
class ThumbnailLoaderTask extends CachedLoaderTask<Image, DrawableFile<?>> {
public ImageLoaderTask(DrawableFile<?> file) {
super(file);
}
@Override
void saveToCache(Image result) {
synchronized (DrawableUIBase.this) {
imageCache = new SoftReference<>(result);
}
}
}
class ThumbnailLoaderTask extends ImageLoaderTask {
public ThumbnailLoaderTask(DrawableFile<?> file) {
ThumbnailLoaderTask(DrawableFile<?> file) {
super(file);
}
@@ -234,5 +254,10 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
Image load() {
return isCancelled() ? null : file.getThumbnail();
}
@Override
void saveToCache(Image result) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
}
@@ -27,7 +27,6 @@ import java.util.Objects;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import org.sleuthkit.autopsy.coreutils.Logger;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
@@ -52,6 +51,7 @@ import javafx.scene.text.Text;
import javafx.util.Pair;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
@@ -166,7 +166,7 @@ public class MetaDataPane extends DrawableUIBase {
}
@Override
protected synchronized void setFileHelper(Long newFileID) {
synchronized protected void setFileHelper(Long newFileID) {
setFileIDOpt(Optional.ofNullable(newFileID));
if (newFileID == null) {
Platform.runLater(() -> {
@@ -177,25 +177,24 @@ public class MetaDataPane extends DrawableUIBase {
});
} else {
disposeContent();
updateUI();
updateAttributesTable();
updateCategory();
updateContent();
}
}
@Override
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
CachedLoaderTask<Image, DrawableFile<?>> newReadImageTask(DrawableFile<?> file) {
return new ThumbnailLoaderTask(file);
}
public void updateUI() {
public void updateAttributesTable() {
getFile().ifPresent(file -> {
final List<Pair<DrawableAttribute<?>, Collection<?>>> attributesList = file.getAttributesList();
Platform.runLater(() -> {
tableView.getItems().clear();
tableView.getItems().setAll(attributesList);
});
updateCategory();
});
}
@@ -204,13 +203,15 @@ public class MetaDataPane extends DrawableUIBase {
return imageBorder;
}
/** {@inheritDoc } */
/**
* {@inheritDoc }
*/
@Subscribe
@Override
public void handleCategoryChanged(CategoryManager.CategoryChangeEvent evt) {
getFileID().ifPresent(fileID -> {
if (evt.getFileIDs().contains(fileID)) {
updateUI();
updateAttributesTable();
}
});
}
@@ -220,7 +221,7 @@ public class MetaDataPane extends DrawableUIBase {
public void handleTagAdded(ContentTagAddedEvent evt) {
getFileID().ifPresent((fileID) -> {
if (Objects.equals(evt.getAddedTag().getContent().getId(), fileID)) {
updateUI();
updateAttributesTable();
}
});
}
@@ -229,7 +230,7 @@ public class MetaDataPane extends DrawableUIBase {
public void handleTagDeleted(ContentTagDeletedEvent evt) {
getFileID().ifPresent((fileID) -> {
if (Objects.equals(evt.getDeletedTagInfo().getContentID(), fileID)) {
updateUI();
updateAttributesTable();
}
});
}
@@ -241,4 +242,5 @@ public class MetaDataPane extends DrawableUIBase {
getValueDisplayString(selectedItem)));
}
}
}
@@ -23,15 +23,19 @@ import java.lang.ref.SoftReference;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import static javafx.scene.input.KeyCode.LEFT;
import static javafx.scene.input.KeyCode.RIGHT;
@@ -40,7 +44,8 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaException;
import javafx.scene.media.MediaPlayer;
import javafx.scene.text.Text;
import org.controlsfx.control.MaskerPane;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
@@ -50,7 +55,9 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableUIBase.exec;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Displays the files of a group one at a time. Designed to be embedded in a
@@ -69,7 +76,7 @@ public class SlideShowView extends DrawableTileBase {
@FXML
private BorderPane footer;
private Task<Node> mediaTask;
SlideShowView(GroupPane gp, ImageGalleryController controller) {
super(gp, controller);
@@ -159,42 +166,87 @@ public class SlideShowView extends DrawableTileBase {
stopVideo();
super.disposeContent();
if (mediaTask != null) {
mediaTask.cancel(true);
}
mediaTask = null;
// if (mediaTask != null) {
// mediaTask.cancel(true);
// }
// mediaTask = null;
mediaCache = null;
}
private SoftReference<Node> mediaCache;
/**
* {@inheritDoc }
*/
@Override
Node getContentNode() {
synchronized protected void updateContent() {
if (getFile().isPresent() == false) {
mediaCache = null;
return super.getContentNode();
Platform.runLater(() -> imageBorder.setCenter(null));
} else {
DrawableFile<?> file = getFile().get();
if (file.isVideo()) {
//specially handling for videos
Node mediaNode = (isNull(mediaCache)) ? null : mediaCache.get();
if (nonNull(mediaNode)) {
return mediaNode;
Platform.runLater(() -> imageBorder.setCenter(mediaNode));
} else {
if (isNull(mediaTask)) {
mediaTask = new MediaLoadTask(((VideoFile<?>) file));
new Thread(mediaTask).start();
} else if (mediaTask.isDone()) {
return null;
}
return getLoadingProgressIndicator();
// if (isNull(mediaTask)) {
MediaLoadTask mediaTask = new MediaLoadTask(((VideoFile<?>) file));
Node progressNode = newProgressIndicator(mediaTask);
mediaTask.setOnSucceeded((WorkerStateEvent event) -> {
showMedia(file, mediaTask);//on fx thread already
});
mediaTask.setOnFailed((WorkerStateEvent event) -> {
showErrorNode(getMediaLoadErrorLabel(mediaTask), file);//on fx thread already
});
Platform.runLater(() -> imageBorder.setCenter(progressNode));
exec.execute(mediaTask);
// } else {
// //not on fx thread;
// Platform.runLater(() -> showMedia(file));
// }
}
} else {
super.updateContent();
}
return super.getContentNode();
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void showMedia(DrawableFile<?> file, Task<Node> mediaTask) {
//Note that all error conditions are allready logged in readImageTask.succeeded()
try {
Node mediaNode = mediaTask.get();
if (nonNull(mediaNode)) {
//we have non-null media node show it
imageBorder.setCenter(mediaNode);
} else {
showErrorNode(getMediaLoadErrorLabel(mediaTask), file);
}
} catch (InterruptedException | ExecutionException ex) {
showErrorNode(getMediaLoadErrorLabel(mediaTask), file);
}
}
private String getMediaLoadErrorLabel(Task<Node> mediaTask) {
return Bundle.MediaViewImagePanel_errorLabel_text() + ": " + mediaTask.getException().getLocalizedMessage();
}
/**
*
* @param file the value of file
* @param imageTask the value of imageTask
*/
@Override
Node newProgressIndicator(final Task<?> imageTask) {
MaskerPane maskerPane = new MaskerPane();
ProgressIndicator loadingProgressIndicator = new ProgressBar(-1);
maskerPane.setProgressNode(loadingProgressIndicator);
maskerPane.textProperty().bind(imageTask.messageProperty());
loadingProgressIndicator.progressProperty().bind(imageTask.progressProperty());
return maskerPane;
}
/**
* {@inheritDoc }
*/
@@ -250,46 +302,41 @@ public class SlideShowView extends DrawableTileBase {
}
@Override
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
Task<Image> newReadImageTask(DrawableFile<?> file) {
return file.getReadFullSizeImageTask();
return new ImageLoaderTask(file) {
@Override
Image load() {
return isCancelled() ? null : file.getFullSizeImage();
}
};
}
private class MediaLoadTask extends CachedLoaderTask<Node, VideoFile<?>> {
@NbBundle.Messages({"# {0} - file name",
"MediaLoadTask.messageText=Reading media: {0}"})
private class MediaLoadTask extends Task<Node> {
public MediaLoadTask(VideoFile<?> file) {
super(file);
private final VideoFile<?> file;
MediaLoadTask(VideoFile<?> file) {
this.file = file;
}
@Override
void saveToCache(Node result) {
synchronized (SlideShowView.this) {
mediaCache = new SoftReference<>(result);
}
}
@Override
Node load() {
protected Node call() throws Exception {
updateMessage(Bundle.MediaLoadTask_messageText(file.getName()));
try {
final Media media = file.getMedia();
return new VideoPlayer(new MediaPlayer(media), file);
} catch (MediaException | IOException | OutOfMemoryError ex) {
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "failed to initialize MediaControl for file " + file.getName(), ex);
try {
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to initialize VideoPlayer for file {0} : {1}", new Object[]{file.getUniquePath(), ex.getLocalizedMessage()});
} catch (TskCoreException tskCoreException) {
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to get unique path " + file.getName(), tskCoreException);
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to initialize VideoPlayer for file {0} : {1}", new Object[]{file.getName(), ex.getLocalizedMessage()});
}
if (file.isDisplayableAsImage()) {
Image fullSizeImage = file.getFullSizeImage();
Platform.runLater(() -> {
imageView.setImage(fullSizeImage);
});
return imageView;
return doReadImageTask(file);
}
return new Text(ex.getLocalizedMessage() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action.");
//if we couldn't even show it as an image. rethrow exception.
throw ex;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B