Fixed the date in the bundle properties

This commit is contained in:
Oliver Spohngellert 2016-01-07 09:54:50 -05:00
commit aff4d7864c
23 changed files with 1087 additions and 646 deletions

View File

@ -21,21 +21,17 @@ 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;
import javafx.scene.Scene;
@ -46,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;
@ -63,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
@ -110,7 +98,7 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
.map("."::concat) //NOI18N
.collect(Collectors.toList());
private LoadImageTask readImageTask;
private Task<Image> readImageTask;
/**
* Creates new form MediaViewImagePanel
@ -160,6 +148,14 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
});
}
private void showErrorNode(AbstractFile file) {
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);
}
/**
* Show the contents of the given AbstractFile as a visual image.
*
@ -175,7 +171,49 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
if (readImageTask != null) {
readImageTask.cancel();
}
readImageTask = new LoadImageTask(file);
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>() {
@Override
public void handle(WorkerStateEvent event) {
if (!Case.isCaseOpen()) {
/*
* handle in-between condition when case is being closed
* and an image was previously selected
*/
reset();
return;
}
showErrorNode(file);
borderpane.setCursor(Cursor.DEFAULT);
}
});
maskerPane.setProgressNode(progressBar);
progressBar.progressProperty().bind(readImageTask.progressProperty());
@ -233,170 +271,4 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
private class LoadImageTask extends Task<Image> implements IIOReadProgressListener {
private final AbstractFile file;
volatile private BufferedImage bufferedImage = null;
LoadImageTask(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
}
}
}
private void logError(@Nullable Throwable e) {
String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N
try {
LOGGER.log(Level.WARNING, "The MediaView tab could not read the image: {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.WARNING, "The MediaView tab could not read the image: {0}. {1}", new Object[]{file.getName(), message}); //NOI18N
LOGGER.log(Level.SEVERE, "Failes to get unique path for file", tskCoreException); //NOI18N
}
}
@Override
protected void failed() {
super.failed();
if (!Case.isCaseOpen()) {
/*
* handle in-between condition when case is being closed and an
* image was previously selected
*/
reset();
return;
}
handleError(getException());
borderpane.setCursor(Cursor.DEFAULT);
}
private void handleError(Throwable e) {
logError(e);
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);
}
@Override
protected void succeeded() {
super.succeeded();
if (!Case.isCaseOpen()) {
/*
* handle in-between condition when case is being closed and an
* image was previously selected
*/
reset();
return;
}
try {
Image fxImage = get();
if (fxImage == null) {
handleError(null);
} else {
//we have non-null image show it
fxImageView.setImage(fxImage);
borderpane.setCenter(fxImageView);
if (fxImage.isError()) {
//if there was somekind of error, log it
logError(fxImage.getException());
}
}
} catch (InterruptedException | ExecutionException ex) {
handleError(ex.getCause());
}
borderpane.setCursor(Cursor.DEFAULT);
}
@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) {
}
}
}

View File

@ -22,6 +22,8 @@ import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import org.openide.filesystems.FileObject;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* File and dir utilities
@ -29,6 +31,7 @@ import org.openide.filesystems.FileObject;
public class FileUtil {
private static final Logger logger = Logger.getLogger(FileUtil.class.getName());
private static String TEMP_FILE_PREFIX = "Autopsy";
/**
* Recursively delete all of the files and sub-directories in a directory.
@ -167,4 +170,28 @@ public class FileUtil {
//with underscores. We are only keeping \ as it could be part of the path.
return fileName.replaceAll("[/:\"*?<>|]+", "_");
}
/**
* Test if the current user has read and write access to the path.
*
* @param path The path to test for read and write access.
*
* @return True if we have both read and write access, false otherwise.
*/
public static boolean hasReadWriteAccess(Path path) {
Path p = null;
try {
p = Files.createTempFile(path, TEMP_FILE_PREFIX, null);
return (p.toFile().canRead() && p.toFile().canWrite());
} catch (IOException ex) {
return false;
} finally {
if (p != null) {
try {
p.toFile().delete();
} catch (Exception ignored) {
}
}
}
}
}

View File

