improve disposal of resources

This commit is contained in:
jmillman 2015-12-30 15:45:51 -05:00
parent 8bb612ab2c
commit 8f32064c2d
5 changed files with 95 additions and 129 deletions

View File

@ -623,10 +623,13 @@ public class ImageUtils {
ImageReader reader = readers.next(); ImageReader reader = readers.next();
reader.setInput(input); reader.setInput(input);
try { try {
return propertyExtractor.extract(reader); return propertyExtractor.extract(reader);
} catch (IOException ex) { } catch (IOException ex) {
ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + ex.toString(), file); ImageUtils.logContentError(LOGGER, Level.WARNING, errorTemplate + ex.toString(), file);
throw ex; throw ex;
} finally {
reader.dispose();
} }
} else { } else {
IIOException iioException = newImageReaderException(file); IIOException iioException = newImageReaderException(file);
@ -672,7 +675,7 @@ public class ImageUtils {
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) { if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
return SwingFXUtils.toFXImage(cachedThumbnail, null); 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); 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) { ReadImageTask(AbstractFile file) {
super(file); super(file);
updateMessage(Bundle.LoadImageTask_mesageText(file.getName()));
} }
@Override @Override
@ -754,7 +758,7 @@ public class ImageUtils {
"# {0} - file name", "# {0} - file name",
"LoadImageTask.mesageText=Reading image: {0}"}) "LoadImageTask.mesageText=Reading image: {0}"})
protected javafx.scene.image.Image call() throws Exception { protected javafx.scene.image.Image call() throws Exception {
updateMessage(Bundle.LoadImageTask_mesageText(file.getName()));
return readImage(); return readImage();
} }
} }
@ -762,18 +766,12 @@ public class ImageUtils {
static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener { static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
final AbstractFile file; final AbstractFile file;
private volatile BufferedImage bufferedImage = null;
private ImageReader reader; private ImageReader reader;
ReadImageTaskBase(AbstractFile file) { ReadImageTaskBase(AbstractFile file) {
this.file = file; this.file = file;
} }
public BufferedImage getBufferedImage() throws InterruptedException, ExecutionException {
get();
return bufferedImage;
}
protected javafx.scene.image.Image readImage() throws IOException { protected javafx.scene.image.Image readImage() throws IOException {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
if (ImageUtils.isGIF(file)) { if (ImageUtils.isGIF(file)) {
@ -803,23 +801,23 @@ public class ImageUtils {
*/ */
ImageReadParam param = reader.getDefaultReadParam(); 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); param.setDestination(bufferedImage);
try { try {
reader.read(0, param); bufferedImage = reader.read(0, param);
if (isCancelled()) { if (isCancelled()) {
return null; return null;
} }
} catch (IOException iOException) { } catch (IOException iOException) {
// Ignore this exception or display a warning or similar, for exceptions happening during decoding // 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); 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); return SwingFXUtils.toFXImage(bufferedImage, null);
} else { } else {
throw newImageReaderException(file); throw newImageReaderException(file);
} }
} }
} }

View File

@ -66,7 +66,7 @@ public enum ThumbnailCache {
* in memory cache. keeps at most 1000 items each for up to 10 minutes. * 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. * items may be garbage collected if there are no strong references to them.
*/ */
private final Cache<Long, Optional<Image>> cache = CacheBuilder.newBuilder() private final Cache<Long, Image> cache = CacheBuilder.newBuilder()
.maximumSize(1000) .maximumSize(1000)
.softValues() .softValues()
.expireAfterAccess(10, TimeUnit.MINUTES).build(); .expireAfterAccess(10, TimeUnit.MINUTES).build();
@ -99,7 +99,7 @@ public enum ThumbnailCache {
@Nullable @Nullable
public Image get(DrawableFile<?> file) { public Image get(DrawableFile<?> file) {
try { try {
return cache.get(file.getId(), () -> load(file)).orElse(null); return cache.get(file.getId(), () -> load(file));
} catch (UncheckedExecutionException | CacheLoader.InvalidCacheLoadException | ExecutionException ex) { } catch (UncheckedExecutionException | CacheLoader.InvalidCacheLoadException | ExecutionException ex) {
LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + file.getName(), ex.getCause()); LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + file.getName(), ex.getCause());
return null; return null;
@ -124,12 +124,12 @@ public enum ThumbnailCache {
* *
* @return an (possibly empty) optional containing a thumbnail * @return an (possibly empty) optional containing a thumbnail
*/ */
private Optional<Image> load(DrawableFile<?> file) { private Image load(DrawableFile<?> file) {
if (FileTypeUtils.isGIF(file)) { if (FileTypeUtils.isGIF(file)) {
//directly read gif to preserve potential animation, //directly read gif to preserve potential animation,
//NOTE: not saved to disk! //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 -> { BufferedImage thumbnail = getCacheFile(file).map(cachFile -> {
@ -160,7 +160,7 @@ public enum ThumbnailCache {
jfxthumbnail = SwingFXUtils.toFXImage(thumbnail, null); 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<Image> getThumbnailTask(DrawableFile<?> file) { public Task<Image> getThumbnailTask(DrawableFile<?> file) {
final Optional<Image> option = cache.getIfPresent(file.getId()); final Image thumbnail = cache.getIfPresent(file.getId());
if (option != null && option.isPresent()) { if (thumbnail != null) {
return new Task<Image>() { return new Task<Image>() {
@Override @Override
protected Image call() throws Exception { protected Image call() throws Exception {
return option.get(); return thumbnail;
} }
}; };
} }
@ -196,7 +196,7 @@ public enum ThumbnailCache {
switch (newGetThumbnailTask.getState()) { switch (newGetThumbnailTask.getState()) {
case SUCCEEDED: case SUCCEEDED:
try { try {
cache.put(Long.MIN_VALUE, Optional.of(newGetThumbnailTask.get())); cache.put(Long.MIN_VALUE, newGetThumbnailTask.get());
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex); Exceptions.printStackTrace(ex);
} }

View File

@ -60,9 +60,9 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName());
@FXML @FXML
BorderPane imageBorder; BorderPane imageBorder;
@FXML @FXML
ImageView imageView; ImageView imageView;
private final ImageGalleryController controller; private final ImageGalleryController controller;
@ -118,33 +118,41 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
synchronized public void setFile(Long newFileID) { synchronized public void setFile(Long newFileID) {
if (getFileID().isPresent()) { if (getFileID().isPresent()) {
if (Objects.equals(newFileID, getFileID().get()) == false) { if (Objects.equals(newFileID, getFileID().get()) == false) {
// if (Objects.nonNull(newFileID)) {
setFileHelper(newFileID); setFileHelper(newFileID);
// }
} }
} else {//if (Objects.nonNull(newFileID)) { } else {
setFileHelper(newFileID); setFileHelper(newFileID);
} }
} }
synchronized protected void updateContent() { synchronized protected void updateContent() {
if (getFile().isPresent() == false) { if (getFile().isPresent()) {
Platform.runLater(() -> imageBorder.setCenter(null));
} else {
doReadImageTask(getFile().get()); doReadImageTask(getFile().get());
} }
} }
synchronized Node doReadImageTask(DrawableFile<?> file) { synchronized Node doReadImageTask(DrawableFile<?> file) {
disposeContent();
Task<Image> myTask = newReadImageTask(file); Task<Image> myTask = newReadImageTask(file);
imageTask = myTask; imageTask = myTask;
Node progressNode = newProgressIndicator(myTask); Node progressNode = newProgressIndicator(myTask);
Platform.runLater(() -> imageBorder.setCenter(progressNode)); Platform.runLater(() -> imageBorder.setCenter(progressNode));
//called on fx thread //called on fx thread
imageTask.setOnSucceeded(succeeded -> showImage(file, myTask)); myTask.setOnSucceeded(succeeded -> {
imageTask.setOnFailed(failed -> showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file)); 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); exec.execute(myTask);
return progressNode; return progressNode;
@ -155,8 +163,10 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
imageTask.cancel(); imageTask.cancel();
} }
imageTask = null; 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) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void showImage(DrawableFile<?> file, Task<Image> imageTask) { private void showImage(DrawableFile<?> file, Task<Image> imageTask) {
//Note that all error conditions are allready logged in readImageTask.succeeded() //Note that all error conditions are allready logged in readImageTask.succeeded()
try { try {
Image fxImage = imageTask.get(); Image fxImage = imageTask.get();
@ -201,57 +211,4 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
} }
abstract Task<Image> newReadImageTask(DrawableFile<?> file); abstract Task<Image> newReadImageTask(DrawableFile<?> file);
abstract class CachedLoaderTask<X, Y extends DrawableFile<?>> extends Task<X> {
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<Image, DrawableFile<?>> {
//
// 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.
// }
// }
} }

View File

@ -169,21 +169,23 @@ public class MetaDataPane extends DrawableUIBase {
@Override @Override
synchronized protected void setFileHelper(Long newFileID) { synchronized protected void setFileHelper(Long newFileID) {
setFileIDOpt(Optional.ofNullable(newFileID)); setFileIDOpt(Optional.ofNullable(newFileID));
if (newFileID == null) { disposeContent();
Platform.runLater(() -> { if (nonNull(newFileID)) {
imageView.setImage(null);
imageBorder.setCenter(null);
tableView.getItems().clear();
getCategoryBorderRegion().setBorder(null);
});
} else {
disposeContent();
updateAttributesTable(); updateAttributesTable();
updateCategory(); updateCategory();
updateContent(); updateContent();
} }
} }
@Override
protected synchronized void disposeContent() {
super.disposeContent();
Platform.runLater(() -> {
tableView.getItems().clear();
getCategoryBorderRegion().setBorder(null);
});
}
@Override @Override
Task<Image> newReadImageTask(DrawableFile<?> file) { Task<Image> newReadImageTask(DrawableFile<?> file) {
return file.getThumbnailTask(); return file.getThumbnailTask();

View File

@ -19,8 +19,6 @@
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.SoftReference;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -76,6 +74,8 @@ public class SlideShowView extends DrawableTileBase {
@FXML @FXML
private BorderPane footer; private BorderPane footer;
private volatile MediaLoadTask mediaTask;
SlideShowView(GroupPane gp, ImageGalleryController controller) { SlideShowView(GroupPane gp, ImageGalleryController controller) {
super(gp, controller); super(gp, controller);
FXMLConstructor.construct(this, "SlideShowView.fxml"); FXMLConstructor.construct(this, "SlideShowView.fxml");
@ -160,49 +160,58 @@ public class SlideShowView extends DrawableTileBase {
} }
@Override @Override
protected void disposeContent() { synchronized protected void disposeContent() {
stopVideo(); stopVideo();
if (mediaTask != null) {
mediaTask.cancel(true);
}
mediaTask = null;
super.disposeContent(); super.disposeContent();
// if (mediaTask != null) {
// mediaTask.cancel(true);
// }
// mediaTask = null;
mediaCache = null;
} }
private SoftReference<Node> mediaCache;
@Override @Override
synchronized protected void updateContent() { synchronized protected void updateContent() {
if (getFile().isPresent() == false) { disposeContent();
mediaCache = null; if (getFile().isPresent()) {
Platform.runLater(() -> imageBorder.setCenter(null));
} else {
DrawableFile<?> file = getFile().get(); DrawableFile<?> file = getFile().get();
if (file.isVideo()) { if (file.isVideo()) {
//specially handling for videos doMediaLoadTask((VideoFile<?>) file);
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);
}
} else { } 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) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void showMedia(DrawableFile<?> file, Task<Node> mediaTask) { private void showMedia(DrawableFile<?> file, Task<Node> mediaTask) {
//Note that all error conditions are allready logged in readImageTask.succeeded() //Note that all error conditions are allready logged in readImageTask.succeeded()