diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 9a5edcb0a7..a95e359f80 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -21,10 +21,18 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Color; import java.awt.Cursor; import java.awt.EventQueue; +import java.awt.Image; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.ListSelectionModel; @@ -68,6 +76,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { private int curPageImages; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; private final PageUpdater pageUpdater = new PageUpdater(); + private final ThumbnailLoader thumbLoader = new ThumbnailLoader(); /** * Constructs a thumbnail viewer for the results view, with paging support, @@ -312,6 +321,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { @Override public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + thumbLoader.cancellAll(); try { if (givenNode != null) { /* @@ -319,7 +329,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { * produce ThumbnailPageNodes with ThumbnailViewNode children * from the child nodes of the given node. */ - ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, iconSize); + ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, thumbLoader); + childNode.setIconSize(iconSize); final Node root = new AbstractNode(childNode); pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); @@ -433,8 +444,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { try { get(); } catch (InterruptedException | ExecutionException ex) { - NotifyDescriptor d - = new NotifyDescriptor.Message( + NotifyDescriptor d = + new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", ex.getMessage()), NotifyDescriptor.ERROR_MESSAGE); @@ -585,4 +596,21 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } } } + + static class ThumbnailLoader { + + private final ExecutorService executor = Executors.newFixedThreadPool(4); + + private final List> futures = new ArrayList<>(); + + synchronized void cancellAll() { + futures.forEach(future -> future.cancel(true)); + futures.clear(); + } + + synchronized void load(ThumbnailViewNode.ThumbnailLoadTask swingWorker) { + futures.add(swingWorker); + executor.submit(swingWorker); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index bb16323625..3d6d545154 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -25,6 +25,7 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail.ThumbnailLoader; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; @@ -48,15 +49,16 @@ class ThumbnailViewChildren extends Children.Keys { private int totalPages = 0; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName()); + private final ThumbnailLoader thumbLoader; /** * the constructor */ - ThumbnailViewChildren(Node arg, int iconSize) { + ThumbnailViewChildren(Node arg, ThumbnailLoader thumbLoader) { super(true); //support lazy loading this.parent = arg; - this.iconSize = iconSize; + this.thumbLoader = thumbLoader; } @Override @@ -188,17 +190,19 @@ class ThumbnailViewChildren extends Children.Keys { protected void removeNotify() { super.removeNotify(); - setKeys(new ArrayList()); + setKeys(new ArrayList<>()); } @Override protected Node[] createNodes(Node wrapped) { if (wrapped != null) { - final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, iconSize); + final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, thumbLoader); + thumb.setIconSize(iconSize); return new Node[]{thumb}; } else { return new Node[]{}; } } } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java index 6d0fcd6205..dda5783b6b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java @@ -24,8 +24,6 @@ import java.awt.event.ActionEvent; import java.lang.ref.SoftReference; 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 javax.swing.SwingWorker; import javax.swing.Timer; @@ -34,6 +32,7 @@ import org.netbeans.api.progress.ProgressHandle; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail.ThumbnailLoader; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; @@ -44,22 +43,23 @@ import org.sleuthkit.datamodel.Content; */ class ThumbnailViewNode extends FilterNode { + private Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); + static private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); - private SoftReference iconCache = null; + private SoftReference thumbCache = null; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; -private final static Executor executor = Executors.newFixedThreadPool(1); - - private SwingWorker swingWorker; + private ThumbnailLoadTask thumbTask; private Timer timer; + private final ThumbnailLoader thumbLoader; /** * the constructor */ - ThumbnailViewNode(Node arg, int iconSize) { + ThumbnailViewNode(Node arg, ThumbnailLoader thumbLoader) { super(arg, Children.LEAF); - this.iconSize = iconSize; + this.thumbLoader = thumbLoader; } @Override @@ -70,50 +70,48 @@ private final static Executor executor = Executors.newFixedThreadPool(1); @Override @NbBundle.Messages({"# {0} - file name", "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"}) - public Image getIcon(int type) { - Image icon = null; + synchronized public Image getIcon(int type) { + Image thumbnail = null; - if (iconCache != null) { - icon = iconCache.get(); + if (thumbCache != null) { + thumbnail = thumbCache.get(); } - if (icon != null) { - return icon; + if (thumbnail != null) { + return thumbnail; } else { final Content content = this.getLookup().lookup(Content.class); if (content == null) { return ImageUtils.getDefaultThumbnail(); } - if (swingWorker == null || swingWorker.isDone()) { - swingWorker = new ThumbnailLoadingWorker(content); - executor.execute(swingWorker); -// swingWorker.execute(); + if (thumbTask == null || thumbTask.isDone()) { + thumbTask = new ThumbnailLoadTask(content); + thumbLoader.load(thumbTask); + } if (timer == null) { - timer = new Timer(100, (ActionEvent e) -> { - fireIconChange(); - }); + timer = new Timer(1, actionEvent -> fireIconChange()); timer.start(); } return waitingIcon; } } - public void setIconSize(int iconSize) { + synchronized public void setIconSize(int iconSize) { this.iconSize = iconSize; - iconCache = null; - swingWorker = null; + thumbCache = null; + thumbTask = null; } - private class ThumbnailLoadingWorker extends SwingWorker { + class ThumbnailLoadTask extends SwingWorker { private final Content content; private final ProgressHandle progressHandle; - ThumbnailLoadingWorker(Content content) { + ThumbnailLoadTask(Content content) { this.content = content; final String progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); - progressHandle = ProgressHandle.createHandle(progressText, this::cancel); + progressHandle = ProgressHandle.createHandle(progressText); } private boolean cancel() { @@ -130,12 +128,12 @@ private final static Executor executor = Executors.newFixedThreadPool(1); protected void done() { super.done(); try { - iconCache = new SoftReference<>(super.get()); + thumbCache = new SoftReference<>(super.get()); fireIconChange(); } catch (CancellationException ex) { //do nothing, it was cancelled } catch (InterruptedException | ExecutionException ex) { - Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS + logger.log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS } finally { progressHandle.finish(); if (timer != null) { @@ -143,7 +141,7 @@ private final static Executor executor = Executors.newFixedThreadPool(1); timer = null; } - swingWorker = null; + thumbTask = null; } } } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 25ee380a7e..bbb1447def 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -1007,5 +1007,4 @@ public class ImageUtils { return getCachedThumbnailFile(content, iconSize); } - }