@ -1,5 +1,4 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2012-15 Basis Technology Corp.
@ -27,28 +26,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.ObjectUtils;
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;
@ -59,31 +67,34 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utilities for working with Images and creating thumbnails. Reuses thumbnails
* by storing them in the case's cache directory.
* Utilities for working with image files and creating thumbnails. Reuses
* thumbnails by storing them in the case's cache directory.
*/
public class ImageUtils {
private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName());
private static final String COULD_NOT_WRITE_CACHE_THUMBNAIL = "Could not write cache thumbnail: "; //NOI18N
private static final String COULD_NOT_CREATE_IMAGE_INPUT_STREAM = "Could not create ImageInputStream."; //NOI18N
private static final String NO_IMAGE_READER_FOUND_FOR_ = "No ImageReader found for "; //NOI18N
/**
* save thumbnails to disk as this format
*/
private static final String FORMAT = "png"; //NON-NLS
private static final String FORMAT = "png"; //NON-NLS //NOI18N
public static final int ICON_SIZE_SMALL = 50;
public static final int ICON_SIZE_MEDIUM = 100;
public static final int ICON_SIZE_LARGE = 200;
private static final Logger logger = LOGGER;
private static final BufferedImage DEFAULT_THUMBNAIL;
private static final String IMAGE_GIF_MIME = "image/gif";
private static final String IMAGE_GIF_MIME = "image/gif"; //NOI18N
private static final SortedSet<String> GIF_MIME_SET = ImmutableSortedSet.copyOf(new String[]{IMAGE_GIF_MIME});
private static final List<String> SUPPORTED_IMAGE_EXTENSIONS;
private static final SortedSet<String> SUPPORTED_IMAGE_MIME_TYPES;
private static final List<String> CONDITIONAL_MIME_TYPES = Arrays.asList("audio/x-aiff", "application/octet-stream");
private static final List<String> CONDITIONAL_MIME_TYPES = Arrays.asList("audio/x-aiff", "application/octet-stream"); //NOI18N
private static final boolean openCVLoaded;
@ -91,9 +102,9 @@ public class ImageUtils {
ImageIO.scanForPlugins();
BufferedImage tempImage;
try {
tempImage = ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/file-icon.png"));//NON-NLS
tempImage = ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/file-icon.png"));//NON-NLS //NOI18N
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Failed to load default icon.", ex);
LOGGER.log(Level.SEVERE, "Failed to load default icon.", ex); //NOI18N
tempImage = null;
}
DEFAULT_THUMBNAIL = tempImage;
@ -102,16 +113,16 @@ public class ImageUtils {
boolean openCVLoadedTemp;
try {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) {
System.loadLibrary("opencv_ffmpeg248_64");
if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NOI18N
System.loadLibrary("opencv_ffmpeg248_64"); //NOI18N
} else {
System.loadLibrary("opencv_ffmpeg248");
System.loadLibrary("opencv_ffmpeg248"); //NOI18N
}
openCVLoadedTemp = true;
} catch (UnsatisfiedLinkError e) {
openCVLoadedTemp = false;
LOGGER.log(Level.SEVERE, "OpenCV Native code library failed to load", e);
LOGGER.log(Level.SEVERE, "OpenCV Native code library failed to load", e); //NOI18N
//TODO: show warning bubble
}
@ -129,8 +140,8 @@ public class ImageUtils {
"image/x-ms-bmp",
"image/x-portable-graymap",
"image/x-portable-bitmap",
"application/x-123")); //TODO: is this correct? -jm
SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals);
"application/x-123")); //TODO: is this correct? -jm //NOI18N
SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals); //NOI18N
}
/**
@ -143,7 +154,7 @@ public class ImageUtils {
*/
private static final Executor imageSaver =
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern("icon saver-%d").build());
.namingPattern("icon saver-%d").build()); //NOI18N
public static List<String> getSupportedImageExtensions() {
return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
@ -179,10 +190,12 @@ public class ImageUtils {
/**
* Can a thumbnail be generated for the content?
*
* @param content
* Although this method accepts Content, it always returns false for objects
* that are not instances of AbstractFile.
*
* @return
* @param content A content object to test for thumbnail support.
*
* @return true if a thumbnail can be generated for the given content.
*/
public static boolean thumbnailSupported(Content content) {
@ -199,12 +212,26 @@ public class ImageUtils {
}
/**
* is the file an image that we can read and generate a thumbnail for
*
* @param file
*
* @return true if the file is an image we can read and generate thumbnail
* for.
*/
public static boolean isImageThumbnailSupported(AbstractFile file) {
return isMediaThumbnailSupported(file, SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS, CONDITIONAL_MIME_TYPES)
|| hasImageFileHeader(file);
}
/**
* Does the image have a GIF mimetype.
*
* @param file
*
* @return true if the given file has a GIF mimetype
*/
public static boolean isGIF(AbstractFile file) {
try {
final FileTypeDetector fileTypeDetector = getFileTypeDetector();
@ -213,16 +240,16 @@ public class ImageUtils {
return IMAGE_GIF_MIME.equalsIgnoreCase(fileType);
}
} catch (TskCoreException | FileTypeDetectorInitException ex) {
LOGGER.log(Level.WARNING, "Failed to get mime type with FileTypeDetector.", ex);
LOGGER.log(Level.WARNING, "Failed to get mime type with FileTypeDetector.", ex); //NOI18N
}
LOGGER.log(Level.WARNING, "Falling back on direct mime type check.");
LOGGER.log(Level.WARNING, "Falling back on direct mime type check."); //NOI18N
switch (file.isMimeType(GIF_MIME_SET)) {
case TRUE:
return true;
case UNDEFINED:
LOGGER.log(Level.WARNING, "Falling back on extension check.");
return "gif".equals(file.getNameExtension());
LOGGER.log(Level.WARNING, "Falling back on extension check."); //NOI18N
return "gif".equals(file.getNameExtension()); //NOI18N
case FALSE:
default:
return false;
@ -230,7 +257,7 @@ public class ImageUtils {
}
/**
* Check if a file is "supported" by checking it mimetype and extension
* Check if a file is "supported" by checking its mimetype and extension
*
* //TODO: this should move to a better place. Should ImageUtils and
* VideoUtils both implement/extend some base interface/abstract class. That
@ -245,8 +272,8 @@ public class ImageUtils {
* @param conditionalMimes a set of mimetypes that a file could have to be
* supoprted if it also has a supported extension
*
* @return true if a thumbnail can be generated for the given file with the
* given lists of supported mimetype and extensions
* @return true if a thumbnail can be generated for the given file based on
* the given lists of supported mimetype and extensions
*/
static boolean isMediaThumbnailSupported(AbstractFile file, final SortedSet<String> supportedMimeTypes, final List<String> supportedExtension, List<String> conditionalMimes) {
if (file.getSize() == 0) {
@ -260,8 +287,7 @@ public class ImageUtils {
|| (conditionalMimes.contains(mimeType.toLowerCase()) && supportedExtension.contains(extension));
}
} catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) {
LOGGER.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
LOGGER.log(Level.WARNING, "Failed to look up mimetype for " + getContentPathSafe(file) + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex); //NOI18N
AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(supportedMimeTypes);
if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) {
return true;
@ -289,29 +315,27 @@ public class ImageUtils {
}
/**
* Get a thumbnail of a specified size. Generates the image if it is not
* already cached.
* Get a thumbnail of a specified size for the given image. Generates the
* thumbnail if it is not already cached.
*
* @param content
* @param iconSize
*
*
* @return a thumbnail for the given image or a default one if there was a
* problem making a thumbnail.
*
* @deprecated use {@link #getThumbnail(org.sleuthkit.datamodel.Content, int)
* } instead.
*
*/
@Nonnull
@Deprecated
public static Image getIcon(Content content, int iconSize) {
public static BufferedImage getIcon(Content content, int iconSize) {
return getThumbnail(content, iconSize);
}
/**
* Get a thumbnail of a specified size. Generates the image if it is not
* already cached.
* Get a thumbnail of a specified size for the given image. Generates the
* thumbnail if it is not already cached.
*
* @param content
* @param iconSize
@ -319,25 +343,17 @@ 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
File cacheFile = getCachedThumbnailLocation(content.getId());
if (cacheFile.exists()) {
try {
BufferedImage thumbnail = ImageIO.read(cacheFile);
if (isNull(thumbnail) || thumbnail.getWidth() != iconSize) {
return generateAndSaveThumbnail(file, iconSize, cacheFile);
} else {
return thumbnail;
}
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error while reading image: " + content.getName(), ex); //NON-NLS
return generateAndSaveThumbnail(file, iconSize, cacheFile);
}
} else {
return generateAndSaveThumbnail(file, iconSize, cacheFile);
Task<javafx.scene.image.Image> thumbnailTask = newGetThumbnailTask(file, iconSize, true);
thumbnailTask.run();
try {
return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.WARNING, "Failed to get thumbnail for {0}: " + ex.toString(), getContentPathSafe(content));
return DEFAULT_THUMBNAIL;
}
} else {
return DEFAULT_THUMBNAIL;
@ -345,8 +361,8 @@ public class ImageUtils {
}
/**
* Get a thumbnail of a specified size. Generates the image if it is not
* already cached.
* Get a thumbnail of a specified size for the given image. Generates the
* thumbnail if it is not already cached.
*
* @param content
* @param iconSize
@ -367,8 +383,8 @@ public class ImageUtils {
/**
*
* Get a thumbnail of a specified size. Generates the image if it is not
* already cached.
* Get a thumbnail of a specified size for the given image. Generates the
* thumbnail if it is not already cached.
*
* @param content
* @param iconSize
@ -390,7 +406,6 @@ public class ImageUtils {
*
* @return
*
*
* @deprecated use {@link #getCachedThumbnailLocation(long) } instead
*/
@Deprecated
@ -400,18 +415,26 @@ public class ImageUtils {
}
/**
* Get a file object for where the cached icon should exist. The returned
* file may not exist.
* Get a file object for where the cached thumbnail should exist. The
* returned file may not exist.
*
* @param fileID
*
* @return
*
* @return a File object representing the location of the cached thumbnail.
* This file may not actually exist(yet).
*/
private static File getCachedThumbnailLocation(long fileID) {
return Paths.get(Case.getCurrentCase().getCacheDirectory(), "thumbnails", fileID + ".png").toFile();
return Paths.get(Case.getCurrentCase().getCacheDirectory(), "thumbnails", fileID + ".png").toFile(); //NOI18N
}
/**
* Do a direct check to see if the given file has an image file header.
* NOTE: Currently only jpeg and png are supported.
*
* @param file
*
* @return true if the given file has one of the supported image headers.
*/
public static boolean hasImageFileHeader(AbstractFile file) {
return isJpegFileHeader(file) || isPngFileHeader(file);
}
@ -476,91 +499,426 @@ public class ImageUtils {
if (bytesRead != buffLength) {
//ignore if can't read the first few bytes, not an image
throw new TskCoreException("Could not read " + buffLength + " bytes from " + file.getName());
throw new TskCoreException("Could not read " + buffLength + " bytes from " + file.getName()); //NOI18N
}
return fileHeaderBuffer;
}
/**
* Generate an icon and save it to specified location.
* Get the width of the given image, in pixels.
*
* @param file File to generate icon for
* @param iconSize
* @param cacheFile Location to save thumbnail to
* @param file
*
* @return Generated icon or null on error
* @return the width in pixels
*
* @throws IOException If the file is not a supported image or the width
* could not be determined.
*/
private static Image generateAndSaveThumbnail(AbstractFile file, int iconSize, File cacheFile) {
BufferedImage thumbnail = null;
try {
if (VideoUtils.isVideoThumbnailSupported(file)) {
if (openCVLoaded) {
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
} else {
return DEFAULT_THUMBNAIL;
}
} else {
thumbnail = generateImageThumbnail(file, iconSize);
}
if (thumbnail == null) {
return DEFAULT_THUMBNAIL;
} else {
BufferedImage toSave = thumbnail;
imageSaver.execute(() -> {
try {
Files.createParentDirs(cacheFile);
if (cacheFile.exists()) {
cacheFile.delete();
}
ImageIO.write(toSave, FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex1) {
LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + file, ex1); //NON-NLS
}
});
}
} catch (NullPointerException ex) {
logger.log(Level.WARNING, "Could not write cache thumbnail: " + file, ex); //NON-NLS
}
return thumbnail;
static public int getImageWidth(AbstractFile file) throws IOException {
return getImageProperty(file,
"ImageIO could not determine width of {0}: ", //NOI18N
imageReader -> imageReader.getWidth(0)
);
}
/**
* Get the height of the given image,in pixels.
*
* Generate and return a scaled image
* @param file
*
* @param content
* @param iconSize
* @return the height in pixels
*
* @return a Thumbnail of the given content at the given size, or null if
* there was a problem.
* @throws IOException If the file is not a supported image or the height
* could not be determined.
*/
@Nullable
private static BufferedImage generateImageThumbnail(Content content, int iconSize) {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(content));) {
BufferedImage bi = ImageIO.read(inputStream);
if (bi == null) {
LOGGER.log(Level.WARNING, "No image reader for file: {0}", content.getName()); //NON-NLS
return null;
}
try {
return ScalrWrapper.resizeFast(bi, iconSize);
} catch (IllegalArgumentException e) {
// if resizing does not work due to extreme aspect ratio,
// crop the image instead.
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
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
}
return null;
static public int getImageHeight(AbstractFile file) throws IOException {
return getImageProperty(file,
"ImageIO could not determine height of {0}: ", //NOI18N
imageReader -> imageReader.getHeight(0)
);
}
/**
* Functional interface for methods that extract a property out of an
* ImageReader. Initially created to abstract over
* {@link #getImageHeight(org.sleuthkit.datamodel.AbstractFile)} and
* {@link #getImageWidth(org.sleuthkit.datamodel.AbstractFile)}
*
* @param <T> The type of the property.
*/
@FunctionalInterface
private static interface PropertyExtractor<T> {
public T extract(ImageReader reader) throws IOException;
}
/**
* Private template method designed to be used as the implementation of
* public methods that pull particular (usually meta-)data out of a image
* file. ./**
*
* @param <T> the type of the property to be retrieved.
* @param file the file to extract the data from
* @param errorTemplate a message template used to log errors. Should
* take one parameter: the file's unique path or
* name.
* @param propertyExtractor an implementation of {@link PropertyExtractor}
* used to retrieve the specific property.
*
* @return the the value of the property extracted by the given
* propertyExtractor
*
* @throws IOException if there was a problem reading the property from the
* file.
*
* @see PropertyExtractor
* @see #getImageHeight(org.sleuthkit.datamodel.AbstractFile)
*/
private static <T> T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor<T> propertyExtractor) throws IOException {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
if (input == null) {
IIOException iioException = new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM);
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
throw iioException;
}
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.setInput(input);
try {
return propertyExtractor.extract(reader);
} catch (IOException ex) {
LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
throw ex;
} finally {
reader.dispose();
}
} else {
IIOException iioException = new IIOException(NO_IMAGE_READER_FOUND_FOR_ + getContentPathSafe(file));
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
throw iioException;
}
}
}
}
/**
* Create a new {@link Task} that will get a thumbnail for the given image
* of the specified size. If a cached thumbnail is available it will be
* returned as the result of the task, otherwise a new thumbnail will be
* created and cached.
*
* Note: the returned task is suitable for running in a background thread,
* but is not started automatically. Clients are responsible for running the
* task, monitoring its progress, and using its result.
*
* @param file the file to create a thumbnail for
* @param iconSize the size of the thumbnail
*
* @return a new Task that returns a thumbnail as its result.
*/
public static Task<javafx.scene.image.Image> newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
return new GetThumbnailTask(file, iconSize, defaultOnFailure);
}
/**
* A Task that gets cached thumbnails and makes new ones as needed.
*/
static private class GetThumbnailTask extends ReadImageTaskBase {
private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read image for thumbnail generation."; //NOI18N
private final int iconSize;
private final File cacheFile;
private final boolean defaultOnFailure;
@NbBundle.Messages({"# {0} - file name",
"GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", "# {0} - file name",
"GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
super(file);
updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
this.iconSize = iconSize;
this.defaultOnFailure = defaultOnFailure;
this.cacheFile = getCachedThumbnailLocation(file.getId());
}
@Override
protected javafx.scene.image.Image call() throws Exception {
// If a thumbnail file is already saved locally, just read that.
if (cacheFile.exists()) {
try {
BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
return SwingFXUtils.toFXImage(cachedThumbnail, null);
}
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
}
}
//There was no correctly-sized cached thumbnail so make one.
BufferedImage thumbnail = null;
if (VideoUtils.isVideoThumbnailSupported(file)) {
if (openCVLoaded) {
updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
} else if (defaultOnFailure) {
thumbnail = DEFAULT_THUMBNAIL;
} else {
throw new IIOException("Failed to read image for thumbnail generation.");
}
} else {
//read the image into abuffered image.
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null);
if (null == bufferedImage) {
LOGGER.log(Level.WARNING, FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION);
throw new IIOException(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION);
}
updateProgress(-1, 1);
//resize, or if that fails, crop it
try {
thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
} catch (IllegalArgumentException | OutOfMemoryError e) {
// if resizing does not work due to extreme aspect ratio or oom, crop the image instead.
LOGGER.log(Level.WARNING, "Could not scale image {0}: " + e.toString() + ". Attemptying to crop {0} instead", ImageUtils.getContentPathSafe(file)); //NOI18N
final int height = bufferedImage.getHeight();
final int width = bufferedImage.getWidth();
if (iconSize < height || iconSize < width) {
final int cropHeight = Math.min(iconSize, height);
final int cropWidth = Math.min(iconSize, width);
try {
thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
} catch (Exception cropException) {
LOGGER.log(Level.WARNING, "Could not crop image {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
throw cropException;
}
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not scale image {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
throw e;
}
}
updateProgress(-1, 1);
//if we got a valid thumbnail save it
if (nonNull(thumbnail) && DEFAULT_THUMBNAIL != thumbnail) {
saveThumbnail(thumbnail);
}
return SwingFXUtils.toFXImage(thumbnail, null);
}
/**
* submit the thumbnail saving to another background thread.
*
* @param thumbnail
*/
private void saveThumbnail(BufferedImage thumbnail) {
imageSaver.execute(() -> {
try {
Files.createParentDirs(cacheFile);
if (cacheFile.exists()) {
cacheFile.delete();
}
ImageIO.write(thumbnail, FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex) {
LOGGER.log(Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
}
});
}
}
/**
* Create a new {@link Task} that will read the fileinto memory as an
* {@link javafx.scene.image.Image}
*
* Note: the returned task is suitable for running in a background thread,
* but is not started automatically. Clients are responsible for running the
* task, monitoring its progress, and using its result.
*
* @param file the file to read as an Image
*
* @return a new Task that returns an Image as its result
*/
public static Task<javafx.scene.image.Image> newReadImageTask(AbstractFile file) {
return new ReadImageTask(file);
}
/**
* A task that reads the content of a AbstractFile as a javafx Image.
*/
static private class ReadImageTask extends ReadImageTaskBase {
ReadImageTask(AbstractFile file) {
super(file);
updateMessage(Bundle.LoadImageTask_mesageText(file.getName()));
}
@Override
@NbBundle.Messages({
"# {0} - file name",
"LoadImageTask.mesageText=Reading image: {0}"})
protected javafx.scene.image.Image call() throws Exception {
return readImage();
}
}
/**
* Base class for tasks that need to read AbstractFiles as Images.
*/
static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
private static final String IMAGE_IO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NOI18N
final AbstractFile file;
private ImageReader reader;
ReadImageTaskBase(AbstractFile file) {
this.file = file;
}
protected javafx.scene.image.Image readImage() throws IOException {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
if (ImageUtils.isGIF(file)) {
//use JavaFX to 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 image reading code if there was an error
}
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
if (input == null) {
throw new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM);
}
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
//we use the first ImageReader, is there any point to trying the others?
if (readers.hasNext()) {
reader = readers.next();
reader.addIIOReadProgressListener(this);
reader.setInput(input);
/*
* This is the important part, get or create a
* ImageReadParam, create a destination image to hold
* the decoded result, then pass that image with the
* param.
*/
ImageReadParam param = reader.getDefaultReadParam();
BufferedImage bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
param.setDestination(bufferedImage);
try {
bufferedImage = reader.read(0, param); //should always be same bufferedImage object
if (isCancelled()) {
return null;
}
} catch (IOException iOException) {
// Ignore this exception or display a warning or similar, for exceptions happening during decoding
LOGGER.log(Level.WARNING, IMAGE_IO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
} finally {
reader.removeIIOReadProgressListener(this);
reader.dispose();
}
return SwingFXUtils.toFXImage(bufferedImage, null);
} else {
throw new IIOException(NO_IMAGE_READER_FOUND_FOR_ + ImageUtils.getContentPathSafe(file));
}
}
}
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
//update this task with the progress reported by ImageReader.read
updateProgress(percentageDone, 100);
if (isCancelled()) {
reader.abort();
reader.dispose();
}
}
@Override
protected void succeeded() {
super.succeeded();
try {
javafx.scene.image.Image fxImage = get();
if (fxImage == null) {
LOGGER.log(Level.WARNING, IMAGE_IO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT, ImageUtils.getContentPathSafe(file));
} else {
if (fxImage.isError()) {
//if there was somekind of error, log it
LOGGER.log(Level.WARNING, IMAGE_IO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file));
}
}
} catch (InterruptedException | ExecutionException ex) {
failed();
}
}
@Override
protected void failed() {
super.failed();
LOGGER.log(Level.WARNING, IMAGE_IO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file));
}
@Override
public void imageComplete(ImageReader source) {
updateProgress(100, 100);
}
@Override
public void imageStarted(ImageReader source, int imageIndex) {
}
@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) {
}
}
/**
* Get the unique path for the content, or if that fails, just return the
* name.
*
* @param content
*
* @return
*/
private static String getContentPathSafe(Content content) {
try {
return content.getUniquePath();
} catch (TskCoreException tskCoreException) {
String contentName = content.getName();
LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N
return contentName;
}
}
}

