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()