mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 18:17:43 +00:00
Fixed the date in the bundle properties
This commit is contained in:
commit
aff4d7864c
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
@ -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) {
|
||||
|
@ -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> {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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" />
|
||||
|
@ -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("");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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 |
@ -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() {
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user