View File

@ -24,3 +24,5 @@ PhotoRecIngestModule.cancelledByUser=PhotoRec cancelled by user.
PhotoRecIngestModule.error.exitValue=PhotoRec carver returned error exit value = {0} when scanning {1}
PhotoRecIngestModule.error.msg=Error processing {0} with PhotoRec carver.
PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving\:
PhotoRecIngestModule.PermissionsNotSufficient=Insufficient permissions accessing
PhotoRecIngestModule.PermissionsNotSufficientSeeReference=See "Shared Drive Authentication" in Autopsy help.

View File

@ -77,6 +77,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
private static final String PHOTOREC_REPORT = "report.xml"; //NON-NLS
private static final String LOG_FILE = "run_log.txt"; //NON-NLS
private static final String TEMP_DIR_NAME = "temp"; // NON-NLS
private static final String SEP = System.getProperty("line.separator");
private static final Logger logger = Logger.getLogger(PhotoRecCarverFileIngestModule.class.getName());
private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
@ -377,15 +378,21 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
*/
synchronized Path createModuleOutputDirectoryForCase() throws IngestModule.IngestModuleException {
Path path = Paths.get(Case.getCurrentCase().getModuleDirectory(), PhotoRecCarverIngestModuleFactory.getModuleName());
if (UNCPathUtilities.isUNC(path)) {
// if the UNC path is using an IP address, convert to hostname
path = uncPathUtilities.ipToHostName(path);
if (path == null) {
throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.nonHostnameUNCPathUsed"));
}
}
try {
Files.createDirectory(path);
if (UNCPathUtilities.isUNC(path)) {
// if the UNC path is using an IP address, convert to hostname
path = uncPathUtilities.ipToHostName(path);
if (path == null) {
throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.nonHostnameUNCPathUsed"));
}
if (false == FileUtil.hasReadWriteAccess(path)) {
throw new IngestModule.IngestModuleException(
NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficient")
+ SEP + path.toString() + SEP // SEP is line breaks to make the dialog display nicely.
+ NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficientSeeReference"));
}
}
} catch (FileAlreadyExistsException ex) {
// No worries.
} catch (IOException | SecurityException | UnsupportedOperationException ex) {

View File

@ -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} - number 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> {

View File

@ -30,14 +30,16 @@ 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.Observable;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@ -64,7 +66,7 @@ public enum ThumbnailCache {
* in memory cache. keeps at most 1000 items each for up to 10 minutes.
* items may be garbage collected if there are no strong references to them.
*/
private final Cache<Long, Optional<Image>> cache = CacheBuilder.newBuilder()
private final Cache<Long, Image> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.softValues()
.expireAfterAccess(10, TimeUnit.MINUTES).build();
@ -97,9 +99,9 @@ public enum ThumbnailCache {
@Nullable
public Image get(DrawableFile<?> file) {
try {
return cache.get(file.getId(), () -> load(file)).orElse(null);
return cache.get(file.getId(), () -> load(file));
} 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 +111,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;
}
}
@ -122,35 +124,32 @@ public enum ThumbnailCache {
*
* @return an (possibly empty) optional containing a thumbnail
*/
private Optional<Image> load(DrawableFile<?> file) {
private Image load(DrawableFile<?> file) {
if (FileTypeUtils.isGIF(file)) {
//directly read gif to preserve potential animation,
//NOTE: not saved to disk!
return Optional.of(new Image(new BufferedInputStream(new ReadContentInputStream(file.getAbstractFile())), MAX_THUMBNAIL_SIZE, MAX_THUMBNAIL_SIZE, true, true));
return 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(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;
@ -161,7 +160,7 @@ public enum ThumbnailCache {
jfxthumbnail = SwingFXUtils.toFXImage(thumbnail, null);
}
return Optional.ofNullable(jfxthumbnail); //return icon, or null if generation failed
return jfxthumbnail; //return icon, or null if generation failed
}
/**
@ -169,16 +168,40 @@ public enum ThumbnailCache {
*
* @param id the obj id of the file to get a cache file for
*
* @return a Optional containing a File to store the cahced icon in or an
* @return a Optional containing a File to store the cached icon in or an
* empty optional if there was a problem.
*/
private static Optional<File> getCacheFile(DrawableFile<?> file) {
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();
}
}
public Task<Image> getThumbnailTask(DrawableFile<?> file) {
final Image thumbnail = cache.getIfPresent(file.getId());
if (thumbnail != null) {
return new Task<Image>() {
@Override
protected Image call() throws Exception {
return thumbnail;
}
};
}
final Task<Image> newGetThumbnailTask = ImageUtils.newGetThumbnailTask(file.getAbstractFile(), MAX_THUMBNAIL_SIZE, false);
newGetThumbnailTask.stateProperty().addListener((Observable observable) -> {
switch (newGetThumbnailTask.getState()) {
case SUCCEEDED:
try {
cache.put(Long.MIN_VALUE, newGetThumbnailTask.get());
} catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
}
}
});
return newGetThumbnailTask;
}
}

View File

@ -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));
}
}

View File

@ -23,12 +23,13 @@ import java.util.ArrayList;
import java.util.Collection;
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;
@ -38,6 +39,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
@ -77,6 +79,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
}
SoftReference<Image> imageRef;
// SoftReference<Image> thumbref;
private String drawablePath;
@ -91,12 +94,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 +240,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,17 +253,59 @@ 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.
}
}
@Deprecated
public Image getThumbnail() {
return ThumbnailCache.getDefault().get(this);
try {
return getThumbnailTask().get();
} catch (InterruptedException | ExecutionException ex) {
return null;
}
}
public abstract Image getFullSizeImage();
public Task<Image> getThumbnailTask() {
return ThumbnailCache.getDefault().getThumbnailTask(this);
}
@Deprecated //use non-blocking getReadFullSizeImageTask instead for most cases
public Image getFullSizeImage() {
try {
return getReadFullSizeImageTask().get();
} catch (InterruptedException | ExecutionException ex) {
return null;
}
}
public Task<Image> getReadFullSizeImageTask() {
Image image = (imageRef != null) ? imageRef.get() : null;
if (image == null || image.isError()) {
Task<Image> readImageTask = getReadFullSizeImageTaskHelper();
readImageTask.stateProperty().addListener(stateProperty -> {
switch (readImageTask.getState()) {
case SUCCEEDED:
try {
imageRef = new SoftReference<>(readImageTask.get());
} catch (InterruptedException | ExecutionException exception) {
LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe());
}
break;
}
});
return readImageTask;
} else {
return TaskUtils.taskFrom(() -> image);
}
}
abstract String getMessageTemplate(Exception exception);
abstract Task<Image> getReadFullSizeImageTaskHelper();
public void setAnalyzed(Boolean analyzed) {
this.analyzed.set(analyzed);
@ -266,11 +337,6 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
}
}
public boolean isDisplayableAsImage() {
Image thumbnail = getThumbnail();
return Objects.nonNull(thumbnail) && thumbnail.errorProperty().get() == false;
}
@Nonnull
public Set<String> getHashSetNamesUnchecked() {
try {
@ -280,4 +346,22 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
return Collections.emptySet();
}
}
/**
* Get the unique path for this DrawableFile, or if that fails, just return
* the name.
*
* @param content
*
* @return
*/
public String getContentPathSafe() {
try {
return this.getUniquePath();
} catch (TskCoreException tskCoreException) {
String contentName = this.getName();
LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N
return contentName;
}
}
}

