From cc742bbcd6acf27a34928f6ae48e23ce2dbde93f Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Mon, 28 Dec 2015 17:12:07 -0500 Subject: [PATCH 01/24] Check if permissions are sufficient to read and write output folder --- .../sleuthkit/autopsy/coreutils/FileUtil.java | 39 +++++++++++++++++++ .../modules/photoreccarver/Bundle.properties | 4 +- .../PhotoRecCarverFileIngestModule.java | 7 ++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java index a6f11ec684..159bb6efce 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java @@ -22,6 +22,11 @@ import java.io.File; import java.io.IOException; import java.util.logging.Level; import org.openide.filesystems.FileObject; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Path; /** * File and dir utilities @@ -29,6 +34,8 @@ import org.openide.filesystems.FileObject; public class FileUtil { private static final Logger logger = Logger.getLogger(FileUtil.class.getName()); + private static String TEST_STRING = "Testing"; + private static String TEMP_FILE_PREFIX = "Autopsy"; /** * Recursively delete all of the files and sub-directories in a directory. @@ -167,4 +174,36 @@ 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 arePermissionsAppropriate(Path path) { + Path p = null; + try { + p = Files.createTempFile(path, TEMP_FILE_PREFIX, null); + try (FileWriter fw = new FileWriter(p.toFile(), false)) { + fw.write(TEST_STRING); + } + + String result; + try (BufferedReader br = new BufferedReader(new FileReader(p.toFile()))) { + result = br.readLine(); + } + return result.compareTo(TEST_STRING) == 0; + } catch (Exception ex) { + return false; + } finally { + if (p != null) { + try { + p.toFile().delete(); + } catch (Exception ignored) { + } + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties index ed1afb9be9..0d18ff4e2f 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties @@ -23,4 +23,6 @@ PhotoRecIngestModule.NotEnoughDiskSpace.detail.msg=PhotoRec error processing {0} 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\: \ No newline at end of file +PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving\: +PhotoRecIngestModule.PermissionsNotSufficient=Insufficient permissions accessing +PhotoRecIngestModule.PermissionsNotSufficientSeeReference=See "Shared Drive Authentication" in Autopsy help. \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index b923c4294a..16e3659777 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -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 totalsForIngestJobs = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); @@ -383,6 +384,12 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { if (path == null) { throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.nonHostnameUNCPathUsed")); } + if (false == FileUtil.arePermissionsAppropriate(path)) { + throw new IngestModule.IngestModuleException( + NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficient") + + SEP + path.toString()+ SEP + + NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficientSeeReference")); + } } try { Files.createDirectory(path); From 991e52e180660fc8641ef8bb5706da38dfa8bdef Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Tue, 29 Dec 2015 12:16:33 -0500 Subject: [PATCH 02/24] Use built-in File checks, create before checking --- .../sleuthkit/autopsy/coreutils/FileUtil.java | 13 ++-------- .../PhotoRecCarverFileIngestModule.java | 26 +++++++++---------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java index 159bb6efce..2725a22135 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java @@ -34,7 +34,6 @@ import java.nio.file.Path; public class FileUtil { private static final Logger logger = Logger.getLogger(FileUtil.class.getName()); - private static String TEST_STRING = "Testing"; private static String TEMP_FILE_PREFIX = "Autopsy"; /** @@ -186,16 +185,8 @@ public class FileUtil { Path p = null; try { p = Files.createTempFile(path, TEMP_FILE_PREFIX, null); - try (FileWriter fw = new FileWriter(p.toFile(), false)) { - fw.write(TEST_STRING); - } - - String result; - try (BufferedReader br = new BufferedReader(new FileReader(p.toFile()))) { - result = br.readLine(); - } - return result.compareTo(TEST_STRING) == 0; - } catch (Exception ex) { + return (p.toFile().canRead() && p.toFile().canWrite()); + } catch (IOException ex) { return false; } finally { if (p != null) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index 16e3659777..c93182bba9 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -378,21 +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")); - } - if (false == FileUtil.arePermissionsAppropriate(path)) { - throw new IngestModule.IngestModuleException( - NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficient") - + SEP + path.toString()+ SEP + - NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficientSeeReference")); - } - } 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.arePermissionsAppropriate(path)) { + throw new IngestModule.IngestModuleException( + NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficient") + + SEP + path.toString() + SEP + + NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficientSeeReference")); + } + } } catch (FileAlreadyExistsException ex) { // No worries. } catch (IOException | SecurityException | UnsupportedOperationException ex) { From b93360e80e5b58da2836e7da9fce2ec1000eaa5f Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 11 Dec 2015 17:55:02 -0500 Subject: [PATCH 03/24] decouple ReadImageTask and MediaViewImagePanel --- .../corecomponents/MediaViewImagePanel.java | 118 +++++++++++------- 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index c34f465589..9a8b374302 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -28,14 +28,17 @@ 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; @@ -110,7 +113,7 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi .map("."::concat) //NOI18N .collect(Collectors.toList()); - private LoadImageTask readImageTask; + private ReadImageTask readImageTask; /** * Creates new form MediaViewImagePanel @@ -160,6 +163,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 +186,58 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi if (readImageTask != null) { readImageTask.cancel(); } - readImageTask = new LoadImageTask(file); + readImageTask = new ReadImageTask(file); + readImageTask.setOnSucceeded(new EventHandler() { + + @Override + public void handle(WorkerStateEvent event) { + //Note that all error conditions are allready logged in readImageTask.succeeded() + if (!Case.isCaseOpen()) { + /* + * handle in-between condition when case is being closed + * and an image was previously selected + */ + reset(); + return; + } + + try { + Image fxImage = readImageTask.get(); + if (nonNull(fxImage)) { + //we have non-null image show it + fxImageView.setImage(fxImage); + borderpane.setCenter(fxImageView); + } else { + showErrorNode(file); + } + } catch (InterruptedException | ExecutionException ex) { + showErrorNode(file); + } + borderpane.setCursor(Cursor.DEFAULT); + } + + }); + readImageTask.setOnFailed(new EventHandler() { + + @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; + } + + 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); + borderpane.setCursor(Cursor.DEFAULT); + } + }); maskerPane.setProgressNode(progressBar); progressBar.progressProperty().bind(readImageTask.progressProperty()); @@ -233,12 +295,12 @@ 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 implements IIOReadProgressListener { + static private class ReadImageTask extends Task implements IIOReadProgressListener { private final AbstractFile file; volatile private BufferedImage bufferedImage = null; - LoadImageTask(AbstractFile file) { + ReadImageTask(AbstractFile file) { this.file = file; } @@ -292,72 +354,38 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi } } - private void logError(@Nullable Throwable e) { + public 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 + LOGGER.log(Level.WARNING, "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 + LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getName(), message}); //NOI18N + LOGGER.log(Level.SEVERE, "Failed to get unique path for file", tskCoreException); //NOI18N } } @Override protected void failed() { super.failed(); - 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); + logError(getException()); } @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); + logError(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()); + logError(ex.getCause()); } - borderpane.setCursor(Cursor.DEFAULT); } @Override From 8dc7208876fb4a02ba11f1f5b1c2df29d5cdcda0 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 11 Dec 2015 18:21:17 -0500 Subject: [PATCH 04/24] move ReadImageTask to ImageUtils, refactoring in IG to use readImageTask --- .../corecomponents/MediaViewImagePanel.java | 206 ++--------------- .../autopsy/coreutils/ImageUtils.java | 217 ++++++++++++++++-- .../ui/detailview/DetailViewPane.java | 1 + .../autopsy/imagegallery/ThumbnailCache.java | 36 ++- .../actions/OpenExternalViewerAction.java | 56 +++++ .../imagegallery/datamodel/DrawableFile.java | 50 +++- .../imagegallery/datamodel/ImageFile.java | 68 +++--- .../imagegallery/datamodel/VideoFile.java | 46 +++- .../gui/drawableviews/DrawableTile.java | 4 +- .../gui/drawableviews/DrawableTileBase.java | 20 +- .../gui/drawableviews/DrawableUIBase.java | 155 +++++++------ .../gui/drawableviews/MetaDataPane.java | 24 +- .../gui/drawableviews/SlideShowView.java | 139 +++++++---- .../autopsy/imagegallery/images/external.png | Bin 0 -> 685 bytes 14 files changed, 629 insertions(+), 393 deletions(-) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/external.png diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index 9a8b374302..aeff32aec9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -21,23 +21,16 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.event.ActionEvent; -import java.awt.image.BufferedImage; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.Collections; -import java.util.Iterator; import java.util.List; import static java.util.Objects.nonNull; import java.util.SortedSet; import java.util.concurrent.ExecutionException; -import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.concurrent.WorkerStateEvent; import javafx.embed.swing.JFXPanel; -import javafx.embed.swing.SwingFXUtils; import javafx.event.EventHandler; import javafx.geometry.Pos; import javafx.scene.Cursor; @@ -49,13 +42,7 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; -import javax.annotation.Nullable; -import javax.imageio.IIOException; import javax.imageio.ImageIO; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageReader; -import javax.imageio.event.IIOReadProgressListener; -import javax.imageio.stream.ImageInputStream; import javax.swing.JPanel; import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; @@ -66,8 +53,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.ReadContentInputStream; -import org.sleuthkit.datamodel.TskCoreException; /** * Image viewer part of the Media View layered pane. Uses JavaFX to display the @@ -113,7 +98,7 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi .map("."::concat) //NOI18N .collect(Collectors.toList()); - private ReadImageTask readImageTask; + private Task readImageTask; /** * Creates new form MediaViewImagePanel @@ -186,36 +171,31 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi if (readImageTask != null) { readImageTask.cancel(); } - readImageTask = new ReadImageTask(file); - readImageTask.setOnSucceeded(new EventHandler() { - - @Override - public void handle(WorkerStateEvent event) { - //Note that all error conditions are allready logged in readImageTask.succeeded() - if (!Case.isCaseOpen()) { - /* - * handle in-between condition when case is being closed - * and an image was previously selected - */ - reset(); - return; - } - - try { - Image fxImage = readImageTask.get(); - if (nonNull(fxImage)) { - //we have non-null image show it - fxImageView.setImage(fxImage); - borderpane.setCenter(fxImageView); - } else { - showErrorNode(file); - } - } catch (InterruptedException | ExecutionException ex) { - showErrorNode(file); - } - borderpane.setCursor(Cursor.DEFAULT); + readImageTask = ImageUtils.newReadImageTask(file); + readImageTask.setOnSucceeded((WorkerStateEvent event) -> { + //Note that all error conditions are allready logged in readImageTask.succeeded() + if (!Case.isCaseOpen()) { + /* + * handle in-between condition when case is being closed and + * an image was previously selected + */ + reset(); + return; } + try { + Image fxImage = readImageTask.get(); + if (nonNull(fxImage)) { + //we have non-null image show it + fxImageView.setImage(fxImage); + borderpane.setCenter(fxImageView); + } else { + showErrorNode(file); + } + } catch (InterruptedException | ExecutionException ex) { + showErrorNode(file); + } + borderpane.setCursor(Cursor.DEFAULT); }); readImageTask.setOnFailed(new EventHandler() { @@ -230,11 +210,7 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi return; } - externalViewerButton.setOnAction(actionEvent -> //fx ActionEvent - new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file)) - .actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")) //Swing ActionEvent //NOI18N - ); - borderpane.setCenter(errorNode); + showErrorNode(file); borderpane.setCursor(Cursor.DEFAULT); } }); @@ -295,136 +271,4 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables - static private class ReadImageTask extends Task implements IIOReadProgressListener { - - private final AbstractFile file; - volatile private BufferedImage bufferedImage = null; - - ReadImageTask(AbstractFile file) { - this.file = file; - } - - @Override - @NbBundle.Messages({ - "# {0} - file name", - "LoadImageTask.mesageText=Reading image: {0}"}) - protected Image call() throws Exception { - updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); - try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { - - if (ImageUtils.isGIF(file)) { - //directly read GIF to preserve potential animation, - Image image = new Image(new BufferedInputStream(inputStream)); - if (image.isError() == false) { - return image; - } - //fall through to default iamge reading code if there was an error - } - - ImageInputStream input = ImageIO.createImageInputStream(inputStream); - if (input == null) { - throw new IIOException("Could not create ImageInputStream."); //NOI18N - } - Iterator readers = ImageIO.getImageReaders(input); - - if (readers.hasNext()) { - ImageReader reader = readers.next(); - reader.addIIOReadProgressListener(this); - reader.setInput(input); - /* - * This is the important part, get or create a ReadParam, - * create a destination image to hold the decoded result, - * then pass that image with the param. - */ - ImageReadParam param = reader.getDefaultReadParam(); - - bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); - param.setDestination(bufferedImage); - try { - reader.read(0, param); - } catch (IOException iOException) { - // Ignore this exception or display a warning or similar, for exceptions happening during decoding - logError(iOException); - } - reader.removeIIOReadProgressListener(this); - return SwingFXUtils.toFXImage(bufferedImage, null); - } else { - throw new IIOException("No ImageReader found for file."); //NOI18N - } - } - } - - public void logError(@Nullable Throwable e) { - String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N - try { - LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getName(), message}); //NOI18N - LOGGER.log(Level.SEVERE, "Failed to get unique path for file", tskCoreException); //NOI18N - } - } - - @Override - protected void failed() { - super.failed(); - logError(getException()); - } - - @Override - protected void succeeded() { - super.succeeded(); - try { - Image fxImage = get(); - if (fxImage == null) { - logError(null); - } else { - if (fxImage.isError()) { - //if there was somekind of error, log it - logError(fxImage.getException()); - } - } - } catch (InterruptedException | ExecutionException ex) { - logError(ex.getCause()); - } - } - - @Override - public void imageProgress(ImageReader source, float percentageDone) { - //update this task with the progress reported by ImageReader.read - updateProgress(percentageDone, 100); - } - - @Override - public void sequenceStarted(ImageReader source, int minIndex) { - } - - @Override - public void sequenceComplete(ImageReader source) { - } - - @Override - public void imageStarted(ImageReader source, int imageIndex) { - } - - @Override - public void imageComplete(ImageReader source) { - } - - @Override - public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) { - } - - @Override - public void thumbnailProgress(ImageReader source, float percentageDone) { - } - - @Override - public void thumbnailComplete(ImageReader source) { - } - - @Override - public void readAborted(ImageReader source) { - } - } - } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index bec4a9acbd..c7f98cc061 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -27,28 +27,37 @@ import com.google.common.io.Files; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; -import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Objects; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Level; +import javafx.concurrent.Task; +import javafx.embed.swing.SwingFXUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.imageio.IIOException; import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.stream.ImageInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.opencv.core.Core; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; @@ -141,8 +150,8 @@ public class ImageUtils { /** * thread that saves generated thumbnails to disk in the background */ - private static final Executor imageSaver = - Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() + private static final Executor imageSaver + = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() .namingPattern("icon saver-%d").build()); public static List getSupportedImageExtensions() { @@ -319,7 +328,7 @@ public class ImageUtils { * @return a thumbnail for the given image or a default one if there was a * problem making a thumbnail. */ - public static Image getThumbnail(Content content, int iconSize) { + public static BufferedImage getThumbnail(Content content, int iconSize) { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; // If a thumbnail file is already saved locally @@ -333,7 +342,7 @@ public class ImageUtils { return thumbnail; } } catch (Exception ex) { - LOGGER.log(Level.WARNING, "Error while reading image: " + content.getName(), ex); //NON-NLS + LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{content.getName(), ex.getLocalizedMessage()}); //NON-NLS return generateAndSaveThumbnail(file, iconSize, cacheFile); } } else { @@ -490,7 +499,7 @@ public class ImageUtils { * * @return Generated icon or null on error */ - private static Image generateAndSaveThumbnail(AbstractFile file, int iconSize, File cacheFile) { + private static BufferedImage generateAndSaveThumbnail(AbstractFile file, int iconSize, File cacheFile) { BufferedImage thumbnail = null; try { if (VideoUtils.isVideoThumbnailSupported(file)) { @@ -537,13 +546,15 @@ public class ImageUtils { * there was a problem. */ @Nullable - private static BufferedImage generateImageThumbnail(Content content, int iconSize) { + private static BufferedImage generateImageThumbnail(AbstractFile content, int iconSize) { - try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(content));) { - BufferedImage bi = ImageIO.read(inputStream); + try { + final ReadImageTask readImageTask = new ReadImageTask(content); + + readImageTask.run(); + BufferedImage bi = SwingFXUtils.fromFXImage(readImageTask.get(), null); if (bi == null) { - LOGGER.log(Level.WARNING, "No image reader for file: {0}", content.getName()); //NON-NLS return null; } try { @@ -554,13 +565,191 @@ public class ImageUtils { return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight())); } } catch (OutOfMemoryError e) { - LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS - } catch (EOFException e) { - LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS + LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName() + ": " + e.getLocalizedMessage()); //NON-NLS } catch (Exception e) { - LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS + LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.getLocalizedMessage()); //NON-NLS } return null; } + static public int getWidth(AbstractFile file) throws IIOException, IOException { + + try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { + + try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { + if (input == null) { + throw new IIOException("Could not create ImageInputStream."); //NOI18N + } + Iterator readers = ImageIO.getImageReaders(input); + + if (readers.hasNext()) { + ImageReader reader = readers.next(); + reader.setInput(input); + return reader.getWidth(0); + } else { + throw new IIOException("No ImageReader found for file." + file.getName()); //NOI18N + } + } + } + } + + static public int getHeight(AbstractFile file) throws IIOException, IOException { + try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { + + try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { + if (input == null) { + throw new IIOException("Could not create ImageInputStream."); //NOI18N + } + Iterator readers = ImageIO.getImageReaders(input); + + if (readers.hasNext()) { + ImageReader reader = readers.next(); + reader.setInput(input); + + return reader.getHeight(0); + } else { + throw new IIOException("No ImageReader found for file." + file.getName()); //NOI18N + } + } + + } + } + + public static Task newReadImageTask(AbstractFile file) { + return new ReadImageTask(file); + } + + static private class ReadImageTask extends Task implements IIOReadProgressListener { + + private final AbstractFile file; + volatile private BufferedImage bufferedImage = null; + + ReadImageTask(AbstractFile file) { + this.file = file; + } + + @Override + @NbBundle.Messages({ + "# {0} - file name", + "LoadImageTask.mesageText=Reading image: {0}"}) + protected javafx.scene.image.Image call() throws Exception { + updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); + try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { + + if (ImageUtils.isGIF(file)) { + //directly read GIF to preserve potential animation, + javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(inputStream)); + if (image.isError() == false) { + return image; + } + //fall through to default iamge reading code if there was an error + } + + try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { + if (input == null) { + throw new IIOException("Could not create ImageInputStream."); //NOI18N + } + Iterator readers = ImageIO.getImageReaders(input); + + if (readers.hasNext()) { + ImageReader reader = readers.next(); + reader.addIIOReadProgressListener(this); + reader.setInput(input); + /* + * This is the important part, get or create a + * ReadParam, create a destination image to hold the + * decoded result, then pass that image with the param. + */ + ImageReadParam param = reader.getDefaultReadParam(); + + bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); + param.setDestination(bufferedImage); + try { + reader.read(0, param); + } catch (IOException iOException) { + // Ignore this exception or display a warning or similar, for exceptions happening during decoding + logError(iOException); + } + reader.removeIIOReadProgressListener(this); + return SwingFXUtils.toFXImage(bufferedImage, null); + } else { + throw new IIOException("No ImageReader found for file."); //NOI18N + } + } + } + } + + public void logError(@Nullable Throwable e) { + String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N + try { + LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N + } catch (TskCoreException tskCoreException) { + LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getName(), message}); //NOI18N + LOGGER.log(Level.SEVERE, "Failed to get unique path for file", tskCoreException); //NOI18N + } + } + + @Override + protected void failed() { + super.failed(); + logError(getException()); + } + + @Override + protected void succeeded() { + super.succeeded(); + try { + javafx.scene.image.Image fxImage = get(); + if (fxImage == null) { + logError(null); + } else { + if (fxImage.isError()) { + //if there was somekind of error, log it + logError(fxImage.getException()); + } + } + } catch (InterruptedException | ExecutionException ex) { + logError(ex.getCause()); + } + } + + @Override + public void imageProgress(ImageReader source, float percentageDone) { + //update this task with the progress reported by ImageReader.read + updateProgress(percentageDone, 100); + } + + @Override + public void imageStarted(ImageReader source, int imageIndex) { + } + + @Override + public void imageComplete(ImageReader source) { + updateProgress(100, 100); + } + + @Override + public void sequenceStarted(ImageReader source, int minIndex) { + } + + @Override + public void sequenceComplete(ImageReader source) { + } + + @Override + public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) { + } + + @Override + public void thumbnailProgress(ImageReader source, float percentageDone) { + } + + @Override + public void thumbnailComplete(ImageReader source) { + } + + @Override + public void readAborted(ImageReader source) { + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index efad107ba2..f3b19a3df8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -391,6 +391,7 @@ public class DetailViewPane extends AbstractVisualizationPane { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index 26275b48fa..19551d6f7f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -30,7 +30,6 @@ import java.net.MalformedURLException; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import java.util.logging.Level; import javafx.beans.property.SimpleIntegerProperty; import javafx.embed.swing.SwingFXUtils; @@ -99,7 +98,7 @@ public enum ThumbnailCache { try { return cache.get(file.getId(), () -> load(file)).orElse(null); } catch (UncheckedExecutionException | CacheLoader.InvalidCacheLoadException | ExecutionException ex) { - LOGGER.log(Level.WARNING, "failed to load icon for file: " + file.getName(), ex.getCause()); + LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + file.getName(), ex.getCause()); return null; } } @@ -109,7 +108,7 @@ public enum ThumbnailCache { try { return get(ImageGalleryController.getDefault().getFileFromId(fileID)); } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "failed to load icon for file id : " + fileID, ex.getCause()); + LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause()); return null; } } @@ -130,27 +129,24 @@ public enum ThumbnailCache { return Optional.of(new Image(new BufferedInputStream(new ReadContentInputStream(file.getAbstractFile())), MAX_THUMBNAIL_SIZE, MAX_THUMBNAIL_SIZE, true, true)); } - BufferedImage thumbnail = getCacheFile(file).map(new Function() { - @Override - public BufferedImage apply(File cachFile) { - if (cachFile.exists()) { - // If a thumbnail file is already saved locally, load it - try { - BufferedImage cachedThumbnail = ImageIO.read(cachFile); + BufferedImage thumbnail = getCacheFile(file).map((File cachFile) -> { + if (cachFile.exists()) { + // If a thumbnail file is already saved locally, load it + try { + BufferedImage cachedThumbnail = ImageIO.read(cachFile); - if (cachedThumbnail.getWidth() < MAX_THUMBNAIL_SIZE) { - return cachedThumbnail; - } - } catch (MalformedURLException ex) { - LOGGER.log(Level.WARNING, "Unable to parse cache file path: " + cachFile.getPath(), ex); - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Unable to read cache file " + cachFile.getPath(), ex); + if (cachedThumbnail.getWidth() < MAX_THUMBNAIL_SIZE) { + return cachedThumbnail; } + } catch (MalformedURLException ex) { + LOGGER.log(Level.WARNING, "Unable to parse cache file path: " + cachFile.getPath(), ex); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Unable to read cache file " + cachFile.getPath(), ex); } - return null; } + return null; }).orElseGet(() -> { - return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE); + return ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE); }); WritableImage jfxthumbnail; @@ -176,7 +172,7 @@ public enum ThumbnailCache { try { return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_THUMBNAIL_SIZE)); - } catch (IllegalStateException e) { + } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to create cache file.{0}", e.getLocalizedMessage()); return Optional.empty(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java new file mode 100644 index 0000000000..c132a8789f --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java @@ -0,0 +1,56 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.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)); + } +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 4726341eed..f0f3268678 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -25,10 +25,12 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.concurrent.Task; import javafx.scene.image.Image; import javafx.util.Pair; import javax.annotation.Nonnull; @@ -91,12 +93,36 @@ public abstract class DrawableFile extends AbstractFile private String model; protected DrawableFile(T file, Boolean analyzed) { - /* @TODO: the two 'new Integer(0).shortValue()' values and null are + /* + * @TODO: the two 'new Integer(0).shortValue()' values and null are * placeholders because the super constructor expects values i can't get * easily at the moment. I assume this is related to why - * ReadContentInputStream can't read from DrawableFiles. */ + * ReadContentInputStream can't read from DrawableFiles. + */ - super(file.getSleuthkitCase(), file.getId(), file.getAttrType(), file.getAttrId(), file.getName(), file.getType(), file.getMetaAddr(), (int) file.getMetaSeq(), file.getDirType(), file.getMetaType(), null, new Integer(0).shortValue(), file.getSize(), file.getCtime(), file.getCrtime(), file.getAtime(), file.getMtime(), new Integer(0).shortValue(), file.getUid(), file.getGid(), file.getMd5Hash(), file.getKnown(), file.getParentPath()); + super(file.getSleuthkitCase(), + file.getId(), + file.getAttrType(), + file.getAttrId(), + file.getName(), + file.getType(), + file.getMetaAddr(), + (int) file.getMetaSeq(), + file.getDirType(), + file.getMetaType(), + null, + new Integer(0).shortValue(), + file.getSize(), + file.getCtime(), + file.getCrtime(), + file.getAtime(), + file.getMtime(), + new Integer(0).shortValue(), + file.getUid(), + file.getGid(), + file.getMd5Hash(), + file.getKnown(), + file.getParentPath()); this.analyzed = new SimpleBooleanProperty(analyzed); this.file = file; } @@ -213,7 +239,9 @@ public abstract class DrawableFile extends AbstractFile return category; } - /** set the category property to the most severe one found */ + /** + * set the category property to the most severe one found + */ private void updateCategory() { try { category.set(getSleuthkitCase().getContentTagsByContent(this).stream() @@ -224,7 +252,7 @@ public abstract class DrawableFile extends AbstractFile .orElse(Category.ZERO) ); } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "problem looking up category for file " + this.getName(), ex); + LOGGER.log(Level.WARNING, "problem looking up category for file " + this.getName() + ex.getLocalizedMessage()); } catch (IllegalStateException ex) { // We get here many times if the case is closed during ingest, so don't print out a ton of warnings. } @@ -234,7 +262,17 @@ public abstract class DrawableFile extends AbstractFile return ThumbnailCache.getDefault().get(this); } - public abstract Image getFullSizeImage(); + @Deprecated + + public Image getFullSizeImage() { + try { + return getReadFullSizeImageTask().get(); + } catch (InterruptedException | ExecutionException ex) { + return null; + } + } + + public abstract Task getReadFullSizeImageTask(); public void setAnalyzed(Boolean analyzed) { this.analyzed.set(analyzed); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index c235870448..103561ce32 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -18,17 +18,17 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel; -import java.awt.image.BufferedImage; -import java.io.BufferedInputStream; +import java.io.IOException; import java.lang.ref.SoftReference; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javafx.embed.swing.SwingFXUtils; +import javafx.beans.Observable; +import javafx.concurrent.Task; import javafx.scene.image.Image; import javax.imageio.ImageIO; +import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.ReadContentInputStream; /** * ImageGallery data model object that represents an image file. It is a @@ -49,43 +49,53 @@ public class ImageFile extends DrawableFile { } @Override - public Image getFullSizeImage() { + public Task getReadFullSizeImageTask() { Image image = (imageRef != null) ? imageRef.get() : null; if (image == null || image.isError()) { - if (FileTypeUtils.isGIF(getAbstractFile())) { - //directly read gif to preserve potential animation, - image = new Image(new BufferedInputStream(new ReadContentInputStream(getAbstractFile()))); - } + final Task newReadImageTask = ImageUtils.newReadImageTask(this.getAbstractFile()); + newReadImageTask.stateProperty().addListener((Observable observable) -> { + switch (newReadImageTask.getState()) { + case CANCELLED: + break; + case FAILED: + break; + case SUCCEEDED: + try { + imageRef = new SoftReference<>(newReadImageTask.get()); + } catch (InterruptedException | ExecutionException interruptedException) { + } + break; + } + }); + return newReadImageTask; + } else { + return new Task() { + @Override + protected Image call() throws Exception { + return image; + } + }; } - if (image == null || image.isError()) { - try (BufferedInputStream readContentInputStream = new BufferedInputStream(new ReadContentInputStream(this.getAbstractFile()))) { - BufferedImage read = ImageIO.read(readContentInputStream); - image = SwingFXUtils.toFXImage(read, null); - } catch (Exception ex) { - LOGGER.log(Level.WARNING, "unable to read file " + getName(), ex.getMessage()); - return null; - } - } - imageRef = new SoftReference<>(image); - return image; } @Override Double getWidth() { - final Image fullSizeImage = getFullSizeImage(); - if (fullSizeImage != null) { - return fullSizeImage.getWidth(); + try { + return (double) ImageUtils.getWidth(this.getAbstractFile()); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "getWidth", ex); + return -1.0; } - return -1.0; } @Override Double getHeight() { - final Image fullSizeImage = getFullSizeImage(); - if (fullSizeImage != null) { - return fullSizeImage.getHeight(); + try { + return (double) ImageUtils.getHeight(this.getAbstractFile()); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "getHeight", ex); + return -1.0; } - return -1.0; } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index 19fa08a8c7..fde2a890f1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -25,7 +25,10 @@ import java.io.IOException; import java.lang.ref.SoftReference; import java.nio.file.Paths; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javafx.beans.Observable; +import javafx.concurrent.Task; import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; import javafx.scene.media.Media; @@ -51,16 +54,43 @@ public class VideoFile extends DrawableFile { } @Override - public Image getFullSizeImage() { - Image image = (null == imageRef) ? null : imageRef.get(); + public Task getReadFullSizeImageTask() { + Image image = (imageRef != null) ? imageRef.get() : null; + if (image == null || image.isError()) { + Task newReadImageTask = new Task() { - if (image == null) { - final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024); - image = (bufferedImage == ImageUtils.getDefaultThumbnail()) ? null : SwingFXUtils.toFXImage(bufferedImage, null); - imageRef = new SoftReference<>(image); + @Override + protected Image call() throws Exception { + final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024); + return (bufferedImage == ImageUtils.getDefaultThumbnail()) + ? null + : SwingFXUtils.toFXImage(bufferedImage, null); + } + }; + + newReadImageTask.stateProperty().addListener((Observable observable) -> { + switch (newReadImageTask.getState()) { + case CANCELLED: + break; + case FAILED: + break; + case SUCCEEDED: + try { + imageRef = new SoftReference<>(newReadImageTask.get()); + } catch (InterruptedException | ExecutionException interruptedException) { + } + break; + } + }); + return newReadImageTask; + } else { + return new Task() { + @Override + protected Image call() throws Exception { + return image; + } + }; } - - return image; } private SoftReference mediaRef; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java index 6088540262..6018c323c6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java @@ -90,7 +90,7 @@ public class DrawableTile extends DrawableTileBase { } @Override - CachedLoaderTask> getNewImageLoadTask(DrawableFile file) { + CachedLoaderTask> newReadImageTask(DrawableFile file) { return new ThumbnailLoaderTask(file); } @@ -99,4 +99,6 @@ public class DrawableTile extends DrawableTileBase { return getFile().map(AbstractContent::getName).orElse(""); } + + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index 17687b54f7..aa16c22b5e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -47,6 +47,7 @@ import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javax.swing.Action; import javax.swing.SwingUtilities; +import org.controlsfx.control.action.ActionUtils; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.actions.Presenter; @@ -58,7 +59,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; -import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; @@ -67,6 +67,7 @@ import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction; +import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction; import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; @@ -221,15 +222,10 @@ public abstract class DrawableTileBase extends DrawableUIBase { }); menuItems.add(contentViewer); - MenuItem externalViewer = new MenuItem(Bundle.DrawableTileBase_externalViewerAction_text()); - final ExternalViewerAction externalViewerAction = new ExternalViewerAction(Bundle.DrawableTileBase_externalViewerAction_text(), new FileNode(file.getAbstractFile())); - - externalViewer.setDisable(externalViewerAction.isEnabled() == false); - externalViewer.setOnAction((ActionEvent t) -> { - SwingUtilities.invokeLater(() -> { - externalViewerAction.actionPerformed(null); - }); - }); + OpenExternalViewerAction openExternalViewerAction = new OpenExternalViewerAction(file.getAbstractFile()); + MenuItem externalViewer = ActionUtils.createMenuItem(openExternalViewerAction); + externalViewer.textProperty().unbind(); + externalViewer.textProperty().bind(openExternalViewerAction.longTextProperty()); menuItems.add(externalViewer); Collection menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); @@ -311,12 +307,12 @@ public abstract class DrawableTileBase extends DrawableUIBase { updateSelectionState(); updateCategory(); updateFollowUpIcon(); - updateUI(); updateContent(); + updateMetaData(); } } - private void updateUI() { + private void updateMetaData() { getFile().ifPresent(file -> { final boolean isVideo = file.isVideo(); final boolean hasHashSetHits = hasHashHit(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 6983f49180..8cc42c8306 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -18,32 +18,46 @@ */ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; -import java.lang.ref.SoftReference; import java.util.Objects; -import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.logging.Level; import javafx.application.Platform; import javafx.concurrent.Task; +import javafx.concurrent.WorkerStateEvent; import javafx.fxml.FXML; +import javafx.geometry.Pos; import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import org.controlsfx.control.action.ActionUtils; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; +import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; +import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; /** * */ +@NbBundle.Messages({"MediaViewImagePanel.errorLabel.text=Could not load file."}) abstract public class DrawableUIBase extends AnchorPane implements DrawableView { + static final Executor exec = Executors.newWorkStealingPool(); + private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); @FXML @@ -56,9 +70,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView private Optional> fileOpt = Optional.empty(); private Optional fileIDOpt = Optional.empty(); - private Task imageTask; - private SoftReference imageCache; - private ProgressIndicator progressIndicator; + protected volatile Task imageTask; public DrawableUIBase(ImageGalleryController controller) { this.controller = controller; @@ -107,20 +119,41 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView synchronized public void setFile(Long newFileID) { if (getFileID().isPresent()) { if (Objects.equals(newFileID, getFileID().get()) == false) { - if (Objects.nonNull(newFileID)) { - setFileHelper(newFileID); - } +// if (Objects.nonNull(newFileID)) { + setFileHelper(newFileID); +// } } - } else if (Objects.nonNull(newFileID)) { + } else {//if (Objects.nonNull(newFileID)) { setFileHelper(newFileID); } } synchronized protected void updateContent() { - Node content = getContentNode(); - Platform.runLater(() -> { - imageBorder.setCenter(content); + if (getFile().isPresent() == false) { + Platform.runLater(() -> imageBorder.setCenter(null)); + } else { + DrawableFile file = getFile().get(); + //is an image + doReadImageTask(file); + } + } + + synchronized Node doReadImageTask(DrawableFile file) { + disposeContent(); + final Task myTask = newReadImageTask(file); + imageTask = myTask; + Node progressNode = newProgressIndicator(myTask); + Platform.runLater(() -> imageBorder.setCenter(progressNode)); + + imageTask.setOnSucceeded((WorkerStateEvent event) -> { + showImage(file, myTask);//on fx thread already }); + imageTask.setOnFailed((WorkerStateEvent event) -> { + showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);//on fx thread already + }); + + exec.execute(myTask); + return progressNode; } synchronized protected void disposeContent() { @@ -128,50 +161,52 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView imageTask.cancel(true); } imageTask = null; - imageCache = null; + Platform.runLater(() -> imageView.setImage(null)); + } - ProgressIndicator getLoadingProgressIndicator() { - if (progressIndicator == null) { - progressIndicator = new ProgressIndicator(); - } - return progressIndicator; + /** + * + * @param file the value of file + * @param imageTask the value of imageTask + */ + Node newProgressIndicator(final Task imageTask) { + ProgressIndicator loadingProgressIndicator = new ProgressIndicator(-1); + loadingProgressIndicator.progressProperty().bind(imageTask.progressProperty()); + return loadingProgressIndicator; } - Node getContentNode() { - if (getFile().isPresent() == false) { - imageCache = null; - Platform.runLater(() -> { - if (imageView != null) { - imageView.setImage(null); - } - }); - return null; - } else { - Image thumbnail = isNull(imageCache) ? null : imageCache.get(); - - if (nonNull(thumbnail)) { - Platform.runLater(() -> { - if (imageView != null) { - imageView.setImage(thumbnail); - } - }); - return imageView; + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + void showImage(DrawableFile file, Task 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> getNewImageLoadTask(DrawableFile file); + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + void showErrorNode(String errorMessage, AbstractFile file) { + Button createButton = ActionUtils.createButton(new OpenExternalViewerAction(file)); + + VBox vBox = new VBox(10, + new Label(errorMessage), createButton); + + vBox.setAlignment(Pos.CENTER); + imageBorder.setCenter(vBox); + } + + abstract Task newReadImageTask(DrawableFile file); abstract class CachedLoaderTask> extends Task { @@ -186,7 +221,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView return (isCancelled() == false) ? load() : null; } - abstract X load(); + abstract X load() throws Exception; @Override protected void succeeded() { @@ -194,7 +229,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView if (isCancelled() == false) { try { saveToCache(get()); - updateContent(); } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.WARNING, "Failed to cache content for" + file.getName(), ex); } @@ -210,23 +244,9 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView abstract void saveToCache(X result); } - abstract class ImageLoaderTask extends CachedLoaderTask> { + class ThumbnailLoaderTask extends CachedLoaderTask> { - public ImageLoaderTask(DrawableFile file) { - super(file); - } - - @Override - void saveToCache(Image result) { - synchronized (DrawableUIBase.this) { - imageCache = new SoftReference<>(result); - } - } - } - - class ThumbnailLoaderTask extends ImageLoaderTask { - - public ThumbnailLoaderTask(DrawableFile file) { + ThumbnailLoaderTask(DrawableFile file) { super(file); } @@ -234,5 +254,10 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView Image load() { return isCancelled() ? null : file.getThumbnail(); } + + @Override + void saveToCache(Image result) { +// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index f0a828f644..cb77dbd8b2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -27,7 +27,6 @@ import java.util.Objects; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; -import org.sleuthkit.autopsy.coreutils.Logger; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; @@ -52,6 +51,7 @@ import javafx.scene.text.Text; import javafx.util.Pair; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; @@ -166,7 +166,7 @@ public class MetaDataPane extends DrawableUIBase { } @Override - protected synchronized void setFileHelper(Long newFileID) { + synchronized protected void setFileHelper(Long newFileID) { setFileIDOpt(Optional.ofNullable(newFileID)); if (newFileID == null) { Platform.runLater(() -> { @@ -177,25 +177,24 @@ public class MetaDataPane extends DrawableUIBase { }); } else { disposeContent(); - updateUI(); + updateAttributesTable(); + updateCategory(); updateContent(); } } @Override - CachedLoaderTask> getNewImageLoadTask(DrawableFile file) { + CachedLoaderTask> newReadImageTask(DrawableFile file) { return new ThumbnailLoaderTask(file); } - public void updateUI() { + public void updateAttributesTable() { getFile().ifPresent(file -> { final List, Collection>> attributesList = file.getAttributesList(); Platform.runLater(() -> { tableView.getItems().clear(); tableView.getItems().setAll(attributesList); }); - - updateCategory(); }); } @@ -204,13 +203,15 @@ public class MetaDataPane extends DrawableUIBase { return imageBorder; } - /** {@inheritDoc } */ + /** + * {@inheritDoc } + */ @Subscribe @Override public void handleCategoryChanged(CategoryManager.CategoryChangeEvent evt) { getFileID().ifPresent(fileID -> { if (evt.getFileIDs().contains(fileID)) { - updateUI(); + updateAttributesTable(); } }); } @@ -220,7 +221,7 @@ public class MetaDataPane extends DrawableUIBase { public void handleTagAdded(ContentTagAddedEvent evt) { getFileID().ifPresent((fileID) -> { if (Objects.equals(evt.getAddedTag().getContent().getId(), fileID)) { - updateUI(); + updateAttributesTable(); } }); } @@ -229,7 +230,7 @@ public class MetaDataPane extends DrawableUIBase { public void handleTagDeleted(ContentTagDeletedEvent evt) { getFileID().ifPresent((fileID) -> { if (Objects.equals(evt.getDeletedTagInfo().getContentID(), fileID)) { - updateUI(); + updateAttributesTable(); } }); } @@ -241,4 +242,5 @@ public class MetaDataPane extends DrawableUIBase { getValueDisplayString(selectedItem))); } } + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index f8555046d3..13539084ca 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -23,15 +23,19 @@ import java.lang.ref.SoftReference; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.ObservableList; import javafx.concurrent.Task; +import javafx.concurrent.WorkerStateEvent; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.Button; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.ProgressIndicator; import javafx.scene.image.Image; import static javafx.scene.input.KeyCode.LEFT; import static javafx.scene.input.KeyCode.RIGHT; @@ -40,7 +44,8 @@ import javafx.scene.layout.BorderPane; import javafx.scene.media.Media; import javafx.scene.media.MediaException; import javafx.scene.media.MediaPlayer; -import javafx.scene.text.Text; +import org.controlsfx.control.MaskerPane; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; @@ -50,7 +55,9 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer; +import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableUIBase.exec; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH; +import org.sleuthkit.datamodel.TskCoreException; /** * Displays the files of a group one at a time. Designed to be embedded in a @@ -69,7 +76,7 @@ public class SlideShowView extends DrawableTileBase { @FXML private BorderPane footer; - private Task mediaTask; + SlideShowView(GroupPane gp, ImageGalleryController controller) { super(gp, controller); @@ -159,42 +166,87 @@ public class SlideShowView extends DrawableTileBase { stopVideo(); super.disposeContent(); - if (mediaTask != null) { - mediaTask.cancel(true); - } - mediaTask = null; +// if (mediaTask != null) { +// mediaTask.cancel(true); +// } +// mediaTask = null; mediaCache = null; } private SoftReference mediaCache; - /** - * {@inheritDoc } - */ @Override - Node getContentNode() { + synchronized protected void updateContent() { if (getFile().isPresent() == false) { mediaCache = null; - return super.getContentNode(); + Platform.runLater(() -> imageBorder.setCenter(null)); + } else { DrawableFile file = getFile().get(); if (file.isVideo()) { + //specially handling for videos Node mediaNode = (isNull(mediaCache)) ? null : mediaCache.get(); if (nonNull(mediaNode)) { - return mediaNode; + Platform.runLater(() -> imageBorder.setCenter(mediaNode)); } else { - if (isNull(mediaTask)) { - mediaTask = new MediaLoadTask(((VideoFile) file)); - new Thread(mediaTask).start(); - } else if (mediaTask.isDone()) { - return null; - } - return getLoadingProgressIndicator(); +// if (isNull(mediaTask)) { + MediaLoadTask mediaTask = new MediaLoadTask(((VideoFile) file)); + Node progressNode = newProgressIndicator(mediaTask); + + mediaTask.setOnSucceeded((WorkerStateEvent event) -> { + showMedia(file, mediaTask);//on fx thread already + }); + mediaTask.setOnFailed((WorkerStateEvent event) -> { + showErrorNode(getMediaLoadErrorLabel(mediaTask), file);//on fx thread already + }); + Platform.runLater(() -> imageBorder.setCenter(progressNode)); + exec.execute(mediaTask); +// } else { +// //not on fx thread; +// Platform.runLater(() -> showMedia(file)); +// } } + } else { + super.updateContent(); } - return super.getContentNode(); } } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + private void showMedia(DrawableFile file, Task 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 mediaTask) { + return Bundle.MediaViewImagePanel_errorLabel_text() + ": " + mediaTask.getException().getLocalizedMessage(); + } + + /** + * + * @param file the value of file + * @param imageTask the value of imageTask + */ + @Override + Node newProgressIndicator(final Task imageTask) { + MaskerPane maskerPane = new MaskerPane(); + ProgressIndicator loadingProgressIndicator = new ProgressBar(-1); + maskerPane.setProgressNode(loadingProgressIndicator); + + maskerPane.textProperty().bind(imageTask.messageProperty()); + loadingProgressIndicator.progressProperty().bind(imageTask.progressProperty()); + return maskerPane; + } + /** * {@inheritDoc } */ @@ -250,46 +302,41 @@ public class SlideShowView extends DrawableTileBase { } @Override - CachedLoaderTask> getNewImageLoadTask(DrawableFile file) { + Task newReadImageTask(DrawableFile file) { + return file.getReadFullSizeImageTask(); - return new ImageLoaderTask(file) { - - @Override - Image load() { - return isCancelled() ? null : file.getFullSizeImage(); - } - }; } - private class MediaLoadTask extends CachedLoaderTask> { + @NbBundle.Messages({"# {0} - file name", + "MediaLoadTask.messageText=Reading media: {0}"}) + private class MediaLoadTask extends Task { - public MediaLoadTask(VideoFile file) { - super(file); + private final VideoFile file; + + MediaLoadTask(VideoFile file) { + this.file = file; } @Override - void saveToCache(Node result) { - synchronized (SlideShowView.this) { - mediaCache = new SoftReference<>(result); - } - } - - @Override - Node load() { + protected Node call() throws Exception { + updateMessage(Bundle.MediaLoadTask_messageText(file.getName())); try { final Media media = file.getMedia(); return new VideoPlayer(new MediaPlayer(media), file); } catch (MediaException | IOException | OutOfMemoryError ex) { - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "failed to initialize MediaControl for file " + file.getName(), ex); + try { + Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to initialize VideoPlayer for file {0} : {1}", new Object[]{file.getUniquePath(), ex.getLocalizedMessage()}); + } catch (TskCoreException tskCoreException) { + Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to get unique path " + file.getName(), tskCoreException); + Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to initialize VideoPlayer for file {0} : {1}", new Object[]{file.getName(), ex.getLocalizedMessage()}); + } if (file.isDisplayableAsImage()) { - Image fullSizeImage = file.getFullSizeImage(); - Platform.runLater(() -> { - imageView.setImage(fullSizeImage); - }); - return imageView; + return doReadImageTask(file); } - return new Text(ex.getLocalizedMessage() + "\nSee the logs for details.\n\nTry the \"Open In External Viewer\" action."); + + //if we couldn't even show it as an image. rethrow exception. + throw ex; } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/external.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/external.png new file mode 100644 index 0000000000000000000000000000000000000000..976dcacaa1e6e62d17946d64a5b413225f8cd972 GIT binary patch literal 685 zcmV;e0#f~nP)FSZapB5^3#~zD5yYKTEK)1D5kW<8J1J84D}v+ISA`S1MaKf@i)nR{s%H=OFCY{OMdq{T(IzrP)TsqqcmtDnFB!OFxCh>64Lp2;Ujq^{0Wgo4Bh5^r; z!6=titR{=-@mSGj6A?=)7#;h8;Vb)aXulotlty(HmJmOsw`L%iOp;!dN-A8ARx099 z*dT~R*U^8@jgB^vg$gxYrNYyX6^tVYX0vTGT_P+N0jfr*K^doybwIN6hOnla3*5l( z)D)Yj9%26NHP&1T&uKJ$X0)0taPQa-r+u5gXUxtMUBk51;eYM|rM>&q Date: Mon, 21 Dec 2015 09:13:47 -0500 Subject: [PATCH 05/24] taskify thumbnail loading --- .../autopsy/coreutils/ImageUtils.java | 173 +++++++++++++++--- .../autopsy/imagegallery/ThumbnailCache.java | 29 ++- .../imagegallery/datamodel/DrawableFile.java | 19 +- .../imagegallery/datamodel/ImageFile.java | 29 +++ .../imagegallery/datamodel/VideoFile.java | 24 +-- .../gui/drawableviews/DrawableTile.fxml | 5 - .../gui/drawableviews/DrawableTile.java | 5 +- .../gui/drawableviews/DrawableTileBase.java | 9 +- .../gui/drawableviews/DrawableUIBase.java | 36 ++-- .../gui/drawableviews/GroupPane.java | 2 - .../gui/drawableviews/MetaDataPane.java | 5 +- .../gui/drawableviews/SlideShowView.fxml | 5 - .../gui/drawableviews/SlideShowView.java | 8 +- 13 files changed, 251 insertions(+), 98 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index c7f98cc061..538d5a30ad 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -44,6 +44,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Level; +import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.embed.swing.SwingFXUtils; import javax.annotation.Nonnull; @@ -57,6 +58,7 @@ import javax.imageio.stream.ImageInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.opencv.core.Core; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; @@ -409,6 +411,7 @@ public class ImageUtils { } /** + * * Get a file object for where the cached icon should exist. The returned * file may not exist. * @@ -615,17 +618,119 @@ public class ImageUtils { } } - public static Task newReadImageTask(AbstractFile file) { - return new ReadImageTask(file); + public static Task newGetThumbnailTask(AbstractFile file, int iconSize) { + return new GetOrGenerateThumbnailTask(file, iconSize); } - static private class ReadImageTask extends Task implements IIOReadProgressListener { + static private class GetOrGenerateThumbnailTask extends ReadImageTaskBase { + + private final int iconSize; + + private GetOrGenerateThumbnailTask(AbstractFile file, int iconSize) { + super(file); + this.iconSize = iconSize; + } + + @Override + protected javafx.scene.image.Image call() throws Exception { + + // If a thumbnail file is already saved locally, just read that. + File cacheFile = getCachedThumbnailLocation(file.getId()); + if (cacheFile.exists()) { + try { + BufferedImage cachedThumbnail = ImageIO.read(cacheFile); + if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) { + return SwingFXUtils.toFXImage(cachedThumbnail, null); + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{file.getName(), ex.getMessage()}); //NON-NLS + } + } + + //no cached thumbnail so read the image from the datasource using the ReadImageTask + final ReadImageTask readImageTask = new ReadImageTask(file); + readImageTask.progressProperty().addListener((Observable observable) + -> updateProgress(readImageTask.getWorkDone(), readImageTask.getTotalWork())); + readImageTask.messageProperty().addListener((Observable observable) + -> updateMessage(readImageTask.getMessage())); + readImageTask.run(); + try { + BufferedImage bufferedImage = readImageTask.getBufferedImage(); + if (isNull(bufferedImage)) { + LOGGER.log(Level.WARNING, "Failed to read image for thumbnail generation."); + throw new IIOException("Failed to read image for thumbnail generation."); + } + + updateMessage("scaling image"); + updateProgress(-1, 1); + + BufferedImage thumbnail; + try { + thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); + } catch (IllegalArgumentException | OutOfMemoryError e) { + // if resizing does not work due to extreme aspect ratio, crop the image instead. + try { + LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getUniquePath(), e.getLocalizedMessage()}); //NON-NLS + } catch (TskCoreException tskCoreException) { + LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getName(), e.getLocalizedMessage()}); //NON-NLS + LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N + } + final int cropHeight = Math.min(iconSize, bufferedImage.getHeight()); + final int cropWidth = Math.min(iconSize, bufferedImage.getWidth()); + try { + thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight); + } catch (Exception e2) { + LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}", new Object[]{file.getName(), e2.getLocalizedMessage()}); //NON-NLS + throw e2; + } + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}", new Object[]{file.getName(), e.getLocalizedMessage()}); //NON-NLS + throw e; + } + updateProgress(-1, 1); + + saveThumbnail(thumbnail, cacheFile); + return SwingFXUtils.toFXImage(thumbnail, null); + } catch (InterruptedException | ExecutionException e) { +// try { +// LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getUniquePath(), e.getLocalizedMessage()}); //NON-NLS +// } catch (TskCoreException tskCoreException) { +// LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getName(), e.getLocalizedMessage()}); //NON-NLS +// LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N +// } + throw e; + } + + } + + private void saveThumbnail(BufferedImage thumbnail, File cacheFile) { + if (nonNull(thumbnail) && DEFAULT_THUMBNAIL != thumbnail) { + imageSaver.execute(() -> { + try { + Files.createParentDirs(cacheFile); + if (cacheFile.exists()) { + cacheFile.delete(); + } + ImageIO.write(thumbnail, FORMAT, cacheFile); + } catch (IllegalArgumentException | IOException ex1) { + LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + file.getName(), ex1); //NON-NLS + } + }); + } + } + } + + public static Task newReadImageTask(AbstractFile file) { + return new ReadImageTask(file); + + } + + static private class ReadImageTask extends ReadImageTaskBase { - private final AbstractFile file; volatile private BufferedImage bufferedImage = null; ReadImageTask(AbstractFile file) { - this.file = file; + super(file); } @Override @@ -635,7 +740,6 @@ public class ImageUtils { protected javafx.scene.image.Image call() throws Exception { updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { - if (ImageUtils.isGIF(file)) { //directly read GIF to preserve potential animation, javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(inputStream)); @@ -657,8 +761,9 @@ public class ImageUtils { 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, create a destination image to hold + * the decoded result, then pass that image with the + * param. */ ImageReadParam param = reader.getDefaultReadParam(); @@ -679,20 +784,36 @@ public class ImageUtils { } } - public void logError(@Nullable Throwable e) { - String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N - try { - LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, "Could not read the image: {0}. {1}", new Object[]{file.getName(), message}); //NOI18N - LOGGER.log(Level.SEVERE, "Failed to get unique path for file", tskCoreException); //NOI18N - } + public BufferedImage getBufferedImage() throws InterruptedException, ExecutionException { + get(); + return bufferedImage; + } + + } + + static private abstract class ReadImageTaskBase extends Task implements IIOReadProgressListener { + + protected final AbstractFile file; + + public ReadImageTaskBase(AbstractFile file) { + this.file = file; } @Override - protected void failed() { - super.failed(); - logError(getException()); + public void imageProgress(ImageReader source, float percentageDone) { + //update this task with the progress reported by ImageReader.read + updateProgress(percentageDone, 100); + } + + public void logError(@Nullable Throwable e) { + Exceptions.printStackTrace(e); + String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N + try { + LOGGER.log(Level.WARNING, "ImageIO could not read {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N + } catch (TskCoreException tskCoreException) { + LOGGER.log(Level.WARNING, "ImageIO could not read {0}. {1}", new Object[]{file.getName(), message}); //NOI18N + LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N + } } @Override @@ -714,13 +835,9 @@ public class ImageUtils { } @Override - public void imageProgress(ImageReader source, float percentageDone) { - //update this task with the progress reported by ImageReader.read - updateProgress(percentageDone, 100); - } - - @Override - public void imageStarted(ImageReader source, int imageIndex) { + protected void failed() { + super.failed(); + logError(getException()); } @Override @@ -728,6 +845,10 @@ public class ImageUtils { updateProgress(100, 100); } + @Override + public void imageStarted(ImageReader source, int imageIndex) { + } + @Override public void sequenceStarted(ImageReader source, int minIndex) { } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index 19551d6f7f..0a7f936b12 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -31,12 +31,15 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; 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; @@ -165,7 +168,7 @@ 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 getCacheFile(DrawableFile file) { @@ -177,4 +180,28 @@ public enum ThumbnailCache { return Optional.empty(); } } + + public Task getThumbnailTask(DrawableFile file) { + final Optional option = cache.getIfPresent(file.getId()); + if (option != null && option.isPresent()) { + return new Task() { + @Override + protected Image call() throws Exception { + return option.get(); + } + }; + } + final Task newGetThumbnailTask = ImageUtils.newGetThumbnailTask(file.getAbstractFile(), MAX_THUMBNAIL_SIZE); + newGetThumbnailTask.stateProperty().addListener((Observable observable) -> { + switch (newGetThumbnailTask.getState()) { + case SUCCEEDED: + try { + cache.put(Long.MIN_VALUE, Optional.of(newGetThumbnailTask.get())); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + } + }); + return newGetThumbnailTask; + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index f0f3268678..43a313727b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -23,7 +23,6 @@ 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; @@ -39,7 +38,6 @@ import org.apache.commons.lang3.text.WordUtils; 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.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -79,6 +77,7 @@ public abstract class DrawableFile extends AbstractFile } SoftReference imageRef; +// SoftReference thumbref; private String drawablePath; @@ -258,12 +257,19 @@ public abstract class DrawableFile extends AbstractFile } } + @Deprecated public Image getThumbnail() { - return ThumbnailCache.getDefault().get(this); + try { + return getThumbnailTask().get(); + } catch (InterruptedException | ExecutionException ex) { + return null; + } + } - @Deprecated + public abstract Task getThumbnailTask(); + @Deprecated //use non-blocking getReadFullSizeImageTask instead for most cases public Image getFullSizeImage() { try { return getReadFullSizeImageTask().get(); @@ -304,11 +310,6 @@ public abstract class DrawableFile extends AbstractFile } } - public boolean isDisplayableAsImage() { - Image thumbnail = getThumbnail(); - return Objects.nonNull(thumbnail) && thumbnail.errorProperty().get() == false; - } - @Nonnull public Set getHashSetNamesUnchecked() { try { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index 103561ce32..504922936d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -28,6 +28,7 @@ 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.ThumbnailCache; import org.sleuthkit.datamodel.AbstractFile; /** @@ -48,6 +49,34 @@ public class ImageFile extends DrawableFile { } + @Override + public Task getThumbnailTask() { + return ThumbnailCache.getDefault().getThumbnailTask(this); +// newGetThumbTask.stateProperty().addListener((Observable observable) -> { +// switch (newGetThumbTask.getState()) { +// case CANCELLED: +// break; +// case FAILED: +// break; +// case SUCCEEDED: +// try { +// thumbref = new SoftReference<>(newGetThumbTask.get()); +// } catch (InterruptedException | ExecutionException interruptedException) { +// } +// break; +// } +// }); +// return newGetThumbTask; +// } else { +// return new Task() { +// @Override +// protected Image call() throws Exception { +// return thumbnail; +// } +// }; +// } + } + @Override public Task getReadFullSizeImageTask() { Image image = (imageRef != null) ? imageRef.get() : null; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index fde2a890f1..3c230383d6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -24,9 +24,7 @@ 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.concurrent.ExecutionException; -import java.util.logging.Level; import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.embed.swing.SwingFXUtils; @@ -36,9 +34,9 @@ import javafx.scene.media.MediaException; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.sleuthkit.autopsy.coreutils.ImageUtils; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.VideoUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.datamodel.AbstractFile; public class VideoFile extends DrawableFile { @@ -53,6 +51,12 @@ public class VideoFile extends DrawableFile { return VIDEO_ICON; } + @Override + public Task getThumbnailTask() { + return ThumbnailCache.getDefault().getThumbnailTask(VideoFile.this); + + } + @Override public Task getReadFullSizeImageTask() { Image image = (imageRef != null) ? imageRef.get() : null; @@ -61,7 +65,7 @@ public class VideoFile extends DrawableFile { @Override protected Image call() throws Exception { - final BufferedImage bufferedImage = (BufferedImage) ImageUtils.getThumbnail(getAbstractFile(), 1024); + final BufferedImage bufferedImage = ImageUtils.getThumbnail(getAbstractFile(), 1024); return (bufferedImage == ImageUtils.getDefaultThumbnail()) ? null : SwingFXUtils.toFXImage(bufferedImage, null); @@ -119,18 +123,6 @@ public class VideoFile extends DrawableFile { } - 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 { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.fxml index 2d6c8304e4..acfcdc1336 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.fxml @@ -34,11 +34,6 @@ - - - - - diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java index 6018c323c6..ba7c275063 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java @@ -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> newReadImageTask(DrawableFile file) { - return new ThumbnailLoaderTask(file); + Task newReadImageTask(DrawableFile file) { + return file.getThumbnailTask(); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index aa16c22b5e..809f268902 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -71,7 +71,6 @@ 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; @@ -112,8 +111,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { @FXML private ImageView hashHitImageView; - @FXML - protected ImageView undisplayableImageView; + /** * displays the icon representing follow up tag */ @@ -316,7 +314,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { 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(() -> { @@ -324,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)); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 8cc42c8306..8b6cde0022 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -56,7 +56,7 @@ import org.sleuthkit.datamodel.TskCoreException; @NbBundle.Messages({"MediaViewImagePanel.errorLabel.text=Could not load file."}) abstract public class DrawableUIBase extends AnchorPane implements DrawableView { - static final Executor exec = Executors.newWorkStealingPool(); + static final Executor exec = Executors.newSingleThreadExecutor(); private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); @@ -158,7 +158,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView synchronized protected void disposeContent() { if (imageTask != null) { - imageTask.cancel(true); + imageTask.cancel(); } imageTask = null; Platform.runLater(() -> imageView.setImage(null)); @@ -244,20 +244,20 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView abstract void saveToCache(X result); } - class ThumbnailLoaderTask extends CachedLoaderTask> { - - ThumbnailLoaderTask(DrawableFile file) { - super(file); - } - - @Override - Image load() { - return isCancelled() ? null : file.getThumbnail(); - } - - @Override - void saveToCache(Image result) { -// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - } +// class ThumbnailLoaderTask extends CachedLoaderTask> { +// +// ThumbnailLoaderTask(DrawableFile file) { +// super(file); +// } +// +// @Override +// Image load() { +// return isCancelled() ? null : file.getThumbnail(); +// } +// +// @Override +// void saveToCache(Image result) { +//// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. +// } +// } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 0f25e60d30..7bc52d4c5d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -774,12 +774,10 @@ public class GroupPane extends BorderPane { @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); - tile.setFile(item); } void resetItem() { -// updateItem(null, true); tile.setFile(null); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index cb77dbd8b2..8395b4ab8c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -31,6 +31,7 @@ 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; @@ -184,8 +185,8 @@ public class MetaDataPane extends DrawableUIBase { } @Override - CachedLoaderTask> newReadImageTask(DrawableFile file) { - return new ThumbnailLoaderTask(file); + Task newReadImageTask(DrawableFile file) { + return file.getThumbnailTask(); } public void updateAttributesTable() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.fxml index 712f3a953a..d7a631854e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.fxml @@ -55,11 +55,6 @@ - - - - - diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index 13539084ca..f97dbae1c6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -331,13 +331,9 @@ public class SlideShowView extends DrawableTileBase { Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to initialize VideoPlayer for file {0} : {1}", new Object[]{file.getName(), ex.getLocalizedMessage()}); } - if (file.isDisplayableAsImage()) { + return doReadImageTask(file); - } - - //if we couldn't even show it as an image. rethrow exception. - throw ex; - } + } } } } From 1455dd3a6d886169e474e1d15909f2fd33f36df6 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 28 Dec 2015 11:19:39 -0500 Subject: [PATCH 06/24] refactor read image tasks, support cancellation --- .../autopsy/coreutils/ImageUtils.java | 191 +++++++++--------- .../autopsy/imagegallery/ThumbnailCache.java | 2 +- .../imagegallery/datamodel/DrawableFile.java | 5 +- .../imagegallery/datamodel/ImageFile.java | 44 ++-- .../imagegallery/datamodel/VideoFile.java | 7 - .../netbeans/core/startup/Bundle.properties | 2 +- .../core/windows/view/ui/Bundle.properties | 2 +- 7 files changed, 115 insertions(+), 138 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 538d5a30ad..6ee9f2a2ae 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -44,7 +44,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Level; -import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.embed.swing.SwingFXUtils; import javax.annotation.Nonnull; @@ -55,10 +54,10 @@ 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.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; @@ -152,8 +151,8 @@ public class ImageUtils { /** * thread that saves generated thumbnails to disk in the background */ - private static final Executor imageSaver - = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() + private static final Executor imageSaver = + Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() .namingPattern("icon saver-%d").build()); public static List getSupportedImageExtensions() { @@ -568,9 +567,9 @@ public class ImageUtils { return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight())); } } catch (OutOfMemoryError e) { - LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName() + ": " + e.getLocalizedMessage()); //NON-NLS + LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName() + ": " + e.toString()); //NON-NLS } catch (Exception e) { - LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.getLocalizedMessage()); //NON-NLS + LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.toString()); //NON-NLS } return null; } @@ -590,12 +589,20 @@ public class ImageUtils { reader.setInput(input); return reader.getWidth(0); } else { - throw new IIOException("No ImageReader found for file." + file.getName()); //NOI18N + throw newImageReaderException(file); } } } } + private static IIOException newImageReaderException(AbstractFile file) { + try { + return new IIOException("No ImageReader found for file." + file.getUniquePath()); //NOI18N + } catch (TskCoreException ex) { + return new IIOException("No ImageReader found for file." + file.getName()); //NOI18N + } + } + static public int getHeight(AbstractFile file) throws IIOException, IOException { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { @@ -611,7 +618,7 @@ public class ImageUtils { return reader.getHeight(0); } else { - throw new IIOException("No ImageReader found for file." + file.getName()); //NOI18N + throw newImageReaderException(file); } } @@ -625,17 +632,18 @@ public class ImageUtils { static private class GetOrGenerateThumbnailTask extends ReadImageTaskBase { private final int iconSize; + private final File cacheFile; private GetOrGenerateThumbnailTask(AbstractFile file, int iconSize) { super(file); this.iconSize = iconSize; + cacheFile = getCachedThumbnailLocation(file.getId()); } @Override protected javafx.scene.image.Image call() throws Exception { // If a thumbnail file is already saved locally, just read that. - File cacheFile = getCachedThumbnailLocation(file.getId()); if (cacheFile.exists()) { try { BufferedImage cachedThumbnail = ImageIO.read(cacheFile); @@ -643,80 +651,60 @@ public class ImageUtils { return SwingFXUtils.toFXImage(cachedThumbnail, null); } } catch (Exception ex) { - LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{file.getName(), ex.getMessage()}); //NON-NLS + logError("ImageIO had a problem reading thumbnail for image {0}: " + ex.toString()); //NON-NLS } } - //no cached thumbnail so read the image from the datasource using the ReadImageTask - final ReadImageTask readImageTask = new ReadImageTask(file); - readImageTask.progressProperty().addListener((Observable observable) - -> updateProgress(readImageTask.getWorkDone(), readImageTask.getTotalWork())); - readImageTask.messageProperty().addListener((Observable observable) - -> updateMessage(readImageTask.getMessage())); - readImageTask.run(); + BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null); + if (isNull(bufferedImage)) { + LOGGER.log(Level.WARNING, "Failed to read image for thumbnail generation."); + throw new IIOException("Failed to read image for thumbnail generation."); + } + updateMessage("scaling image"); + updateProgress(-1, 1); + BufferedImage thumbnail = null; try { - BufferedImage bufferedImage = readImageTask.getBufferedImage(); - if (isNull(bufferedImage)) { - LOGGER.log(Level.WARNING, "Failed to read image for thumbnail generation."); - throw new IIOException("Failed to read image for thumbnail generation."); - } + thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); + } catch (IllegalArgumentException | OutOfMemoryError e) { + // if resizing does not work due to extreme aspect ratio, crop the image instead. + logError("Could not scale image {0}: " + e.toString() + "\nAttemptying to crop {0} instead"); //NON-NLS - updateMessage("scaling image"); - updateProgress(-1, 1); + 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); - BufferedImage thumbnail; - try { - thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); - } catch (IllegalArgumentException | OutOfMemoryError e) { - // if resizing does not work due to extreme aspect ratio, crop the image instead. - try { - LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getUniquePath(), e.getLocalizedMessage()}); //NON-NLS - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getName(), e.getLocalizedMessage()}); //NON-NLS - LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N - } - final int cropHeight = Math.min(iconSize, bufferedImage.getHeight()); - final int cropWidth = Math.min(iconSize, bufferedImage.getWidth()); try { thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight); - } catch (Exception e2) { - LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}", new Object[]{file.getName(), e2.getLocalizedMessage()}); //NON-NLS - throw e2; + } catch (Exception cropException) { + logError("Could not crop image {0}: " + cropException.toString()); //NON-NLS + throw cropException; } - } catch (Exception e) { - LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}", new Object[]{file.getName(), e.getLocalizedMessage()}); //NON-NLS - throw e; } - updateProgress(-1, 1); - - saveThumbnail(thumbnail, cacheFile); - return SwingFXUtils.toFXImage(thumbnail, null); - } catch (InterruptedException | ExecutionException e) { -// try { -// LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getUniquePath(), e.getLocalizedMessage()}); //NON-NLS -// } catch (TskCoreException tskCoreException) { -// LOGGER.log(Level.WARNING, "Could not scale image {0}: {1}.\nAttemptying to crop {0} instead", new Object[]{file.getName(), e.getLocalizedMessage()}); //NON-NLS -// LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N -// } + } catch (Exception e) { + logError("Could not scale image {0}: " + e.toString()); //NON-NLS throw e; } - + updateProgress(-1, 1); + if (nonNull(thumbnail) && DEFAULT_THUMBNAIL != thumbnail) { + saveThumbnail(thumbnail); + } + return SwingFXUtils.toFXImage(thumbnail, null); } - private void saveThumbnail(BufferedImage thumbnail, File cacheFile) { - if (nonNull(thumbnail) && DEFAULT_THUMBNAIL != thumbnail) { - imageSaver.execute(() -> { - try { - Files.createParentDirs(cacheFile); - if (cacheFile.exists()) { - cacheFile.delete(); - } - ImageIO.write(thumbnail, FORMAT, cacheFile); - } catch (IllegalArgumentException | IOException ex1) { - LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + file.getName(), ex1); //NON-NLS + 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) { + logError("Could not write thumbnail for {0}: " + ex.toString()); + } + }); } } @@ -727,8 +715,6 @@ public class ImageUtils { static private class ReadImageTask extends ReadImageTaskBase { - volatile private BufferedImage bufferedImage = null; - ReadImageTask(AbstractFile file) { super(file); } @@ -739,6 +725,26 @@ public class ImageUtils { "LoadImageTask.mesageText=Reading image: {0}"}) protected javafx.scene.image.Image call() throws Exception { updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); + return readImage(); + } + } + + static private abstract class ReadImageTaskBase extends Task implements IIOReadProgressListener { + + protected final AbstractFile file; + private volatile BufferedImage bufferedImage = null; + private ImageReader reader; + + public ReadImageTaskBase(AbstractFile file) { + this.file = file; + } + + public BufferedImage getBufferedImage() throws InterruptedException, ExecutionException { + get(); + return bufferedImage; + } + + protected javafx.scene.image.Image readImage() throws IOException { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { if (ImageUtils.isGIF(file)) { //directly read GIF to preserve potential animation, @@ -756,7 +762,7 @@ public class ImageUtils { Iterator readers = ImageIO.getImageReaders(input); if (readers.hasNext()) { - ImageReader reader = readers.next(); + reader = readers.next(); reader.addIIOReadProgressListener(this); reader.setInput(input); /* @@ -771,11 +777,15 @@ public class ImageUtils { param.setDestination(bufferedImage); try { reader.read(0, param); + if (isCancelled()) { + return null; + } } catch (IOException iOException) { // Ignore this exception or display a warning or similar, for exceptions happening during decoding - logError(iOException); + logError("ImageIO could not read {0}. It may be unsupported or corrupt: " + iOException.toString()); } reader.removeIIOReadProgressListener(this); + reader.dispose(); return SwingFXUtils.toFXImage(bufferedImage, null); } else { throw new IIOException("No ImageReader found for file."); //NOI18N @@ -784,34 +794,21 @@ public class ImageUtils { } } - public BufferedImage getBufferedImage() throws InterruptedException, ExecutionException { - get(); - return bufferedImage; - } - - } - - static private abstract class ReadImageTaskBase extends Task implements IIOReadProgressListener { - - protected final AbstractFile file; - - public ReadImageTaskBase(AbstractFile file) { - this.file = 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(); + } } - public void logError(@Nullable Throwable e) { - Exceptions.printStackTrace(e); - String message = e == null ? "" : "It may be unsupported or corrupt: " + e.getLocalizedMessage(); //NOI18N + public void logError(String template) { try { - LOGGER.log(Level.WARNING, "ImageIO could not read {0}. {1}", new Object[]{file.getUniquePath(), message}); //NOI18N - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, "ImageIO could not read {0}. {1}", new Object[]{file.getName(), message}); //NOI18N + LOGGER.log(Level.WARNING, template, file.getUniquePath()); //NOI18N + } catch (Exception tskCoreException) { + LOGGER.log(Level.WARNING, template, file.getName()); //NOI18N LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N } } @@ -822,22 +819,22 @@ public class ImageUtils { try { javafx.scene.image.Image fxImage = get(); if (fxImage == null) { - logError(null); + logError("ImageIO could not read {0}. It may be unsupported or corrupt"); } else { if (fxImage.isError()) { //if there was somekind of error, log it - logError(fxImage.getException()); + logError("ImageIO could not read {0}. It may be unsupported or corrupt:" + ObjectUtils.toString(fxImage.getException())); } } } catch (InterruptedException | ExecutionException ex) { - logError(ex.getCause()); + failed(); } } @Override protected void failed() { super.failed(); - logError(getException()); + logError("ImageIO could not read {0}. It may be unsupported or corrupt: " + ObjectUtils.toString(getException())); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index 0a7f936b12..9223cb564e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -132,7 +132,7 @@ public enum ThumbnailCache { return Optional.of(new Image(new BufferedInputStream(new ReadContentInputStream(file.getAbstractFile())), MAX_THUMBNAIL_SIZE, MAX_THUMBNAIL_SIZE, true, true)); } - BufferedImage thumbnail = getCacheFile(file).map((File cachFile) -> { + BufferedImage thumbnail = getCacheFile(file).map(cachFile -> { if (cachFile.exists()) { // If a thumbnail file is already saved locally, load it try { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 43a313727b..9a52245bca 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -38,6 +38,7 @@ import org.apache.commons.lang3.text.WordUtils; 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.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -267,7 +268,9 @@ public abstract class DrawableFile extends AbstractFile } - public abstract Task getThumbnailTask(); + public Task getThumbnailTask() { + return ThumbnailCache.getDefault().getThumbnailTask(this); + } @Deprecated //use non-blocking getReadFullSizeImageTask instead for most cases public Image getFullSizeImage() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index 504922936d..071dc730ed 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -28,8 +28,8 @@ 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.ThumbnailCache; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; /** * ImageGallery data model object that represents an image file. It is a @@ -49,33 +49,8 @@ public class ImageFile extends DrawableFile { } - @Override - public Task getThumbnailTask() { - return ThumbnailCache.getDefault().getThumbnailTask(this); -// newGetThumbTask.stateProperty().addListener((Observable observable) -> { -// switch (newGetThumbTask.getState()) { -// case CANCELLED: -// break; -// case FAILED: -// break; -// case SUCCEEDED: -// try { -// thumbref = new SoftReference<>(newGetThumbTask.get()); -// } catch (InterruptedException | ExecutionException interruptedException) { -// } -// break; -// } -// }); -// return newGetThumbTask; -// } else { -// return new Task() { -// @Override -// protected Image call() throws Exception { -// return thumbnail; -// } -// }; -// } - } + + @Override public Task getReadFullSizeImageTask() { @@ -112,7 +87,7 @@ public class ImageFile extends DrawableFile { try { return (double) ImageUtils.getWidth(this.getAbstractFile()); } catch (IOException ex) { - LOGGER.log(Level.WARNING, "getWidth", ex); + logError("ImageIO could not determine width of {0}: {1}", ex.toString()); return -1.0; } } @@ -122,11 +97,20 @@ public class ImageFile extends DrawableFile { try { return (double) ImageUtils.getHeight(this.getAbstractFile()); } catch (IOException ex) { - LOGGER.log(Level.WARNING, "getHeight", ex); + logError("ImageIO could not determine height of {0}: {1}", ex.toString()); return -1.0; } } + private void logError(final String message, String exceptionString) { + try { + LOGGER.log(Level.WARNING, message, new Object[]{this.getUniquePath(), exceptionString}); + } catch (TskCoreException tskCoreException) { + LOGGER.log(Level.SEVERE, "Failed to get unique path for " + this.getName(), tskCoreException); + LOGGER.log(Level.WARNING, message, new Object[]{this.getName(), exceptionString}); + } + } + @Override public boolean isVideo() { return false; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index 3c230383d6..e743781dda 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -36,7 +36,6 @@ import org.netbeans.api.progress.ProgressHandleFactory; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.VideoUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.datamodel.AbstractFile; public class VideoFile extends DrawableFile { @@ -51,12 +50,6 @@ public class VideoFile extends DrawableFile { return VIDEO_ICON; } - @Override - public Task getThumbnailTask() { - return ThumbnailCache.getDefault().getThumbnailTask(VideoFile.this); - - } - @Override public Task getReadFullSizeImageTask() { Image image = (imageRef != null) ? imageRef.get() : null; diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index a4c8f7f8cf..f0d04d8afb 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Wed, 18 Nov 2015 20:51:12 -0500 +#Mon, 21 Dec 2015 06:26:49 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 4792059f24..960ee135b3 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Wed, 18 Nov 2015 20:51:12 -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 From 3e7e9ec592f2ea8ba37dd86bb53b3ebe80cc3742 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Dec 2015 11:40:30 -0500 Subject: [PATCH 07/24] include video thumbnails in thumbnail generation task --- .../autopsy/coreutils/ImageUtils.java | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 6ee9f2a2ae..66a31010c5 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -655,36 +655,45 @@ public class ImageUtils { } } - BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null); - if (isNull(bufferedImage)) { - LOGGER.log(Level.WARNING, "Failed to read image for thumbnail generation."); - throw new IIOException("Failed to read image for thumbnail generation."); - } - updateMessage("scaling image"); - updateProgress(-1, 1); BufferedImage thumbnail = null; - try { - thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); - } catch (IllegalArgumentException | OutOfMemoryError e) { - // if resizing does not work due to extreme aspect ratio, crop the image instead. - logError("Could not scale image {0}: " + e.toString() + "\nAttemptying to crop {0} instead"); //NON-NLS - - 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) { - logError("Could not crop image {0}: " + cropException.toString()); //NON-NLS - throw cropException; - } + if (VideoUtils.isVideoThumbnailSupported(file)) { + if (openCVLoaded) { + thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); + } else { + thumbnail = DEFAULT_THUMBNAIL; + } + } else { + BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null); + if (isNull(bufferedImage)) { + LOGGER.log(Level.WARNING, "Failed to read image for thumbnail generation."); + throw new IIOException("Failed to read image for thumbnail generation."); + } + updateMessage("scaling image"); + updateProgress(-1, 1); + + try { + thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); + } catch (IllegalArgumentException | OutOfMemoryError e) { + // if resizing does not work due to extreme aspect ratio, crop the image instead. + logError("Could not scale image {0}: " + e.toString() + "\nAttemptying to crop {0} instead"); //NON-NLS + + 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) { + logError("Could not crop image {0}: " + cropException.toString()); //NON-NLS + throw cropException; + } + } + } catch (Exception e) { + logError("Could not scale image {0}: " + e.toString()); //NON-NLS + throw e; } - } catch (Exception e) { - logError("Could not scale image {0}: " + e.toString()); //NON-NLS - throw e; } updateProgress(-1, 1); if (nonNull(thumbnail) && DEFAULT_THUMBNAIL != thumbnail) { From 19ae050638f23ad4bfe8795a5c5722e47694aa22 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Dec 2015 12:59:12 -0500 Subject: [PATCH 08/24] cleanup, improve logging and progress messages --- .../autopsy/coreutils/ImageUtils.java | 12 ++--- .../imagegallery/datamodel/VideoFile.java | 2 - .../gui/drawableviews/DrawableUIBase.java | 31 +++++------- .../gui/drawableviews/SlideShowView.java | 50 ++++++++----------- 4 files changed, 40 insertions(+), 55 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 66a31010c5..0fdee5a88c 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -636,6 +636,7 @@ public class ImageUtils { private GetOrGenerateThumbnailTask(AbstractFile file, int iconSize) { super(file); + updateMessage("Loading thumbnail for " + file.getName()); this.iconSize = iconSize; cacheFile = getCachedThumbnailLocation(file.getId()); } @@ -658,6 +659,7 @@ public class ImageUtils { BufferedImage thumbnail = null; if (VideoUtils.isVideoThumbnailSupported(file)) { if (openCVLoaded) { + updateMessage("Generating preview for " + file.getName()); thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); } else { thumbnail = DEFAULT_THUMBNAIL; @@ -668,14 +670,13 @@ public class ImageUtils { LOGGER.log(Level.WARNING, "Failed to read image for thumbnail generation."); throw new IIOException("Failed to read image for thumbnail generation."); } - updateMessage("scaling image"); updateProgress(-1, 1); try { thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); } catch (IllegalArgumentException | OutOfMemoryError e) { // if resizing does not work due to extreme aspect ratio, crop the image instead. - logError("Could not scale image {0}: " + e.toString() + "\nAttemptying to crop {0} instead"); //NON-NLS + logError("Could not scale image {0}: " + e.toString() + ". Attemptying to crop {0} instead"); //NON-NLS final int height = bufferedImage.getHeight(); final int width = bufferedImage.getWidth(); @@ -719,7 +720,6 @@ public class ImageUtils { public static Task newReadImageTask(AbstractFile file) { return new ReadImageTask(file); - } static private class ReadImageTask extends ReadImageTaskBase { @@ -740,11 +740,11 @@ public class ImageUtils { static private abstract class ReadImageTaskBase extends Task implements IIOReadProgressListener { - protected final AbstractFile file; + final AbstractFile file; private volatile BufferedImage bufferedImage = null; private ImageReader reader; - public ReadImageTaskBase(AbstractFile file) { + ReadImageTaskBase(AbstractFile file) { this.file = file; } @@ -816,7 +816,7 @@ public class ImageUtils { public void logError(String template) { try { LOGGER.log(Level.WARNING, template, file.getUniquePath()); //NOI18N - } catch (Exception tskCoreException) { + } catch (TskCoreException tskCoreException) { LOGGER.log(Level.WARNING, template, file.getName()); //NOI18N LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index e743781dda..4ba866144c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -101,13 +101,11 @@ public class VideoFile extends DrawableFile { 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()); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 8b6cde0022..1cb4b0c34c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -28,7 +28,6 @@ import java.util.concurrent.Executors; import java.util.logging.Level; import javafx.application.Platform; import javafx.concurrent.Task; -import javafx.concurrent.WorkerStateEvent; import javafx.fxml.FXML; import javafx.geometry.Pos; import javafx.scene.Node; @@ -53,24 +52,24 @@ import org.sleuthkit.datamodel.TskCoreException; /** * */ -@NbBundle.Messages({"MediaViewImagePanel.errorLabel.text=Could not load file."}) +@NbBundle.Messages({"MediaViewImagePanel.errorLabel.text=Could not read file."}) abstract public class DrawableUIBase extends AnchorPane implements DrawableView { - static final Executor exec = Executors.newSingleThreadExecutor(); + static final Executor exec = Executors.newWorkStealingPool(); private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); @FXML - protected BorderPane imageBorder; + private BorderPane imageBorder; @FXML - protected ImageView imageView; + private ImageView imageView; private final ImageGalleryController controller; private Optional> fileOpt = Optional.empty(); private Optional fileIDOpt = Optional.empty(); - protected volatile Task imageTask; + private volatile Task imageTask; public DrawableUIBase(ImageGalleryController controller) { this.controller = controller; @@ -132,27 +131,21 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView if (getFile().isPresent() == false) { Platform.runLater(() -> imageBorder.setCenter(null)); } else { - DrawableFile file = getFile().get(); - //is an image - doReadImageTask(file); + doReadImageTask(getFile().get()); } } synchronized Node doReadImageTask(DrawableFile file) { disposeContent(); - final Task myTask = newReadImageTask(file); - imageTask = myTask; - Node progressNode = newProgressIndicator(myTask); + imageTask = newReadImageTask(file); + Node progressNode = newProgressIndicator(imageTask); Platform.runLater(() -> imageBorder.setCenter(progressNode)); - imageTask.setOnSucceeded((WorkerStateEvent event) -> { - showImage(file, myTask);//on fx thread already - }); - imageTask.setOnFailed((WorkerStateEvent event) -> { - showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);//on fx thread already - }); + //called on fx thread + imageTask.setOnSucceeded(succeeded -> showImage(file, imageTask)); + imageTask.setOnFailed(failed -> showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file)); - exec.execute(myTask); + exec.execute(imageTask); return progressNode; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index f97dbae1c6..505ddba571 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -29,7 +29,6 @@ import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.ObservableList; import javafx.concurrent.Task; -import javafx.concurrent.WorkerStateEvent; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.Node; @@ -76,7 +75,6 @@ public class SlideShowView extends DrawableTileBase { @FXML private BorderPane footer; - SlideShowView(GroupPane gp, ImageGalleryController controller) { super(gp, controller); @@ -188,22 +186,16 @@ public class SlideShowView extends DrawableTileBase { if (nonNull(mediaNode)) { Platform.runLater(() -> imageBorder.setCenter(mediaNode)); } else { -// if (isNull(mediaTask)) { - MediaLoadTask mediaTask = new MediaLoadTask(((VideoFile) file)); - Node progressNode = newProgressIndicator(mediaTask); - mediaTask.setOnSucceeded((WorkerStateEvent event) -> { - showMedia(file, mediaTask);//on fx thread already - }); - mediaTask.setOnFailed((WorkerStateEvent event) -> { - showErrorNode(getMediaLoadErrorLabel(mediaTask), file);//on fx thread already - }); - Platform.runLater(() -> imageBorder.setCenter(progressNode)); - exec.execute(mediaTask); -// } else { -// //not on fx thread; -// Platform.runLater(() -> showMedia(file)); -// } + MediaLoadTask mediaTask = new MediaLoadTask(((VideoFile) file)); + Node progressNode = newProgressIndicator(mediaTask); + Platform.runLater(() -> imageBorder.setCenter(progressNode)); + + //called on fx thread + mediaTask.setOnSucceeded(succedded -> showMedia(file, mediaTask)); + mediaTask.setOnFailed(failed -> showErrorNode(getMediaLoadErrorLabel(mediaTask), file)); + + exec.execute(mediaTask); } } else { super.updateContent(); @@ -308,32 +300,34 @@ public class SlideShowView extends DrawableTileBase { } @NbBundle.Messages({"# {0} - file name", - "MediaLoadTask.messageText=Reading media: {0}"}) + "MediaLoadTask.messageText=Reading video: {0}"}) private class MediaLoadTask extends Task { private final VideoFile file; MediaLoadTask(VideoFile file) { + updateMessage(Bundle.MediaLoadTask_messageText(file.getName())); this.file = file; } @Override protected Node call() throws Exception { - updateMessage(Bundle.MediaLoadTask_messageText(file.getName())); try { final Media media = file.getMedia(); return new VideoPlayer(new MediaPlayer(media), file); } catch (MediaException | IOException | OutOfMemoryError ex) { - try { - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to initialize VideoPlayer for file {0} : {1}", new Object[]{file.getUniquePath(), ex.getLocalizedMessage()}); - } catch (TskCoreException tskCoreException) { - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to get unique path " + file.getName(), tskCoreException); - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, "Failed to initialize VideoPlayer for file {0} : {1}", new Object[]{file.getName(), ex.getLocalizedMessage()}); - } + logError("Failed to initialize VideoPlayer for {0} : " + ex.toString()); + return doReadImageTask(file); + } + } - - return doReadImageTask(file); - } + private void logError(final String message) { + try { + Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, message, file.getUniquePath()); + } catch (TskCoreException tskCoreException) { + Logger.getLogger(VideoFile.class.getName()).log(Level.SEVERE, "Failed to get unique path for " + file.getName(), tskCoreException); + Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, message, file.getName()); + } } } } From 85759dc7863752debec30578f9e8c24b93b6b6eb Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Dec 2015 14:19:30 -0500 Subject: [PATCH 09/24] introduce logContentError; abstract basic image meta data reading; --- .../autopsy/coreutils/ImageUtils.java | 122 +++++++++++------- .../imagegallery/datamodel/ImageFile.java | 16 --- .../gui/drawableviews/DrawableUIBase.java | 4 +- .../gui/drawableviews/SlideShowView.java | 13 +- 4 files changed, 79 insertions(+), 76 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 0fdee5a88c..971198d9ed 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -76,6 +76,11 @@ public class ImageUtils { private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName()); + private static final String NO_IMAGE_READER_FOUND_FOR = "No ImageReader found for "; + private static final String IMAGE_IO_COULD_NOT_DETERMINE_WIDTH = "ImageIO could not determine width of {0}: "; + private static final String IMAGE_IO_COULD_NOT_DETERMINE_HEIGHT = "ImageIO could not determine height of {0}: "; + private static final String COULD_NOT_CREATE_IMAGE_INPUT_STREAM = "Could not create ImageInputStream."; + /** * save thumbnails to disk as this format */ @@ -575,21 +580,59 @@ public class ImageUtils { } static public int getWidth(AbstractFile file) throws IIOException, IOException { + return getIntProperty(file, + IMAGE_IO_COULD_NOT_DETERMINE_WIDTH, + imageReader -> imageReader.getWidth(0) + ); + } + static public int getHeight(AbstractFile file) throws IIOException, IOException { + return getIntProperty(file, + IMAGE_IO_COULD_NOT_DETERMINE_HEIGHT, + imageReader -> imageReader.getHeight(0) + ); + } + + private interface PropertyExctractor { + + public T extract(ImageReader reader) throws IOException; + } + + /** + * + * @param file the value of file + * @param errorTemplate the value of errorTemplate + * + * @return the int + * + * @throws IIOException + * @throws IOException + */ + private static int getIntProperty(AbstractFile file, final String errorTemplate, PropertyExctractor propertyExtractor) throws IOException { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { if (input == null) { - throw new IIOException("Could not create ImageInputStream."); //NOI18N + IIOException iioException = new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM); + ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + iioException.toString(), file); + throw iioException; } Iterator readers = ImageIO.getImageReaders(input); if (readers.hasNext()) { ImageReader reader = readers.next(); reader.setInput(input); - return reader.getWidth(0); + try { + return propertyExtractor.extract(reader); + } catch (IOException ex) { + ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + ex.toString(), file); + throw ex; + } } else { - throw newImageReaderException(file); + IIOException iioException = newImageReaderException(file); + ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + iioException.toString(), file); + + throw iioException; } } } @@ -597,31 +640,9 @@ public class ImageUtils { private static IIOException newImageReaderException(AbstractFile file) { try { - return new IIOException("No ImageReader found for file." + file.getUniquePath()); //NOI18N + return new IIOException(NO_IMAGE_READER_FOUND_FOR + file.getUniquePath()); } catch (TskCoreException ex) { - return new IIOException("No ImageReader found for file." + file.getName()); //NOI18N - } - } - - static public int getHeight(AbstractFile file) throws IIOException, IOException { - try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { - - try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { - if (input == null) { - throw new IIOException("Could not create ImageInputStream."); //NOI18N - } - Iterator readers = ImageIO.getImageReaders(input); - - if (readers.hasNext()) { - ImageReader reader = readers.next(); - reader.setInput(input); - - return reader.getHeight(0); - } else { - throw newImageReaderException(file); - } - } - + return new IIOException(NO_IMAGE_READER_FOUND_FOR + file.getName()); //NOI18N } } @@ -652,7 +673,7 @@ public class ImageUtils { return SwingFXUtils.toFXImage(cachedThumbnail, null); } } catch (Exception ex) { - logError("ImageIO had a problem reading thumbnail for image {0}: " + ex.toString()); //NON-NLS + logContentError(logger, Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), file); } } @@ -676,7 +697,7 @@ public class ImageUtils { thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); } catch (IllegalArgumentException | OutOfMemoryError e) { // if resizing does not work due to extreme aspect ratio, crop the image instead. - logError("Could not scale image {0}: " + e.toString() + ". Attemptying to crop {0} instead"); //NON-NLS + logContentError(logger, Level.WARNING, "Could not scale image {0}: " + e.toString() + ". Attemptying to crop {0} instead", file); final int height = bufferedImage.getHeight(); final int width = bufferedImage.getWidth(); @@ -687,12 +708,12 @@ public class ImageUtils { try { thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight); } catch (Exception cropException) { - logError("Could not crop image {0}: " + cropException.toString()); //NON-NLS + logContentError(logger, Level.WARNING, "Could not crop image {0}: " + cropException.toString(), file); throw cropException; } } } catch (Exception e) { - logError("Could not scale image {0}: " + e.toString()); //NON-NLS + logContentError(logger, Level.WARNING, "Could not scale image {0}: " + e.toString(), file); throw e; } } @@ -712,7 +733,7 @@ public class ImageUtils { } ImageIO.write(thumbnail, FORMAT, cacheFile); } catch (IllegalArgumentException | IOException ex) { - logError("Could not write thumbnail for {0}: " + ex.toString()); + logContentError(logger, Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), file); } }); } @@ -766,7 +787,7 @@ public class ImageUtils { try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { if (input == null) { - throw new IIOException("Could not create ImageInputStream."); //NOI18N + throw new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM); } Iterator readers = ImageIO.getImageReaders(input); @@ -791,13 +812,14 @@ public class ImageUtils { } } catch (IOException iOException) { // Ignore this exception or display a warning or similar, for exceptions happening during decoding - logError("ImageIO could not read {0}. It may be unsupported or corrupt: " + iOException.toString()); + logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt: " + iOException.toString(), file); } reader.removeIIOReadProgressListener(this); reader.dispose(); return SwingFXUtils.toFXImage(bufferedImage, null); } else { - throw new IIOException("No ImageReader found for file."); //NOI18N + throw newImageReaderException(file); + } } } @@ -813,26 +835,17 @@ public class ImageUtils { } } - public void logError(String template) { - try { - LOGGER.log(Level.WARNING, template, file.getUniquePath()); //NOI18N - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, template, file.getName()); //NOI18N - LOGGER.log(Level.SEVERE, "Failed to get unique path for file: " + file.getName(), tskCoreException); //NOI18N - } - } - @Override protected void succeeded() { super.succeeded(); try { javafx.scene.image.Image fxImage = get(); if (fxImage == null) { - logError("ImageIO could not read {0}. It may be unsupported or corrupt"); + logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt", file); } else { if (fxImage.isError()) { //if there was somekind of error, log it - logError("ImageIO could not read {0}. It may be unsupported or corrupt:" + ObjectUtils.toString(fxImage.getException())); + logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt:" + ObjectUtils.toString(fxImage.getException()), file); } } } catch (InterruptedException | ExecutionException ex) { @@ -843,7 +856,7 @@ public class ImageUtils { @Override protected void failed() { super.failed(); - logError("ImageIO could not read {0}. It may be unsupported or corrupt: " + ObjectUtils.toString(getException())); + logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt: " + ObjectUtils.toString(getException()), file); } @Override @@ -879,4 +892,19 @@ public class ImageUtils { public void readAborted(ImageReader source) { } } + + /** + * @param logger the value of logger + * @param template the value of template + * @param file the value of file + * @param level the value of level + */ + public static void logContentError(Logger logger, final Level level, final String template, Content file) { + try { + logger.log(level, template, file.getUniquePath()); + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Failed to get unique path for " + file.getName(), tskCoreException); + logger.log(level, template, file.getName()); + } + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index 071dc730ed..0c742da1f6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.concurrent.ExecutionException; -import java.util.logging.Level; import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.scene.image.Image; @@ -29,7 +28,6 @@ import javax.imageio.ImageIO; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskCoreException; /** * ImageGallery data model object that represents an image file. It is a @@ -49,9 +47,6 @@ public class ImageFile extends DrawableFile { } - - - @Override public Task getReadFullSizeImageTask() { Image image = (imageRef != null) ? imageRef.get() : null; @@ -87,7 +82,6 @@ public class ImageFile extends DrawableFile { try { return (double) ImageUtils.getWidth(this.getAbstractFile()); } catch (IOException ex) { - logError("ImageIO could not determine width of {0}: {1}", ex.toString()); return -1.0; } } @@ -97,20 +91,10 @@ public class ImageFile extends DrawableFile { try { return (double) ImageUtils.getHeight(this.getAbstractFile()); } catch (IOException ex) { - logError("ImageIO could not determine height of {0}: {1}", ex.toString()); return -1.0; } } - private void logError(final String message, String exceptionString) { - try { - LOGGER.log(Level.WARNING, message, new Object[]{this.getUniquePath(), exceptionString}); - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.SEVERE, "Failed to get unique path for " + this.getName(), tskCoreException); - LOGGER.log(Level.WARNING, message, new Object[]{this.getName(), exceptionString}); - } - } - @Override public boolean isVideo() { return false; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 1cb4b0c34c..6cbc45c406 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -60,9 +60,9 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); @FXML - private BorderPane imageBorder; + BorderPane imageBorder; @FXML - private ImageView imageView; + ImageView imageView; private final ImageGalleryController controller; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index 505ddba571..9b2013c01c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -45,6 +45,7 @@ import javafx.scene.media.MediaException; import javafx.scene.media.MediaPlayer; import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; @@ -56,7 +57,6 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableUIBase.exec; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH; -import org.sleuthkit.datamodel.TskCoreException; /** * Displays the files of a group one at a time. Designed to be embedded in a @@ -316,18 +316,9 @@ public class SlideShowView extends DrawableTileBase { final Media media = file.getMedia(); return new VideoPlayer(new MediaPlayer(media), file); } catch (MediaException | IOException | OutOfMemoryError ex) { - logError("Failed to initialize VideoPlayer for {0} : " + ex.toString()); + ImageUtils.logContentError(LOGGER, Level.WARNING, "Failed to initialize VideoPlayer for {0} : " + ex.toString(), file); return doReadImageTask(file); } } - - private void logError(final String message) { - try { - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, message, file.getUniquePath()); - } catch (TskCoreException tskCoreException) { - Logger.getLogger(VideoFile.class.getName()).log(Level.SEVERE, "Failed to get unique path for " + file.getName(), tskCoreException); - Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, message, file.getName()); - } - } } } From 8bb612ab2c92a85b7d46ce2dd01f7abc8b733d39 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Dec 2015 14:28:46 -0500 Subject: [PATCH 10/24] fix possible null pointer exception, minor cleanup --- Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java | 4 ++-- .../imagegallery/gui/drawableviews/DrawableUIBase.java | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 971198d9ed..2b01030ca0 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -275,7 +275,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); + logContentError(logger, Level.WARNING, "Failed to look up mimetype for {0} using FileTypeDetector: " + ex.toString() + "\nFallingback on AbstractFile.isMimeType", file); AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(supportedMimeTypes); if (mimeMatch == AbstractFile.MimeMatchEnum.TRUE) { @@ -608,7 +608,7 @@ public class ImageUtils { * @throws IIOException * @throws IOException */ - private static int getIntProperty(AbstractFile file, final String errorTemplate, PropertyExctractor propertyExtractor) throws IOException { + private static T getIntProperty(AbstractFile file, final String errorTemplate, PropertyExctractor propertyExtractor) throws IOException { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 6cbc45c406..2011f965fe 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -137,15 +137,16 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView synchronized Node doReadImageTask(DrawableFile file) { disposeContent(); - imageTask = newReadImageTask(file); - Node progressNode = newProgressIndicator(imageTask); + Task myTask = newReadImageTask(file); + imageTask = myTask; + Node progressNode = newProgressIndicator(myTask); Platform.runLater(() -> imageBorder.setCenter(progressNode)); //called on fx thread - imageTask.setOnSucceeded(succeeded -> showImage(file, imageTask)); + imageTask.setOnSucceeded(succeeded -> showImage(file, myTask)); imageTask.setOnFailed(failed -> showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file)); - exec.execute(imageTask); + exec.execute(myTask); return progressNode; } From 8f32064c2d8ad7b763d9f416f3a4edb44130d44f Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Dec 2015 15:45:51 -0500 Subject: [PATCH 11/24] improve disposal of resources --- .../autopsy/coreutils/ImageUtils.java | 24 +++-- .../autopsy/imagegallery/ThumbnailCache.java | 18 ++-- .../gui/drawableviews/DrawableUIBase.java | 91 +++++-------------- .../gui/drawableviews/MetaDataPane.java | 20 ++-- .../gui/drawableviews/SlideShowView.java | 71 ++++++++------- 5 files changed, 95 insertions(+), 129 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 2b01030ca0..207596a080 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -623,10 +623,13 @@ public class ImageUtils { ImageReader reader = readers.next(); reader.setInput(input); try { + return propertyExtractor.extract(reader); } catch (IOException ex) { ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + ex.toString(), file); throw ex; + } finally { + reader.dispose(); } } else { IIOException iioException = newImageReaderException(file); @@ -672,7 +675,7 @@ public class ImageUtils { if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) { return SwingFXUtils.toFXImage(cachedThumbnail, null); } - } catch (Exception ex) { + } catch (IOException ex) { logContentError(logger, Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), file); } } @@ -747,6 +750,7 @@ public class ImageUtils { ReadImageTask(AbstractFile file) { super(file); + updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); } @Override @@ -754,7 +758,7 @@ public class ImageUtils { "# {0} - file name", "LoadImageTask.mesageText=Reading image: {0}"}) protected javafx.scene.image.Image call() throws Exception { - updateMessage(Bundle.LoadImageTask_mesageText(file.getName())); + return readImage(); } } @@ -762,18 +766,12 @@ public class ImageUtils { static private abstract class ReadImageTaskBase extends Task implements IIOReadProgressListener { final AbstractFile file; - private volatile BufferedImage bufferedImage = null; private ImageReader reader; ReadImageTaskBase(AbstractFile file) { this.file = file; } - public BufferedImage getBufferedImage() throws InterruptedException, ExecutionException { - get(); - return bufferedImage; - } - protected javafx.scene.image.Image readImage() throws IOException { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { if (ImageUtils.isGIF(file)) { @@ -803,23 +801,23 @@ public class ImageUtils { */ ImageReadParam param = reader.getDefaultReadParam(); - bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); + BufferedImage bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); param.setDestination(bufferedImage); try { - reader.read(0, param); + bufferedImage = reader.read(0, param); if (isCancelled()) { return null; } } catch (IOException iOException) { // Ignore this exception or display a warning or similar, for exceptions happening during decoding logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt: " + iOException.toString(), file); + } finally { + reader.removeIIOReadProgressListener(this); + reader.dispose(); } - reader.removeIIOReadProgressListener(this); - reader.dispose(); return SwingFXUtils.toFXImage(bufferedImage, null); } else { throw newImageReaderException(file); - } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index 9223cb564e..a6a116102f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -66,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> cache = CacheBuilder.newBuilder() + private final Cache cache = CacheBuilder.newBuilder() .maximumSize(1000) .softValues() .expireAfterAccess(10, TimeUnit.MINUTES).build(); @@ -99,7 +99,7 @@ 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 thumbnail for file: " + file.getName(), ex.getCause()); return null; @@ -124,12 +124,12 @@ public enum ThumbnailCache { * * @return an (possibly empty) optional containing a thumbnail */ - private Optional 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(cachFile -> { @@ -160,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 } /** @@ -182,12 +182,12 @@ public enum ThumbnailCache { } public Task getThumbnailTask(DrawableFile file) { - final Optional option = cache.getIfPresent(file.getId()); - if (option != null && option.isPresent()) { + final Image thumbnail = cache.getIfPresent(file.getId()); + if (thumbnail != null) { return new Task() { @Override protected Image call() throws Exception { - return option.get(); + return thumbnail; } }; } @@ -196,7 +196,7 @@ public enum ThumbnailCache { switch (newGetThumbnailTask.getState()) { case SUCCEEDED: try { - cache.put(Long.MIN_VALUE, Optional.of(newGetThumbnailTask.get())); + cache.put(Long.MIN_VALUE, newGetThumbnailTask.get()); } catch (InterruptedException | ExecutionException ex) { Exceptions.printStackTrace(ex); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 2011f965fe..5e3b9ae798 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -60,9 +60,9 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); @FXML - BorderPane imageBorder; + BorderPane imageBorder; @FXML - ImageView imageView; + ImageView imageView; private final ImageGalleryController controller; @@ -118,33 +118,41 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView synchronized public void setFile(Long newFileID) { if (getFileID().isPresent()) { if (Objects.equals(newFileID, getFileID().get()) == false) { -// if (Objects.nonNull(newFileID)) { setFileHelper(newFileID); -// } } - } else {//if (Objects.nonNull(newFileID)) { + } else { setFileHelper(newFileID); } } synchronized protected void updateContent() { - if (getFile().isPresent() == false) { - Platform.runLater(() -> imageBorder.setCenter(null)); - } else { + if (getFile().isPresent()) { doReadImageTask(getFile().get()); } } synchronized Node doReadImageTask(DrawableFile file) { - disposeContent(); Task myTask = newReadImageTask(file); imageTask = myTask; Node progressNode = newProgressIndicator(myTask); Platform.runLater(() -> imageBorder.setCenter(progressNode)); //called on fx thread - imageTask.setOnSucceeded(succeeded -> showImage(file, myTask)); - imageTask.setOnFailed(failed -> showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file)); + 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; @@ -155,8 +163,10 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView imageTask.cancel(); } imageTask = null; - Platform.runLater(() -> imageView.setImage(null)); - + Platform.runLater(() -> { + imageView.setImage(null); + imageBorder.setCenter(null); + }); } /** @@ -171,7 +181,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView } @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - void showImage(DrawableFile file, Task imageTask) { + private void showImage(DrawableFile file, Task imageTask) { //Note that all error conditions are allready logged in readImageTask.succeeded() try { Image fxImage = imageTask.get(); @@ -201,57 +211,4 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView } abstract Task newReadImageTask(DrawableFile file); - - abstract class CachedLoaderTask> extends Task { - - 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() throws Exception; - - @Override - protected void succeeded() { - super.succeeded(); - if (isCancelled() == false) { - try { - saveToCache(get()); - } 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); - } - -// class ThumbnailLoaderTask extends CachedLoaderTask> { -// -// ThumbnailLoaderTask(DrawableFile file) { -// super(file); -// } -// -// @Override -// Image load() { -// return isCancelled() ? null : file.getThumbnail(); -// } -// -// @Override -// void saveToCache(Image result) { -//// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. -// } -// } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index 8395b4ab8c..3b672ebf49 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -169,21 +169,23 @@ public class MetaDataPane extends DrawableUIBase { @Override 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(); + disposeContent(); + if (nonNull(newFileID)) { updateAttributesTable(); updateCategory(); updateContent(); } } + @Override + protected synchronized void disposeContent() { + super.disposeContent(); + Platform.runLater(() -> { + tableView.getItems().clear(); + getCategoryBorderRegion().setBorder(null); + }); + } + @Override Task newReadImageTask(DrawableFile file) { return file.getThumbnailTask(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index 9b2013c01c..5440f49ad2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -19,8 +19,6 @@ 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; @@ -76,6 +74,8 @@ public class SlideShowView extends DrawableTileBase { @FXML private BorderPane footer; + private volatile MediaLoadTask mediaTask; + SlideShowView(GroupPane gp, ImageGalleryController controller) { super(gp, controller); FXMLConstructor.construct(this, "SlideShowView.fxml"); @@ -160,49 +160,58 @@ public class SlideShowView extends DrawableTileBase { } @Override - protected void disposeContent() { + synchronized protected void disposeContent() { stopVideo(); + if (mediaTask != null) { + mediaTask.cancel(true); + } + mediaTask = null; super.disposeContent(); -// if (mediaTask != null) { -// mediaTask.cancel(true); -// } -// mediaTask = null; - mediaCache = null; } - private SoftReference mediaCache; @Override synchronized protected void updateContent() { - if (getFile().isPresent() == false) { - mediaCache = null; - Platform.runLater(() -> imageBorder.setCenter(null)); - - } else { + disposeContent(); + if (getFile().isPresent()) { DrawableFile file = getFile().get(); if (file.isVideo()) { - //specially handling for videos - Node mediaNode = (isNull(mediaCache)) ? null : mediaCache.get(); - if (nonNull(mediaNode)) { - Platform.runLater(() -> imageBorder.setCenter(mediaNode)); - } else { - - MediaLoadTask mediaTask = new MediaLoadTask(((VideoFile) file)); - Node progressNode = newProgressIndicator(mediaTask); - Platform.runLater(() -> imageBorder.setCenter(progressNode)); - - //called on fx thread - mediaTask.setOnSucceeded(succedded -> showMedia(file, mediaTask)); - mediaTask.setOnFailed(failed -> showErrorNode(getMediaLoadErrorLabel(mediaTask), file)); - - exec.execute(mediaTask); - } + doMediaLoadTask((VideoFile) file); } else { - super.updateContent(); + doReadImageTask(file); } } } + 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 mediaTask) { //Note that all error conditions are allready logged in readImageTask.succeeded() From c072270627ea99af326cb84d57cd3b1638cc95d1 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 31 Dec 2015 14:44:49 -0500 Subject: [PATCH 12/24] revert unrelated changes --- .../autopsy/timeline/ui/detailview/DetailViewPane.java | 1 - .../autopsy/imagegallery/gui/drawableviews/GroupPane.java | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index f3b19a3df8..efad107ba2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -391,7 +391,6 @@ public class DetailViewPane extends AbstractVisualizationPane { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 7bc52d4c5d..0f25e60d30 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -774,10 +774,12 @@ public class GroupPane extends BorderPane { @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); + tile.setFile(item); } void resetItem() { +// updateItem(null, true); tile.setFile(null); } } From a96124952c0cd4cce94ff0e84647439031ced5c2 Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Mon, 4 Jan 2016 11:54:41 -0500 Subject: [PATCH 13/24] insert comment --- .../modules/photoreccarver/PhotoRecCarverFileIngestModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index c93182bba9..814b76ee18 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -389,7 +389,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { if (false == FileUtil.arePermissionsAppropriate(path)) { throw new IngestModule.IngestModuleException( NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficient") - + SEP + path.toString() + SEP + + SEP + path.toString() + SEP // SEP breaks lines to the dialog displays nicely. + NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficientSeeReference")); } } From 3ce23e8b1dc950ec9247108d71d8ed02433db1db Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Mon, 4 Jan 2016 11:55:47 -0500 Subject: [PATCH 14/24] insert comment --- .../modules/photoreccarver/PhotoRecCarverFileIngestModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index 814b76ee18..aa0e158794 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -389,7 +389,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { if (false == FileUtil.arePermissionsAppropriate(path)) { throw new IngestModule.IngestModuleException( NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficient") - + SEP + path.toString() + SEP // SEP breaks lines to the dialog displays nicely. + + SEP + path.toString() + SEP // SEP is line breaks to make the dialog display nicely. + NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.PermissionsNotSufficientSeeReference")); } } From a69ba481968156a032c83a07968a88962cf613c3 Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Mon, 4 Jan 2016 11:57:03 -0500 Subject: [PATCH 15/24] rename method --- Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java | 2 +- .../modules/photoreccarver/PhotoRecCarverFileIngestModule.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java index 2725a22135..29cbd1c4b5 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java @@ -181,7 +181,7 @@ public class FileUtil { * * @return True if we have both read and write access, false otherwise. */ - public static boolean arePermissionsAppropriate(Path path) { + public static boolean hasReadWriteAccess(Path path) { Path p = null; try { p = Files.createTempFile(path, TEMP_FILE_PREFIX, null); diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index aa0e158794..bb8d8ed62f 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -386,7 +386,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { if (path == null) { throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.nonHostnameUNCPathUsed")); } - if (false == FileUtil.arePermissionsAppropriate(path)) { + 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. From 960a2b5b61f266c38a1a83b59839bab0efb66348 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 4 Jan 2016 14:23:50 -0500 Subject: [PATCH 16/24] comments, and cleanup --- .../autopsy/coreutils/ImageUtils.java | 334 ++++++++++++------ .../imagegallery/datamodel/ImageFile.java | 4 +- 2 files changed, 220 insertions(+), 118 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 207596a080..941a3f1609 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -1,5 +1,4 @@ /* - * * Autopsy Forensic Browser * * Copyright 2012-15 Basis Technology Corp. @@ -69,36 +68,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 NO_IMAGE_READER_FOUND_FOR = "No ImageReader found for "; - private static final String IMAGE_IO_COULD_NOT_DETERMINE_WIDTH = "ImageIO could not determine width of {0}: "; - private static final String IMAGE_IO_COULD_NOT_DETERMINE_HEIGHT = "ImageIO could not determine height of {0}: "; - private static final String COULD_NOT_CREATE_IMAGE_INPUT_STREAM = "Could not create ImageInputStream."; + 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 GIF_MIME_SET = ImmutableSortedSet.copyOf(new String[]{IMAGE_GIF_MIME}); private static final List SUPPORTED_IMAGE_EXTENSIONS; private static final SortedSet SUPPORTED_IMAGE_MIME_TYPES; - private static final List CONDITIONAL_MIME_TYPES = Arrays.asList("audio/x-aiff", "application/octet-stream"); + private static final List CONDITIONAL_MIME_TYPES = Arrays.asList("audio/x-aiff", "application/octet-stream"); //NOI18N private static final boolean openCVLoaded; @@ -106,9 +103,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; @@ -117,16 +114,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 } @@ -144,8 +141,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 } /** @@ -158,7 +155,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 getSupportedImageExtensions() { return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS); @@ -194,10 +191,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) { @@ -214,12 +213,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(); @@ -228,16 +241,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; @@ -245,7 +258,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 @@ -260,8 +273,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 supportedMimeTypes, final List supportedExtension, List conditionalMimes) { if (file.getSize() == 0) { @@ -275,8 +288,7 @@ public class ImageUtils { || (conditionalMimes.contains(mimeType.toLowerCase()) && supportedExtension.contains(extension)); } } catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) { - logContentError(logger, Level.WARNING, "Failed to look up mimetype for {0} using FileTypeDetector: " + ex.toString() + "\nFallingback on AbstractFile.isMimeType", file); - + 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; @@ -304,29 +316,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 @@ -348,7 +358,7 @@ public class ImageUtils { return thumbnail; } } catch (Exception ex) { - LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{content.getName(), ex.getLocalizedMessage()}); //NON-NLS + LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{content.getName(), ex.getLocalizedMessage()}); //NON-NLS //NOI18N return generateAndSaveThumbnail(file, iconSize, cacheFile); } } else { @@ -360,8 +370,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 @@ -382,8 +392,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 @@ -405,7 +415,6 @@ public class ImageUtils { * * @return * - * * @deprecated use {@link #getCachedThumbnailLocation(long) } instead */ @Deprecated @@ -415,19 +424,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); } @@ -492,7 +508,7 @@ 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; } @@ -501,7 +517,7 @@ public class ImageUtils { * Generate an icon and save it to specified location. * * @param file File to generate icon for - * @param iconSize + * @param iconSize size in pixels of the thumbnail * @param cacheFile Location to save thumbnail to * * @return Generated icon or null on error @@ -532,18 +548,17 @@ public class ImageUtils { } ImageIO.write(toSave, FORMAT, cacheFile); } catch (IllegalArgumentException | IOException ex1) { - LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + file, ex1); //NON-NLS + LOGGER.log(Level.WARNING, COULD_NOT_WRITE_CACHE_THUMBNAIL + file, ex1); } }); } } catch (NullPointerException ex) { - logger.log(Level.WARNING, "Could not write cache thumbnail: " + file, ex); //NON-NLS + LOGGER.log(Level.WARNING, COULD_NOT_WRITE_CACHE_THUMBNAIL + file, ex); } return thumbnail; } /** - * * Generate and return a scaled image * * @param content @@ -572,49 +587,89 @@ public class ImageUtils { return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight())); } } catch (OutOfMemoryError e) { - LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName() + ": " + e.toString()); //NON-NLS + LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName() + ": " + e.toString()); //NON-NLS //NOI18N } catch (Exception e) { - LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.toString()); //NON-NLS + LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.toString()); //NON-NLS //NOI18N } return null; } - static public int getWidth(AbstractFile file) throws IIOException, IOException { - return getIntProperty(file, - IMAGE_IO_COULD_NOT_DETERMINE_WIDTH, + /** + * Get the width of the given image, in pixels. + * + * @param file + * + * @return the width in pixels + * + * @throws IOException If the file is not a supported image or the width + * could not be determined. + */ + static public int getImageWidth(AbstractFile file) throws IOException { + return getImageProperty(file, + "ImageIO could not determine width of {0}: ", //NOI18N imageReader -> imageReader.getWidth(0) ); } - static public int getHeight(AbstractFile file) throws IIOException, IOException { - return getIntProperty(file, - IMAGE_IO_COULD_NOT_DETERMINE_HEIGHT, + /** + * Get the height of the given image,in pixels. + * + * @param file + * + * @return the height in pixels + * + * @throws IOException If the file is not a supported image or the height + * could not be determined. + */ + static public int getImageHeight(AbstractFile file) throws IOException { + return getImageProperty(file, + "ImageIO could not determine height of {0}: ", //NOI18N imageReader -> imageReader.getHeight(0) ); } - private interface PropertyExctractor { + /** + * 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 The type of the property. + */ + @FunctionalInterface + private static interface PropertyExtractor { 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 file the value of file - * @param errorTemplate the value of errorTemplate + * @param 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 int + * @return the the value of the property extracted by the given + * propertyExtractor * - * @throws IIOException - * @throws IOException + * @throws IOException if there was a problem reading the property from the + * file. + * + * @see PropertyExtractor + * @see #getImageHeight(org.sleuthkit.datamodel.AbstractFile) */ - private static T getIntProperty(AbstractFile file, final String errorTemplate, PropertyExctractor propertyExtractor) throws IOException { + private static T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor 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); - ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + iioException.toString(), file); + LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); throw iioException; } Iterator readers = ImageIO.getImageReaders(input); @@ -626,14 +681,14 @@ public class ImageUtils { return propertyExtractor.extract(reader); } catch (IOException ex) { - ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + ex.toString(), file); + LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file)); throw ex; } finally { reader.dispose(); } } else { - IIOException iioException = newImageReaderException(file); - ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + iioException.toString(), file); + IIOException iioException = new IIOException(NO_IMAGE_READER_FOUND_FOR_ + getContentPathSafe(file)); + LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); throw iioException; } @@ -641,33 +696,47 @@ public class ImageUtils { } } - private static IIOException newImageReaderException(AbstractFile file) { - try { - return new IIOException(NO_IMAGE_READER_FOUND_FOR + file.getUniquePath()); - } catch (TskCoreException ex) { - return new IIOException(NO_IMAGE_READER_FOUND_FOR + file.getName()); //NOI18N - } - } - + /** + * 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 newGetThumbnailTask(AbstractFile file, int iconSize) { - return new GetOrGenerateThumbnailTask(file, iconSize); + return new GetThumbnailTask(file, iconSize); } - static private class GetOrGenerateThumbnailTask extends ReadImageTaskBase { + /** + * 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 GetOrGenerateThumbnailTask(AbstractFile file, int iconSize) { + @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) { super(file); - updateMessage("Loading thumbnail for " + file.getName()); + updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName())); this.iconSize = iconSize; 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 { @@ -676,31 +745,35 @@ public class ImageUtils { return SwingFXUtils.toFXImage(cachedThumbnail, null); } } catch (IOException ex) { - logContentError(logger, Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), file); + 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("Generating preview for " + file.getName()); + updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName())); thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); } else { thumbnail = DEFAULT_THUMBNAIL; } + } else { + //read the image into abuffered image. BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null); - if (isNull(bufferedImage)) { - LOGGER.log(Level.WARNING, "Failed to read image for thumbnail generation."); - throw new IIOException("Failed to read image for thumbnail generation."); + 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, crop the image instead. - logContentError(logger, Level.WARNING, "Could not scale image {0}: " + e.toString() + ". Attemptying to crop {0} instead", file); + // 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(); @@ -711,22 +784,29 @@ public class ImageUtils { try { thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight); } catch (Exception cropException) { - logContentError(logger, Level.WARNING, "Could not crop image {0}: " + cropException.toString(), file); + LOGGER.log(Level.WARNING, "Could not crop image {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N throw cropException; } } } catch (Exception e) { - logContentError(logger, Level.WARNING, "Could not scale image {0}: " + e.toString(), file); + 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 { @@ -736,16 +816,31 @@ public class ImageUtils { } ImageIO.write(thumbnail, FORMAT, cacheFile); } catch (IllegalArgumentException | IOException ex) { - logContentError(logger, Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), file); + 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 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) { @@ -758,13 +853,16 @@ public class ImageUtils { "# {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 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; @@ -775,12 +873,12 @@ public class ImageUtils { protected javafx.scene.image.Image readImage() throws IOException { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { if (ImageUtils.isGIF(file)) { - //directly read GIF to preserve potential animation, + //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 iamge reading code if there was an error + //fall through to default image reading code if there was an error } try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { @@ -789,6 +887,7 @@ public class ImageUtils { } Iterator 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); @@ -804,20 +903,20 @@ public class ImageUtils { BufferedImage bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); param.setDestination(bufferedImage); try { - bufferedImage = reader.read(0, param); + 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 - logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt: " + iOException.toString(), file); + 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 newImageReaderException(file); + throw new IIOException(NO_IMAGE_READER_FOUND_FOR_ + ImageUtils.getContentPathSafe(file)); } } } @@ -839,11 +938,11 @@ public class ImageUtils { try { javafx.scene.image.Image fxImage = get(); if (fxImage == null) { - logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt", file); + 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 - logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt:" + ObjectUtils.toString(fxImage.getException()), file); + LOGGER.log(Level.WARNING, IMAGE_IO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file)); } } } catch (InterruptedException | ExecutionException ex) { @@ -854,7 +953,7 @@ public class ImageUtils { @Override protected void failed() { super.failed(); - logContentError(logger, Level.WARNING, "ImageIO could not read {0}. It may be unsupported or corrupt: " + ObjectUtils.toString(getException()), file); + LOGGER.log(Level.WARNING, IMAGE_IO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file)); } @Override @@ -892,17 +991,20 @@ public class ImageUtils { } /** - * @param logger the value of logger - * @param template the value of template - * @param file the value of file - * @param level the value of level + * Get the unique path for the content, or if that fails, just return the + * name. + * + * @param content + * + * @return */ - public static void logContentError(Logger logger, final Level level, final String template, Content file) { + private static String getContentPathSafe(Content content) { try { - logger.log(level, template, file.getUniquePath()); + return content.getUniquePath(); } catch (TskCoreException tskCoreException) { - logger.log(Level.SEVERE, "Failed to get unique path for " + file.getName(), tskCoreException); - logger.log(level, template, file.getName()); + String contentName = content.getName(); + LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N + return contentName; } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index 0c742da1f6..a6c34647b7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -80,7 +80,7 @@ public class ImageFile extends DrawableFile { @Override Double getWidth() { try { - return (double) ImageUtils.getWidth(this.getAbstractFile()); + return (double) ImageUtils.getImageWidth(this.getAbstractFile()); } catch (IOException ex) { return -1.0; } @@ -89,7 +89,7 @@ public class ImageFile extends DrawableFile { @Override Double getHeight() { try { - return (double) ImageUtils.getHeight(this.getAbstractFile()); + return (double) ImageUtils.getImageHeight(this.getAbstractFile()); } catch (IOException ex) { return -1.0; } From 50e06fe4aa7923164a1fca661face02fc2a0f644 Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Mon, 4 Jan 2016 15:01:03 -0500 Subject: [PATCH 17/24] remove unused imports --- Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java index 29cbd1c4b5..b1d1c33c51 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java @@ -22,9 +22,6 @@ import java.io.File; import java.io.IOException; import java.util.logging.Level; import org.openide.filesystems.FileObject; -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.FileWriter; import java.nio.file.Files; import java.nio.file.Path; From b78b6ec34574bde0af902e3dc6c04edb24c4715e Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 4 Jan 2016 15:26:12 -0500 Subject: [PATCH 18/24] replace usage of removed ImageUtils.logContentError in IG --- .../imagegallery/datamodel/DrawableFile.java | 18 ++++++++++++++++++ .../gui/drawableviews/SlideShowView.java | 3 +-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 9a52245bca..0f8db63ae3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -322,4 +322,22 @@ public abstract class DrawableFile 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; + } + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index 5440f49ad2..1e8c98117f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -43,7 +43,6 @@ import javafx.scene.media.MediaException; import javafx.scene.media.MediaPlayer; import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; @@ -325,7 +324,7 @@ public class SlideShowView extends DrawableTileBase { final Media media = file.getMedia(); return new VideoPlayer(new MediaPlayer(media), file); } catch (MediaException | IOException | OutOfMemoryError ex) { - ImageUtils.logContentError(LOGGER, Level.WARNING, "Failed to initialize VideoPlayer for {0} : " + ex.toString(), file); + LOGGER.log(Level.WARNING, "Failed to initialize VideoPlayer for {0} : " + ex.toString(), file.getContentPathSafe()); return doReadImageTask(file); } } From c3f7cddca2a89f5f22457fb138957f8d52746750 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 31 Dec 2015 15:55:45 -0500 Subject: [PATCH 19/24] ImageUtils.getThumbnail delegates to new image loading task. This is used by ThumbnailViewNode to read thumbnails --- .../autopsy/coreutils/ImageUtils.java | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 941a3f1609..3bb2b5970d 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -347,22 +347,14 @@ public class ImageUtils { 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, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{content.getName(), ex.getLocalizedMessage()}); //NON-NLS //NOI18N - return generateAndSaveThumbnail(file, iconSize, cacheFile); - } - } else { - return generateAndSaveThumbnail(file, iconSize, cacheFile); + + Task thumbnailTask = newGetThumbnailTask(file, iconSize); + thumbnailTask.run(); + try { + return SwingFXUtils.fromFXImage(thumbnailTask.get(), null); + } catch (InterruptedException | ExecutionException ex) { + logContentError(logger, Level.WARNING, "Failed to get thumbnail for {0}: " + ex.toString(), content); + return DEFAULT_THUMBNAIL; } } else { return DEFAULT_THUMBNAIL; From 2d29d55329c88db8101845f7f4f161a9f3370392 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 31 Dec 2015 16:43:10 -0500 Subject: [PATCH 20/24] add option to return default humbnail to task; pull getReadFullSizeImageTask into DrawableFile using template method pattern; create TaskUtils class to create tasks from callables/thunks --- .../autopsy/coreutils/ImageUtils.java | 10 ++-- .../autopsy/imagegallery/ThumbnailCache.java | 2 +- .../imagegallery/datamodel/DrawableFile.java | 27 +++++++++- .../imagegallery/datamodel/ImageFile.java | 37 +++----------- .../imagegallery/datamodel/VideoFile.java | 51 ++++--------------- .../autopsy/imagegallery/utils/TaskUtils.java | 40 +++++++++++++++ 6 files changed, 92 insertions(+), 75 deletions(-) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 3bb2b5970d..0f56629717 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -348,7 +348,7 @@ public class ImageUtils { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; - Task thumbnailTask = newGetThumbnailTask(file, iconSize); + Task thumbnailTask = newGetThumbnailTask(file, iconSize, true); thumbnailTask.run(); try { return SwingFXUtils.fromFXImage(thumbnailTask.get(), null); @@ -716,6 +716,7 @@ public class ImageUtils { 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", @@ -724,7 +725,8 @@ public class ImageUtils { super(file); updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName())); this.iconSize = iconSize; - cacheFile = getCachedThumbnailLocation(file.getId()); + this.defaultOnFailure = defaultOnFailure; + this.cacheFile = getCachedThumbnailLocation(file.getId()); } @Override @@ -747,8 +749,10 @@ public class ImageUtils { if (openCVLoaded) { updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName())); thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); - } else { + } else if (defaultOnFailure) { thumbnail = DEFAULT_THUMBNAIL; + } else { + throw new IIOException("Failed to read image for thumbnail generation."); } } else { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index a6a116102f..4392f59e2d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -191,7 +191,7 @@ public enum ThumbnailCache { } }; } - final Task newGetThumbnailTask = ImageUtils.newGetThumbnailTask(file.getAbstractFile(), MAX_THUMBNAIL_SIZE); + final Task newGetThumbnailTask = ImageUtils.newGetThumbnailTask(file.getAbstractFile(), MAX_THUMBNAIL_SIZE, false); newGetThumbnailTask.stateProperty().addListener((Observable observable) -> { switch (newGetThumbnailTask.getState()) { case SUCCEEDED: diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 0f8db63ae3..7865f6ca67 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -36,9 +36,11 @@ import javax.annotation.Nonnull; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.ImageUtils; 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; @@ -281,7 +283,30 @@ public abstract class DrawableFile extends AbstractFile } } - public abstract Task getReadFullSizeImageTask(); + public Task getReadFullSizeImageTask() { + Image image = (imageRef != null) ? imageRef.get() : null; + if (image == null || image.isError()) { + Task readImageTask = getReadFullSizeImageTaskHelper(); + readImageTask.stateProperty().addListener(stateProperty -> { + switch (readImageTask.getState()) { + case SUCCEEDED: + try { + imageRef = new SoftReference<>(readImageTask.get()); + } catch (InterruptedException | ExecutionException exception) { + ImageUtils.logContentError(LOGGER, Level.WARNING, getMessageTemplate(exception), this); + } + break; + } + }); + return readImageTask; + } else { + return TaskUtils.taskFrom(() -> image); + } + } + + abstract String getMessageTemplate(Exception exception); + + abstract Task getReadFullSizeImageTaskHelper(); public void setAnalyzed(Boolean analyzed) { this.analyzed.set(analyzed); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index a6c34647b7..4249fe3c24 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -19,9 +19,6 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import java.io.IOException; -import java.lang.ref.SoftReference; -import java.util.concurrent.ExecutionException; -import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.scene.image.Image; import javax.imageio.ImageIO; @@ -48,33 +45,13 @@ public class ImageFile extends DrawableFile { } @Override - public Task getReadFullSizeImageTask() { - Image image = (imageRef != null) ? imageRef.get() : null; - if (image == null || image.isError()) { - final Task newReadImageTask = ImageUtils.newReadImageTask(this.getAbstractFile()); - newReadImageTask.stateProperty().addListener((Observable observable) -> { - switch (newReadImageTask.getState()) { - case CANCELLED: - break; - case FAILED: - break; - case SUCCEEDED: - try { - imageRef = new SoftReference<>(newReadImageTask.get()); - } catch (InterruptedException | ExecutionException interruptedException) { - } - break; - } - }); - return newReadImageTask; - } else { - return new Task() { - @Override - protected Image call() throws Exception { - return image; - } - }; - } + String getMessageTemplate(final Exception exception) { + return "Failed to read image {0}: " + exception.toString(); + } + + @Override + Task getReadFullSizeImageTaskHelper() { + return ImageUtils.newReadImageTask(this.getAbstractFile()); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index 4ba866144c..20724ef0bc 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -19,27 +19,26 @@ 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.concurrent.ExecutionException; -import javafx.beans.Observable; import javafx.concurrent.Task; -import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; import javafx.scene.media.Media; import javafx.scene.media.MediaException; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.VideoUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; public class VideoFile extends DrawableFile { + 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,44 +49,16 @@ public class VideoFile extends DrawableFile { return VIDEO_ICON; } + + @Override - public Task getReadFullSizeImageTask() { - Image image = (imageRef != null) ? imageRef.get() : null; - if (image == null || image.isError()) { - Task newReadImageTask = new Task() { + String getMessageTemplate(final Exception exception) { + return "Failed to get image preview for video {0}: " + exception.toString(); + } - @Override - protected Image call() throws Exception { - final BufferedImage bufferedImage = ImageUtils.getThumbnail(getAbstractFile(), 1024); - return (bufferedImage == ImageUtils.getDefaultThumbnail()) - ? null - : SwingFXUtils.toFXImage(bufferedImage, null); - } - }; - - newReadImageTask.stateProperty().addListener((Observable observable) -> { - switch (newReadImageTask.getState()) { - case CANCELLED: - break; - case FAILED: - break; - case SUCCEEDED: - try { - imageRef = new SoftReference<>(newReadImageTask.get()); - } catch (InterruptedException | ExecutionException interruptedException) { - } - break; - } - }); - return newReadImageTask; - } else { - return new Task() { - @Override - protected Image call() throws Exception { - return image; - } - }; - } + @Override + Task getReadFullSizeImageTaskHelper() { + return ImageUtils.newGetThumbnailTask(getAbstractFile(), 1024, false); } private SoftReference mediaRef; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java new file mode 100644 index 0000000000..7b648b5db7 --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java @@ -0,0 +1,40 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.imagegallery.utils; + +import java.util.concurrent.Callable; +import javafx.concurrent.Task; + +/** + * + */ +public class TaskUtils { + + public static Task taskFrom(Callable callable) { + return new Task() { + @Override + protected T call() throws Exception { + return callable.call(); + } + }; + } + + private TaskUtils() { + } +} From c5ca1d55f328ea211e044fc199eedffa2eed345d Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 4 Jan 2016 14:56:03 -0500 Subject: [PATCH 21/24] fix rebase --- Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 0f56629717..51f4a4c7e5 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -35,7 +35,6 @@ 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; @@ -353,7 +352,7 @@ public class ImageUtils { try { return SwingFXUtils.fromFXImage(thumbnailTask.get(), null); } catch (InterruptedException | ExecutionException ex) { - logContentError(logger, Level.WARNING, "Failed to get thumbnail for {0}: " + ex.toString(), content); + LOGGER.log(Level.WARNING, "Failed to get thumbnail for {0}: " + ex.toString(), getContentPathSafe(content)); return DEFAULT_THUMBNAIL; } } else { @@ -703,8 +702,8 @@ public class ImageUtils { * * @return a new Task that returns a thumbnail as its result. */ - public static Task newGetThumbnailTask(AbstractFile file, int iconSize) { - return new GetThumbnailTask(file, iconSize); + public static Task newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) { + return new GetThumbnailTask(file, iconSize, defaultOnFailure); } /** @@ -721,7 +720,7 @@ public class ImageUtils { @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) { + private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) { super(file); updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName())); this.iconSize = iconSize; From 0d526d09337f46a39932feb640fa6bf787d565ef Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 4 Jan 2016 15:05:02 -0500 Subject: [PATCH 22/24] remove dead code --- .../autopsy/coreutils/ImageUtils.java | 81 ------------------- .../imagegallery/datamodel/DrawableFile.java | 3 +- 2 files changed, 1 insertion(+), 83 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 51f4a4c7e5..fc418a005f 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -504,87 +504,6 @@ public class ImageUtils { return fileHeaderBuffer; } - /** - * Generate an icon and save it to specified location. - * - * @param file File to generate icon for - * @param iconSize size in pixels of the thumbnail - * @param cacheFile Location to save thumbnail to - * - * @return Generated icon or null on error - */ - private static BufferedImage 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); - } - }); - } - } catch (NullPointerException ex) { - LOGGER.log(Level.WARNING, COULD_NOT_WRITE_CACHE_THUMBNAIL + file, ex); - } - return thumbnail; - } - - /** - * Generate and return a scaled image - * - * @param content - * @param iconSize - * - * @return a Thumbnail of the given content at the given size, or null if - * there was a problem. - */ - @Nullable - private static BufferedImage generateImageThumbnail(AbstractFile content, int iconSize) { - - try { - final ReadImageTask readImageTask = new ReadImageTask(content); - - readImageTask.run(); - BufferedImage bi = SwingFXUtils.fromFXImage(readImageTask.get(), null); - - if (bi == null) { - 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.toString()); //NON-NLS //NOI18N - } catch (Exception e) { - LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.toString()); //NON-NLS //NOI18N - } - return null; - } - /** * Get the width of the given image, in pixels. * diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 7865f6ca67..fb08256f83 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -36,7 +36,6 @@ import javax.annotation.Nonnull; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; @@ -293,7 +292,7 @@ public abstract class DrawableFile extends AbstractFile try { imageRef = new SoftReference<>(readImageTask.get()); } catch (InterruptedException | ExecutionException exception) { - ImageUtils.logContentError(LOGGER, Level.WARNING, getMessageTemplate(exception), this); + LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe()); } break; } From fdea3ec2720698d56f29f7976b42ada07a214fdf Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 5 Jan 2016 15:25:19 -0500 Subject: [PATCH 23/24] fix compiler warning about undocumented paramater in bundle message string --- .../sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index efad107ba2..f0ab8a9860 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -391,6 +391,7 @@ public class DetailViewPane extends AbstractVisualizationPane { From 7ac28da37cc386c77b663ed44c60531e9c5af444 Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Tue, 5 Jan 2016 17:16:20 -0500 Subject: [PATCH 24/24] update report link --- docs/doxygen/modIngest.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doxygen/modIngest.dox b/docs/doxygen/modIngest.dox index 4a88c317b0..8584bd2f0c 100755 --- a/docs/doxygen/modIngest.dox +++ b/docs/doxygen/modIngest.dox @@ -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.