View File

@ -18,17 +18,13 @@
*/
package org.sleuthkit.autopsy.imagegallery.datamodel;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.lang.ref.SoftReference;
import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils;
import java.io.IOException;
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 +45,31 @@ public class ImageFile<T extends AbstractFile> extends DrawableFile<T> {
}
@Override
public Image getFullSizeImage() {
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())));
}
}
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;
String getMessageTemplate(final Exception exception) {
return "Failed to read image {0}: " + exception.toString();
}
@Override
Task<Image> getReadFullSizeImageTaskHelper() {
return ImageUtils.newReadImageTask(this.getAbstractFile());
}
@Override
Double getWidth() {
final Image fullSizeImage = getFullSizeImage();
if (fullSizeImage != null) {
return fullSizeImage.getWidth();
try {
return (double) ImageUtils.getImageWidth(this.getAbstractFile());
} catch (IOException ex) {
return -1.0;
}
return -1.0;
}
@Override
Double getHeight() {
final Image fullSizeImage = getFullSizeImage();
if (fullSizeImage != null) {
return fullSizeImage.getHeight();
try {
return (double) ImageUtils.getImageHeight(this.getAbstractFile());
} catch (IOException ex) {
return -1.0;
}
return -1.0;
}
@Override

View File

@ -19,14 +19,11 @@
package org.sleuthkit.autopsy.imagegallery.datamodel;
import com.google.common.io.Files;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
import javafx.scene.media.Media;
import javafx.scene.media.MediaException;
@ -40,6 +37,8 @@ import org.sleuthkit.datamodel.AbstractFile;
public class VideoFile<T extends AbstractFile> extends DrawableFile<T> {
private static final Logger LOGGER = Logger.getLogger(VideoFile.class.getName());
private static final Image VIDEO_ICON = new Image("org/sleuthkit/autopsy/imagegallery/images/Clapperboard.png");
VideoFile(T file, Boolean analyzed) {
@ -50,17 +49,16 @@ public class VideoFile<T extends AbstractFile> extends DrawableFile<T> {
return VIDEO_ICON;
}
@Override
public Image getFullSizeImage() {
Image image = (null == imageRef) ? null : imageRef.get();
String getMessageTemplate(final Exception exception) {
return "Failed to get image preview for video {0}: " + exception.toString();
}
if (image == null) {
final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024);
image = (bufferedImage == ImageUtils.getDefaultThumbnail()) ? null : SwingFXUtils.toFXImage(bufferedImage, null);
imageRef = new SoftReference<>(image);
}
return image;
@Override
Task<Image> getReadFullSizeImageTaskHelper() {
return ImageUtils.newGetThumbnailTask(getAbstractFile(), 1024, false);
}
private SoftReference<Media> mediaRef;
@ -74,13 +72,11 @@ public class VideoFile<T extends AbstractFile> extends DrawableFile<T> {
final File cacheFile = VideoUtils.getTempVideoFile(this.getAbstractFile());
if (cacheFile.exists() == false || cacheFile.length() < getAbstractFile().getSize()) {
Files.createParentDirs(cacheFile);
ProgressHandle progressHandle = ProgressHandleFactory.createHandle("writing temporary file to disk");
progressHandle.start(100);
ContentUtils.writeToFile(this.getAbstractFile(), cacheFile, progressHandle, null, true);
progressHandle.finish();
}
media = new Media(Paths.get(cacheFile.getAbsolutePath()).toUri().toString());
@ -89,18 +85,6 @@ public class VideoFile<T extends AbstractFile> extends DrawableFile<T> {
}
public boolean isDisplayableAsMedia() {
try {
Media media = getMedia();
return Objects.nonNull(media) && Objects.isNull(media.getError());
} catch (IOException ex) {
Logger.getLogger(VideoFile.class.getName()).log(Level.SEVERE, "failed to write video to cache for playback.", ex);
return false;
} catch (MediaException ex) {
return false;
}
}
@Override
Double getWidth() {
try {

View File

@ -34,11 +34,6 @@
<Insets bottom="1.0" left="1.0" right="1.0" top="1.0" fx:id="x1" />
</HBox.margin>
</ImageView>
<ImageView fx:id="undisplayableImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/prohibition.png" />
</image>
</ImageView>
</children>
<padding>
<Insets bottom="2.0" right="2.0" top="2.0" />

View File

@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.util.Objects;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.CacheHint;
import javafx.scene.control.Control;
@ -90,8 +91,8 @@ public class DrawableTile extends DrawableTileBase {
}
@Override
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
return new ThumbnailLoaderTask(file);
Task<Image> newReadImageTask(DrawableFile<?> file) {
return file.getThumbnailTask();
}
@Override
@ -99,4 +100,6 @@ public class DrawableTile extends DrawableTileBase {
return getFile().map(AbstractContent::getName).orElse("");
}
}

View File

@ -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,10 +67,10 @@ 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;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
@ -111,8 +111,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@FXML
private ImageView hashHitImageView;
@FXML
protected ImageView undisplayableImageView;
/**
* displays the icon representing follow up tag
*/
@ -221,15 +220,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,16 +305,16 @@ 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();
final boolean isUndisplayable = (isVideo ? ((VideoFile<?>) file).isDisplayableAsMedia() : file.isDisplayableAsImage()) == false;
final String text = getTextForLabel();
Platform.runLater(() -> {
@ -328,8 +322,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
fileTypeImageView.setVisible(isVideo);
hashHitImageView.setManaged(hasHashSetHits);
hashHitImageView.setVisible(hasHashSetHits);
undisplayableImageView.setManaged(isUndisplayable);
undisplayableImageView.setVisible(isUndisplayable);
nameLabel.setText(text);
nameLabel.setTooltip(new Tooltip(text));
});

View File

@ -18,47 +18,58 @@
*/
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.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 read 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
protected BorderPane imageBorder;
BorderPane imageBorder;
@FXML
protected ImageView imageView;
ImageView imageView;
private final ImageGalleryController controller;
private Optional<DrawableFile<?>> fileOpt = Optional.empty();
private Optional<Long> fileIDOpt = Optional.empty();
private Task<Image> imageTask;
private SoftReference<Image> imageCache;
private ProgressIndicator progressIndicator;
private volatile Task<Image> imageTask;
public DrawableUIBase(ImageGalleryController controller) {
this.controller = controller;
@ -107,132 +118,97 @@ 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);
}
setFileHelper(newFileID);
}
} else if (Objects.nonNull(newFileID)) {
} else {
setFileHelper(newFileID);
}
}
synchronized protected void updateContent() {
Node content = getContentNode();
Platform.runLater(() -> {
imageBorder.setCenter(content);
if (getFile().isPresent()) {
doReadImageTask(getFile().get());
}
}
synchronized Node doReadImageTask(DrawableFile<?> file) {
Task<Image> myTask = newReadImageTask(file);
imageTask = myTask;
Node progressNode = newProgressIndicator(myTask);
Platform.runLater(() -> imageBorder.setCenter(progressNode));
//called on fx thread
myTask.setOnSucceeded(succeeded -> {
showImage(file, myTask);
synchronized (DrawableUIBase.this) {
imageTask = null;
}
});
myTask.setOnFailed(failed -> {
showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
synchronized (DrawableUIBase.this) {
imageTask = null;
}
});
myTask.setOnCancelled(cancelled -> {
disposeContent();
});
exec.execute(myTask);
return progressNode;
}
synchronized protected void disposeContent() {
if (imageTask != null) {
imageTask.cancel(true);
imageTask.cancel();
}
imageTask = null;
imageCache = null;
Platform.runLater(() -> {
imageView.setImage(null);
imageBorder.setCenter(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)
private 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));
abstract class CachedLoaderTask<X, Y extends DrawableFile<?>> extends Task<X> {
VBox vBox = new VBox(10,
new Label(errorMessage), createButton);
protected final Y file;
public CachedLoaderTask(Y file) {
this.file = file;
}
@Override
protected X call() throws Exception {
return (isCancelled() == false) ? load() : null;
}
abstract X load();
@Override
protected void succeeded() {
super.succeeded();
if (isCancelled() == false) {
try {
saveToCache(get());
updateContent();
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.WARNING, "Failed to cache content for" + file.getName(), ex);
}
}
}
@Override
protected void failed() {
super.failed();
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
}
abstract void saveToCache(X result);
vBox.setAlignment(Pos.CENTER);
imageBorder.setCenter(vBox);
}
abstract class ImageLoaderTask 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) {
super(file);
}
@Override
Image load() {
return isCancelled() ? null : file.getThumbnail();
}
}
abstract Task<Image> newReadImageTask(DrawableFile<?> file);
}

View File

@ -27,11 +27,11 @@ 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;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
@ -52,6 +52,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,36 +167,37 @@ 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(() -> {
imageView.setImage(null);
imageBorder.setCenter(null);
tableView.getItems().clear();
getCategoryBorderRegion().setBorder(null);
});
} else {
disposeContent();
updateUI();
disposeContent();
if (nonNull(newFileID)) {
updateAttributesTable();
updateCategory();
updateContent();
}
}
@Override
CachedLoaderTask<Image, DrawableFile<?>> getNewImageLoadTask(DrawableFile<?> file) {
return new ThumbnailLoaderTask(file);
protected synchronized void disposeContent() {
super.disposeContent();
Platform.runLater(() -> {
tableView.getItems().clear();
getCategoryBorderRegion().setBorder(null);
});
}
public void updateUI() {
@Override
Task<Image> newReadImageTask(DrawableFile<?> file) {
return file.getThumbnailTask();
}
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 +206,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 +224,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 +233,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 +245,5 @@ public class MetaDataPane extends DrawableUIBase {
getValueDisplayString(selectedItem)));
}
}
}

View File

@ -55,11 +55,6 @@
<Insets bottom="1.0" left="1.0" right="1.0" top="1.0" />
</HBox.margin>
</ImageView>
<ImageView fx:id="undisplayableImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/prohibition.png" />
</image>
</ImageView>
</children>
<padding>
<Insets bottom="2.0" right="2.0" top="2.0" />

View File

@ -19,10 +19,9 @@
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.io.IOException;
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;
@ -32,6 +31,8 @@ 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 +41,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,6 +52,7 @@ 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;
/**
@ -69,7 +72,8 @@ public class SlideShowView extends DrawableTileBase {
@FXML
private BorderPane footer;
private Task<Node> mediaTask;
private volatile MediaLoadTask mediaTask;
SlideShowView(GroupPane gp, ImageGalleryController controller) {
super(gp, controller);
@ -155,46 +159,94 @@ public class SlideShowView extends DrawableTileBase {
}
@Override
protected void disposeContent() {
synchronized protected void disposeContent() {
stopVideo();
super.disposeContent();
if (mediaTask != null) {
mediaTask.cancel(true);
}
mediaTask = null;
mediaCache = null;
super.disposeContent();
}
private SoftReference<Node> mediaCache;
/**
* {@inheritDoc }
*/
@Override
Node getContentNode() {
if (getFile().isPresent() == false) {
mediaCache = null;
return super.getContentNode();
} else {
synchronized protected void updateContent() {
disposeContent();
if (getFile().isPresent()) {
DrawableFile<?> file = getFile().get();
if (file.isVideo()) {
Node mediaNode = (isNull(mediaCache)) ? null : mediaCache.get();
if (nonNull(mediaNode)) {
return mediaNode;
} else {
if (isNull(mediaTask)) {
mediaTask = new MediaLoadTask(((VideoFile<?>) file));
new Thread(mediaTask).start();
} else if (mediaTask.isDone()) {
return null;
}
return getLoadingProgressIndicator();
}
doMediaLoadTask((VideoFile<?>) file);
} else {
doReadImageTask(file);
}
return super.getContentNode();
}
}
synchronized private Node doMediaLoadTask(VideoFile<?> file) {
//specially handling for videos
MediaLoadTask myTask = new MediaLoadTask(file);
mediaTask = myTask;
Node progressNode = newProgressIndicator(myTask);
Platform.runLater(() -> imageBorder.setCenter(progressNode));
//called on fx thread
mediaTask.setOnSucceeded(succeedded -> {
showMedia(file, myTask);
synchronized (SlideShowView.this) {
mediaTask = null;
}
});
mediaTask.setOnFailed(failed -> {
showErrorNode(getMediaLoadErrorLabel(myTask), file);
synchronized (SlideShowView.this) {
mediaTask = null;
}
});
mediaTask.setOnCancelled(cancelled -> {
disposeContent();
});
exec.execute(myTask);
return progressNode;
}
@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,30 @@ 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 video: {0}"})
private class MediaLoadTask extends Task<Node> {
public MediaLoadTask(VideoFile<?> file) {
super(file);
private final VideoFile<?> file;
MediaLoadTask(VideoFile<?> file) {
updateMessage(Bundle.MediaLoadTask_messageText(file.getName()));
this.file = file;
}
@Override
void saveToCache(Node result) {
synchronized (SlideShowView.this) {
mediaCache = new SoftReference<>(result);
}
}
@Override
Node load() {
protected Node call() throws Exception {
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);
if (file.isDisplayableAsImage()) {
Image fullSizeImage = file.getFullSizeImage();
Platform.runLater(() -> {
imageView.setImage(fullSizeImage);
});
return imageView;
}
return new Text(ex.getLocalizedMessage() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action.");
LOGGER.log(Level.WARNING, "Failed to initialize VideoPlayer for {0} : " + ex.toString(), file.getContentPathSafe());
return doReadImageTask(file);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

View File

@ -0,0 +1,40 @@
/*
* 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.utils;
import java.util.concurrent.Callable;
import javafx.concurrent.Task;
/**
*
*/
public class TaskUtils {
public static <T> Task<T> taskFrom(Callable<T> callable) {
return new Task<T>() {
@Override
protected T call() throws Exception {
return callable.call();
}
};
}
private TaskUtils() {
}
}

View File

@ -1,5 +1,5 @@
#Updated by build script
#Tue, 05 Jan 2016 16:31:20 -0500
#Mon, 21 Dec 2015 06:26:49 -0500
LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314
SPLASH_WIDTH=538

View File

@ -1,4 +1,4 @@
#Updated by build script
#Tue, 05 Jan 2016 16:31:20 -0500
#Mon, 21 Dec 2015 06:26:49 -0500
CTL_MainWindow_Title=Autopsy 4.0.0
CTL_MainWindow_Title_No_Project=Autopsy 4.0.0

View File

@ -194,7 +194,7 @@ The first question that you must answer is what type of data do you want the use
\subsection ingest_modules_making_results_bb Posting Results to the Blackboard
The blackboard is used to store results so that they are displayed in the results tree.
See \ref platform_blackboard for details on posting results to it. You use the blackboard when you have specific items to show the user. if you want to just shown them a big report from another library or tool, see \ref ingest_modules_makeing_results_report.
See \ref platform_blackboard for details on posting results to it. You use the blackboard when you have specific items to show the user. if you want to just shown them a big report from another library or tool, see \ref mod_report_page.
The blackboard defines artifacts for specific data types (such as web bookmarks).
You can use one of the standard artifact types or create your own.