From 189b82ef239d70658d11a29b7e37b3031e95dbf8 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 6 Jun 2017 11:12:08 +0200 Subject: [PATCH 01/16] enable cancellation of ThumbnailLoadingWorker, limit to 3 at a time --- .../corecomponents/ThumbnailViewNode.java | 94 ++++++++++++------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java index 8ca68b5689..3d739cf5fa 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-16 Basis Technology Corp. + * Copyright 2011-17 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,10 @@ import java.awt.Image; import java.awt.Toolkit; 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; @@ -46,6 +49,8 @@ class ThumbnailViewNode extends FilterNode { private SoftReference iconCache = null; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; + static Executor executor = Executors.newFixedThreadPool(3); + private SwingWorker swingWorker; private Timer timer; @@ -80,42 +85,16 @@ class ThumbnailViewNode extends FilterNode { return ImageUtils.getDefaultThumbnail(); } if (swingWorker == null || swingWorker.isDone()) { - swingWorker = new SwingWorker() { - final private ProgressHandle progressHandle = ProgressHandle.createHandle(Bundle.ThumbnailViewNode_progressHandle_text(content.getName())); - - @Override - protected Image doInBackground() throws Exception { - progressHandle.start(); - return ImageUtils.getThumbnail(content, iconSize); - } - - @Override - protected void done() { - super.done(); - try { - iconCache = new SoftReference<>(super.get()); - fireIconChange(); - } catch (InterruptedException | ExecutionException ex) { - Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS - } finally { - progressHandle.finish(); - if (timer != null) { - timer.stop(); - timer = null; - - } - swingWorker = null; - } - } - }; - swingWorker.execute(); - } - if (timer == null) { - timer = new Timer(100, (ActionEvent e) -> { - fireIconChange(); - }); - timer.start(); + swingWorker = new ThumbnailLoadingWorker(content); + executor.execute(swingWorker); +// swingWorker.execute(); } +// if (timer == null) { +//// timer = new Timer(100, (ActionEvent e) -> { +//// fireIconChange(); +//// }); +// timer.start(); +// } return waitingIcon; } } @@ -125,4 +104,47 @@ class ThumbnailViewNode extends FilterNode { iconCache = null; swingWorker = null; } + + private class ThumbnailLoadingWorker extends SwingWorker { + + private final Content content; + private final ProgressHandle progressHandle; + + ThumbnailLoadingWorker(Content content) { + this.content = content; + final String progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); + progressHandle = ProgressHandle.createHandle(progressText, this::cancel); + } + + private boolean cancel() { + return this.cancel(true); + } + + @Override + protected Image doInBackground() throws Exception { + progressHandle.start(); + return ImageUtils.getThumbnail(content, iconSize); + } + + @Override + protected void done() { + super.done(); + try { + iconCache = 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 + } finally { + progressHandle.finish(); + if (timer != null) { + timer.stop(); + timer = null; + + } + swingWorker = null; + } + } + } } From 1bb84edc23452c44db41ecd658a693b5a0ce0739 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 6 Jun 2017 12:32:38 +0200 Subject: [PATCH 02/16] reinstate timer triggered spinner as that doesn't seem to be the bottleneck --- .../autopsy/corecomponents/ThumbnailViewNode.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java index 3d739cf5fa..6d0fcd6205 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java @@ -49,7 +49,7 @@ class ThumbnailViewNode extends FilterNode { private SoftReference iconCache = null; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; - static Executor executor = Executors.newFixedThreadPool(3); +private final static Executor executor = Executors.newFixedThreadPool(1); private SwingWorker swingWorker; private Timer timer; @@ -89,12 +89,12 @@ class ThumbnailViewNode extends FilterNode { executor.execute(swingWorker); // swingWorker.execute(); } -// if (timer == null) { -//// timer = new Timer(100, (ActionEvent e) -> { -//// fireIconChange(); -//// }); -// timer.start(); -// } + if (timer == null) { + timer = new Timer(100, (ActionEvent e) -> { + fireIconChange(); + }); + timer.start(); + } return waitingIcon; } } From 115b0a99ce5c63a4617322c29b935a8dab7ac4b4 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 6 Jun 2017 15:27:13 +0200 Subject: [PATCH 03/16] move thread pool to DataResultViewerThumbnail so it can cancel all tasks easily in setNode --- .../DataResultViewerThumbnail.java | 34 ++++++++++- .../corecomponents/ThumbnailViewChildren.java | 12 ++-- .../corecomponents/ThumbnailViewNode.java | 58 +++++++++---------- .../autopsy/coreutils/ImageUtils.java | 1 - 4 files changed, 67 insertions(+), 38 deletions(-) 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); } - } From 3691f87d28da72479bf21c51675e7ba05bf5e5e0 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 6 Jun 2017 15:57:03 +0200 Subject: [PATCH 04/16] move the ThumbnailLoader to ThumbnailViewNode --- .../DataResultViewerThumbnail.java | 26 ++----------------- .../corecomponents/ThumbnailViewChildren.java | 2 +- .../corecomponents/ThumbnailViewNode.java | 25 ++++++++++++++++-- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index a95e359f80..1cad668d5d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -21,18 +21,10 @@ 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; @@ -50,6 +42,7 @@ import org.openide.nodes.NodeMemberEvent; import org.openide.nodes.NodeReorderEvent; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.ThumbnailViewNode.ThumbnailLoader; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; @@ -597,20 +590,5 @@ 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 3d6d545154..c60b49617e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -25,7 +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.corecomponents.ThumbnailViewNode.ThumbnailLoader; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java index dda5783b6b..5ebd7e1ad5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java @@ -18,12 +18,17 @@ */ package org.sleuthkit.autopsy.corecomponents; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.awt.Image; import java.awt.Toolkit; -import java.awt.event.ActionEvent; import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.logging.Level; import javax.swing.SwingWorker; import javax.swing.Timer; @@ -32,7 +37,6 @@ 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; @@ -103,6 +107,23 @@ class ThumbnailViewNode extends FilterNode { thumbTask = null; } + static class ThumbnailLoader { + + private final ExecutorService executor = Executors.newFixedThreadPool(4, + new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build() ); + + private final List> futures = new ArrayList<>(); + + synchronized void cancellAll() { + futures.forEach(future -> future.cancel(true)); + futures.clear(); + } + + synchronized void load(ThumbnailViewNode.ThumbnailLoadTask task) { + futures.add(task); + executor.submit(task); + } + } class ThumbnailLoadTask extends SwingWorker { private final Content content; From cd962e15320484d7545e2d8d4a4c9cd0cf208a3f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 7 Jun 2017 15:56:05 +0200 Subject: [PATCH 05/16] cleanup --- .../autopsy/corecomponents/DataResultViewerThumbnail.java | 6 +++--- .../autopsy/corecomponents/ThumbnailViewChildren.java | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 24d4e01302..020e573236 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -52,19 +52,19 @@ import org.openide.nodes.NodeReorderEvent; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; +import org.sleuthkit.autopsy.corecomponents.ThumbnailViewChildren.ThumbnailLoader; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; -import static org.sleuthkit.autopsy.corecomponents.Bundle.*; -import org.sleuthkit.autopsy.corecomponents.ThumbnailViewChildren.ThumbnailLoader; /** * A thumbnail viewer for the results view, with paging support. * * The paging is intended to reduce memory footprint by load only up to - * (currently) 1000 images at a time. This works whether or not the underlying + * (currently) 200 images at a time. This works whether or not the underlying * content nodes are being lazy loaded or not. * * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 165081f91d..9351625b9d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -166,7 +166,6 @@ class ThumbnailViewChildren extends Children.Keys { } private Comparator getCriterionComparator(SortCriterion criterion) { - Comparator c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()), Comparator.nullsFirst(Comparator.naturalOrder())); return criterion.getSortOrder() == SortOrder.ASCENDING ? c : c.reversed(); @@ -236,7 +235,7 @@ class ThumbnailViewChildren extends Children.Keys { /** * the constructor */ - ThumbnailViewNode(Node arg, ThumbnailLoader thumbLoader) { + private ThumbnailViewNode(Node arg, ThumbnailLoader thumbLoader) { super(arg, FilterNode.Children.LEAF); this.thumbLoader = thumbLoader; } @@ -282,7 +281,7 @@ class ThumbnailViewChildren extends Children.Keys { thumbTask = null; } - class ThumbnailLoadTask extends SwingWorker { + private class ThumbnailLoadTask extends SwingWorker { private final Content content; private final ProgressHandle progressHandle; @@ -338,7 +337,7 @@ class ThumbnailViewChildren extends Children.Keys { futures.clear(); } - synchronized void load(ThumbnailViewNode.ThumbnailLoadTask task) { + private synchronized void load(ThumbnailViewNode.ThumbnailLoadTask task) { futures.add(task); executor.submit(task); } From 0d8855d8b4291b8a50a0a6b1083e063e8f130ea1 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 8 Jun 2017 14:07:20 +0200 Subject: [PATCH 06/16] move functionality of ThumbnailLoader directly into ThumbnailViewChildren --- .../DataResultViewerThumbnail.java | 15 ++--- .../corecomponents/ThumbnailViewChildren.java | 61 ++++++++----------- 2 files changed, 32 insertions(+), 44 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 020e573236..0a4e9ac0f7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -54,7 +54,6 @@ import org.openide.util.NbPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; -import org.sleuthkit.autopsy.corecomponents.ThumbnailViewChildren.ThumbnailLoader; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; @@ -81,8 +80,8 @@ 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(); private TableFilterNode tfn; + private ThumbnailViewChildren tvc; /** * Constructs a thumbnail viewer for the results view, with paging support, @@ -387,7 +386,9 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { @Override public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - thumbLoader.cancellAll(); + if (tvc != null) { + tvc.cancelLoadingThumbnails(); + } try { if (givenNode != null) { tfn = (TableFilterNode) givenNode; @@ -396,15 +397,16 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { * produce ThumbnailPageNodes with ThumbnailViewNode children * from the child nodes of the given node. */ - ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, thumbLoader); - childNode.setIconSize(iconSize); - final Node root = new AbstractNode(childNode); + tvc = new ThumbnailViewChildren(givenNode); + tvc.setIconSize(iconSize); + final Node root = new AbstractNode(tvc); pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); em.setRootContext(root); } else { tfn = null; + tvc = null; Node emptyNode = new AbstractNode(Children.LEAF); em.setRootContext(emptyNode); iconView.setBackground(Color.BLACK); @@ -676,5 +678,4 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } } - } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 9351625b9d..9aa7f03fba 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -48,10 +48,10 @@ import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; +import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadSortCriteria; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; -import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadSortCriteria; /** * Complementary class to ThumbnailViewNode. Children node factory. Wraps around @@ -73,7 +73,6 @@ class ThumbnailViewChildren extends Children.Keys { private int totalImages = 0; private int totalPages = 0; private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; - private final ThumbnailLoader thumbLoader; /** * the constructor @@ -81,11 +80,10 @@ class ThumbnailViewChildren extends Children.Keys { * @param arg * @param iconSize */ - ThumbnailViewChildren(Node arg, ThumbnailLoader thumbLoader) { + ThumbnailViewChildren(Node arg) { super(true); //support lazy loading this.parent = arg; - this.thumbLoader = thumbLoader; } @Override @@ -200,7 +198,7 @@ class ThumbnailViewChildren extends Children.Keys { } - static boolean isSupported(Node node) { + private static boolean isSupported(Node node) { if (node != null) { Content content = node.getLookup().lookup(Content.class); if (content != null) { @@ -215,11 +213,13 @@ class ThumbnailViewChildren extends Children.Keys { } + + /** * Node that wraps around original node and adds the bitmap icon * representing the picture */ - class ThumbnailViewNode extends FilterNode { + private class ThumbnailViewNode extends FilterNode { private Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); @@ -230,14 +230,12 @@ class ThumbnailViewChildren extends Children.Keys { private ThumbnailLoadTask thumbTask; private Timer timer; - private final ThumbnailLoader thumbLoader; /** * the constructor */ - private ThumbnailViewNode(Node arg, ThumbnailLoader thumbLoader) { - super(arg, FilterNode.Children.LEAF); - this.thumbLoader = thumbLoader; + private ThumbnailViewNode(Node wrappedNode) { + super(wrappedNode, FilterNode.Children.LEAF); } @Override @@ -263,8 +261,7 @@ class ThumbnailViewChildren extends Children.Keys { return ImageUtils.getDefaultThumbnail(); } if (thumbTask == null || thumbTask.isDone()) { - thumbTask = new ThumbnailLoadTask(content); - thumbLoader.load(thumbTask); + thumbTask = loadThumbnail(this, content); } if (timer == null) { @@ -281,7 +278,7 @@ class ThumbnailViewChildren extends Children.Keys { thumbTask = null; } - private class ThumbnailLoadTask extends SwingWorker { + private class ThumbnailLoadTask extends SwingWorker { private final Content content; private final ProgressHandle progressHandle; @@ -292,10 +289,6 @@ class ThumbnailViewChildren extends Children.Keys { progressHandle = ProgressHandle.createHandle(progressText); } - private boolean cancel() { - return this.cancel(true); - } - @Override protected Image doInBackground() throws Exception { progressHandle.start(); @@ -317,7 +310,6 @@ class ThumbnailViewChildren extends Children.Keys { if (timer != null) { timer.stop(); timer = null; - } thumbTask = null; } @@ -325,22 +317,21 @@ class ThumbnailViewChildren extends Children.Keys { } } - static class ThumbnailLoader { + private final ExecutorService executor = Executors.newFixedThreadPool(4, + new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build()); - private final ExecutorService executor = Executors.newFixedThreadPool(4, - new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build()); + private final List> futures = new ArrayList<>(); - private final List> futures = new ArrayList<>(); + synchronized void cancelLoadingThumbnails() { + futures.forEach(future -> future.cancel(true)); + futures.clear(); + } - synchronized void cancellAll() { - futures.forEach(future -> future.cancel(true)); - futures.clear(); - } - - private synchronized void load(ThumbnailViewNode.ThumbnailLoadTask task) { - futures.add(task); - executor.submit(task); - } + private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node, Content content) { + ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content); + futures.add(task); + executor.submit(task); + return task; } /** @@ -370,28 +361,25 @@ class ThumbnailViewChildren extends Children.Keys { ThumbnailPageNodeChildren(List contentImages) { super(true); - this.contentImages = contentImages; } @Override protected void addNotify() { super.addNotify(); - setKeys(contentImages); } @Override protected void removeNotify() { super.removeNotify(); - - setKeys(new ArrayList<>()); + setKeys(Collections.emptyList()); } @Override protected Node[] createNodes(Node wrapped) { if (wrapped != null) { - final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, thumbLoader); + final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped); thumb.setIconSize(iconSize); return new Node[]{thumb}; } else { @@ -399,5 +387,4 @@ class ThumbnailViewChildren extends Children.Keys { } } } - } From 4938ad4487b80caf33eb83f191ccce0e23b51ba3 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 8 Jun 2017 17:05:36 +0200 Subject: [PATCH 07/16] check for interupts/cancelation in ImageUtils methods/tasks; set cancelation text on progress bars. --- .../corecomponents/ThumbnailViewChildren.java | 52 +++++++--- .../autopsy/coreutils/ImageUtils.java | 98 ++++++++++++------- 2 files changed, 98 insertions(+), 52 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 9aa7f03fba..6746d86fd6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -32,10 +32,10 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.SortOrder; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.Timer; import org.apache.commons.lang3.StringUtils; @@ -213,13 +213,11 @@ class ThumbnailViewChildren extends Children.Keys { } - - /** * Node that wraps around original node and adds the bitmap icon * representing the picture */ - private class ThumbnailViewNode extends FilterNode { + class ThumbnailViewNode extends FilterNode { private Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); @@ -282,19 +280,28 @@ class ThumbnailViewChildren extends Children.Keys { private final Content content; private final ProgressHandle progressHandle; + private volatile boolean started = false; + private final String progressText; ThumbnailLoadTask(Content content) { this.content = content; - final String progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); + progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); progressHandle = ProgressHandle.createHandle(progressText); } @Override protected Image doInBackground() throws Exception { - progressHandle.start(); + synchronized (progressHandle) { + progressHandle.start(); + started = true; + } return ImageUtils.getThumbnail(content, iconSize); } + private void cancel() { + SwingUtilities.invokeLater(() -> progressHandle.setDisplayName(progressText + " (Cancelling)")); + } + @Override protected void done() { super.done(); @@ -302,11 +309,18 @@ class ThumbnailViewChildren extends Children.Keys { thumbCache = new SoftReference<>(super.get()); fireIconChange(); } catch (CancellationException ex) { - //do nothing, it was cancelled + //Task was cancelled, do nothing } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS + if (ex.getCause() instanceof CancellationException) { + } else { + logger.log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS + } } finally { - progressHandle.finish(); + synchronized (progressHandle) { + if (started) { + progressHandle.finish(); + } + } if (timer != null) { timer.stop(); timer = null; @@ -314,24 +328,30 @@ class ThumbnailViewChildren extends Children.Keys { thumbTask = null; } } + } } private final ExecutorService executor = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build()); - private final List> futures = new ArrayList<>(); + private final List tasks = new ArrayList<>(); synchronized void cancelLoadingThumbnails() { - futures.forEach(future -> future.cancel(true)); - futures.clear(); + tasks.forEach(ThumbnailViewNode.ThumbnailLoadTask::cancel); + tasks.clear(); + executor.shutdownNow(); } private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node, Content content) { - ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content); - futures.add(task); - executor.submit(task); - return task; + if (executor.isShutdown() == false) { + ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content); + tasks.add(task); + executor.submit(task); + return task; + } else { + return null; + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index bbb1447def..0480ffc5a5 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -40,6 +40,7 @@ import java.util.List; import static java.util.Objects.nonNull; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -164,8 +165,8 @@ public class ImageUtils { /** * Thread/Executor 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("thumbnail-saver-%d").build()); //NON-NLS public static List getSupportedImageExtensions() { @@ -213,7 +214,7 @@ public class ImageUtils { * @param file the AbstractFile to test * * @return true if the file is an image we can read and generate thumbnail - * for. + * for. */ public static boolean isImageThumbnailSupported(AbstractFile file) { return isMediaThumbnailSupported(file, "image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || hasImageFileHeader(file);//NON-NLS @@ -239,16 +240,17 @@ public class ImageUtils { * VideoUtils both implement/extend some base interface/abstract class. That * would be the natural place to put this. * - * @param file the AbstractFile to test - * @param mimeTypePrefix a MIME 'top-level type name' such as "image/", - * including the "/". In addition to the list of supported MIME types, any - * type that starts with this prefix will be regarded as supported + * @param file the AbstractFile to test + * @param mimeTypePrefix a MIME 'top-level type name' such as "image/", + * including the "/". In addition to the list of + * supported MIME types, any type that starts with + * this prefix will be regarded as supported * @param supportedMimeTypes a collection of mimetypes that are supported * @param supportedExtension a collection of extensions that are supported * * @return true if a thumbnail can be generated for the given file based on - * the given MIME type prefix and lists of supported MIME types and - * extensions + * the given MIME type prefix and lists of supported MIME types and + * extensions */ static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix, final Collection supportedMimeTypes, final List supportedExtension) { if (false == file.isFile() || file.getSize() <= 0) { @@ -282,7 +284,7 @@ public class ImageUtils { * @return a FileTypeDetector * * @throws FileTypeDetectorInitException if initializing the - * FileTypeDetector failed. + * FileTypeDetector failed. */ synchronized private static FileTypeDetector getFileTypeDetector() throws FileTypeDetector.FileTypeDetectorInitException { if (fileTypeDetector == null) { @@ -295,11 +297,11 @@ public class ImageUtils { * Get a thumbnail of a specified size for the given image. Generates the * thumbnail if it is not already cached. * - * @param content the content to generate a thumbnail for + * @param content the content to generate a thumbnail for * @param iconSize the size (one side of a square) in pixels to generate * * @return A thumbnail for the given image or a default one if there was a - * problem making a thumbnail. + * problem making a thumbnail. */ public static BufferedImage getThumbnail(Content content, int iconSize) { if (content instanceof AbstractFile) { @@ -310,8 +312,14 @@ public class ImageUtils { * to rescale easily, but we lose animations. */ try (BufferedInputStream bufferedReadContentStream = getBufferedReadContentStream(file);) { + if (Thread.interrupted()) { + return DEFAULT_THUMBNAIL; + } final BufferedImage image = ImageIO.read(bufferedReadContentStream); if (image != null) { + if (Thread.interrupted()) { + return DEFAULT_THUMBNAIL; + } return ScalrWrapper.resizeHighQuality(image, iconSize, iconSize); } } catch (IOException iOException) { @@ -321,6 +329,9 @@ public class ImageUtils { } Task thumbnailTask = newGetThumbnailTask(file, iconSize, true); + if (Thread.interrupted()) { + return DEFAULT_THUMBNAIL; + } thumbnailTask.run(); try { return SwingFXUtils.fromFXImage(thumbnailTask.get(), null); @@ -338,7 +349,7 @@ public class ImageUtils { * @param file The AbstractFile to get a stream for. * * @return A BufferedInputStream wrapped around a ReadContentStream for the - * given AbstractFile + * given AbstractFile */ private static BufferedInputStream getBufferedReadContentStream(AbstractFile file) { return new BufferedInputStream(new ReadContentInputStream(file)); @@ -348,11 +359,11 @@ public class ImageUtils { * Get a thumbnail of a specified size for the given image. Generates the * thumbnail if it is not already cached. * - * @param content the content to generate a thumbnail for + * @param content the content to generate a thumbnail for * @param iconSize the size (one side of a square) in pixels to generate * * @return File object for cached image. Is guaranteed to exist, as long as - * there was not an error generating or saving the thumbnail. + * there was not an error generating or saving the thumbnail. */ @Nullable public static File getCachedThumbnailFile(Content content, int iconSize) { @@ -367,8 +378,8 @@ public class ImageUtils { * @param fileID the fileID to get the cached thumbnail location for * * @return A File object representing the location of the cached thumbnail. - * This file may not actually exist(yet). Returns null if there was any - * problem getting the file, such as no case was open. + * This file may not actually exist(yet). Returns null if there was + * any problem getting the file, such as no case was open. */ private static File getCachedThumbnailLocation(long fileID) { return cacheFileMap.computeIfAbsent(fileID, id -> { @@ -426,7 +437,7 @@ public class ImageUtils { * @param file the AbstractFile to parse * * @return Offset of first Start Of Image marker, or 0 if none found. This - * will let ImageIO try to open it from offset 0. + * will let ImageIO try to open it from offset 0. */ private static long getJfifStartOfImageOffset(AbstractFile file) { byte[] fileHeaderBuffer; @@ -506,7 +517,7 @@ public class ImageUtils { * @return the width in pixels * * @throws IOException If the file is not a supported image or the width - * could not be determined. + * could not be determined. */ static public int getImageWidth(AbstractFile file) throws IOException { return getImageProperty(file, @@ -523,7 +534,7 @@ public class ImageUtils { * @return the height in pixels * * @throws IOException If the file is not a supported image or the height - * could not be determined. + * could not be determined. */ static public int getImageHeight(AbstractFile file) throws IOException { return getImageProperty(file, @@ -552,17 +563,18 @@ public class ImageUtils { * public methods that pull particular (usually meta-)data out of a image * file. * - * @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 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. + * used to retrieve the specific property. * * @return the the value of the property extracted by the given - * propertyExtractor + * propertyExtractor * * @throws IOException if there was a problem reading the property from the - * file. + * file. * * @see PropertyExtractor * @see #getImageHeight(org.sleuthkit.datamodel.AbstractFile) @@ -606,8 +618,8 @@ public class ImageUtils { * 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. + * @param file The file to create a thumbnail for. + * @param iconSize The size of the thumbnail. * @param defaultOnFailure Whether or not to default on failure. * * @return a new Task that returns a thumbnail as its result. @@ -695,7 +707,9 @@ public class ImageUtils { throw new IIOException(msg); } updateProgress(-1, 1); - + if (isCancelled()) { + return null; + } //resize, or if that fails, crop it try { thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); @@ -709,6 +723,9 @@ public class ImageUtils { final int cropHeight = Math.min(iconSize, height); final int cropWidth = Math.min(iconSize, width); try { + if (isCancelled()) { + return null; + } thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight); } catch (Exception cropException) { LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS @@ -720,17 +737,15 @@ public class ImageUtils { } } - if (isCancelled()) { - return null; - } - updateProgress(-1, 1); //if we got a valid thumbnail save it if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) { saveThumbnail(thumbnail); } - + if (isCancelled()) { + return null; + } return SwingFXUtils.toFXImage(thumbnail, null); } @@ -806,6 +821,9 @@ public class ImageUtils { } protected javafx.scene.image.Image readImage() throws IOException { + if (isCancelled()) { + return null; + } if (ImageUtils.isGIF(file)) { //use JavaFX to directly read GIF to preserve potential animation javafx.scene.image.Image image = new javafx.scene.image.Image(getBufferedReadContentStream(file)); @@ -865,6 +883,14 @@ public class ImageUtils { } } + @Override + public boolean isCancelled() { + if (Thread.interrupted()) { + this.cancel(true); + } + return super.isCancelled(); + } + @Override protected void succeeded() { super.succeeded(); @@ -976,7 +1002,7 @@ public class ImageUtils { * @param iconSize * * @return a thumbnail for the given image or a default one if there was a - * problem making a thumbnail. + * problem making a thumbnail. * * @deprecated use getThumbnail(org.sleuthkit.datamodel.Content, int) * instead. @@ -995,7 +1021,7 @@ public class ImageUtils { * @param iconSize * * @return File object for cached image. Is guaranteed to exist, as long as - * there was not an error generating or saving the thumbnail. + * there was not an error generating or saving the thumbnail. * * @deprecated use getCachedThumbnailFile(org.sleuthkit.datamodel.Content, * int) instead. From 8fc540554aa4c8a3fab5ef60ced4adf372d9c95d Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 9 Jun 2017 10:26:11 +0200 Subject: [PATCH 08/16] move calls to ThumbnailViewNode.setThumbsSize into ThumbnailViewChildren.setThumbsSize; make ThumbnailViewNode private; other cleanup --- .../DataResultViewerThumbnail.java | 12 +-- .../corecomponents/ThumbnailViewChildren.java | 78 +++++++++++-------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 0a4e9ac0f7..85f986864f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -299,15 +299,9 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { if (iconSize != newIconSize) { iconSize = newIconSize; Node root = em.getRootContext(); - for (Children c : Arrays.asList(root.getChildren())) { - ((ThumbnailViewChildren) c).setIconSize(iconSize); - } + ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(iconSize); - for (Node page : root.getChildren().getNodes()) { - for (Node node : page.getChildren().getNodes()) { - ((ThumbnailViewChildren.ThumbnailViewNode) node).setIconSize(iconSize); - } - } + // Temporarily set the explored context to the root, instead of a child node. // This is a workaround hack to convince org.openide.explorer.ExplorerManager to @@ -398,7 +392,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { * from the child nodes of the given node. */ tvc = new ThumbnailViewChildren(givenNode); - tvc.setIconSize(iconSize); + tvc.setThumbsSize(iconSize); final Node root = new AbstractNode(tvc); pageUpdater.setRoot(root); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 6746d86fd6..d9a002ae71 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -72,18 +72,17 @@ class ThumbnailViewChildren extends Children.Keys { private final HashMap> pages = new HashMap<>(); private int totalImages = 0; private int totalPages = 0; - private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; + private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; /** - * the constructor + * The constructor * - * @param arg - * @param iconSize + * @param parent The node which is the parent of this children. */ - ThumbnailViewChildren(Node arg) { + ThumbnailViewChildren(Node parent) { super(true); //support lazy loading - this.parent = arg; + this.parent = parent; } @Override @@ -193,8 +192,7 @@ class ThumbnailViewChildren extends Children.Keys { @Override protected Node[] createNodes(Integer pageNum) { - final ThumbnailPageNode pageNode = new ThumbnailPageNode(pageNum); - return new Node[]{pageNode}; + return new Node[]{new ThumbnailPageNode(pageNum)}; } @@ -208,32 +206,40 @@ class ThumbnailViewChildren extends Children.Keys { return false; } - public void setIconSize(int iconSize) { - this.iconSize = iconSize; - + public void setThumbsSize(int thumbSize) { + this.thumbSize = thumbSize; + for (Node page : getNodes()) { + for (Node node : page.getChildren().getNodes()) { + ((ThumbnailViewNode) node).setThumbSize(thumbSize); + } + } } /** - * Node that wraps around original node and adds the bitmap icon - * representing the picture + * Node that wraps around original node and adds the thumbnail representing + * the image/video. */ - class ThumbnailViewNode extends FilterNode { + private class ThumbnailViewNode extends FilterNode { - private Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); + private final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); private SoftReference thumbCache = null; - private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; + private int thumbSize; private ThumbnailLoadTask thumbTask; private Timer timer; /** - * the constructor + * The constructor + * + * @param wrappedNode The original node that this Node wraps. + * @param thumbSize The hight and/or width of the thumbnail in pixels. */ - private ThumbnailViewNode(Node wrappedNode) { + private ThumbnailViewNode(Node wrappedNode, int thumbSize) { super(wrappedNode, FilterNode.Children.LEAF); + this.thumbSize = thumbSize; } @Override @@ -259,7 +265,7 @@ class ThumbnailViewChildren extends Children.Keys { return ImageUtils.getDefaultThumbnail(); } if (thumbTask == null || thumbTask.isDone()) { - thumbTask = loadThumbnail(this, content); + thumbTask = loadThumbnail(ThumbnailViewNode.this, content); } if (timer == null) { @@ -270,8 +276,8 @@ class ThumbnailViewChildren extends Children.Keys { } } - synchronized public void setIconSize(int iconSize) { - this.iconSize = iconSize; + synchronized void setThumbSize(int iconSize) { + this.thumbSize = iconSize; thumbCache = null; thumbTask = null; } @@ -282,11 +288,13 @@ class ThumbnailViewChildren extends Children.Keys { private final ProgressHandle progressHandle; private volatile boolean started = false; private final String progressText; + private final int thumbSize; - ThumbnailLoadTask(Content content) { + ThumbnailLoadTask(Content content, int thumbSize) { this.content = content; progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); progressHandle = ProgressHandle.createHandle(progressText); + this.thumbSize = thumbSize; } @Override @@ -295,7 +303,7 @@ class ThumbnailViewChildren extends Children.Keys { progressHandle.start(); started = true; } - return ImageUtils.getThumbnail(content, iconSize); + return ImageUtils.getThumbnail(content, thumbSize); } private void cancel() { @@ -345,7 +353,7 @@ class ThumbnailViewChildren extends Children.Keys { private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node, Content content) { if (executor.isShutdown() == false) { - ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content); + ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content, node.thumbSize); tasks.add(task); executor.submit(task); return task; @@ -369,25 +377,30 @@ class ThumbnailViewChildren extends Children.Keys { setDisplayName(from + "-" + to); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS - } } -//TODO insert node at beginning pressing which goes back to page view + /** + * Children.Keys implementation which uses nodes as keys, and wraps them in + * ThumbnailViewNodes as the child nodes. + * + */ private class ThumbnailPageNodeChildren extends Children.Keys { - //wrapped original nodes - private List contentImages = null; + /* + * wrapped original nodes + */ + private List keyNodes = null; - ThumbnailPageNodeChildren(List contentImages) { + ThumbnailPageNodeChildren(List keyNodes) { super(true); - this.contentImages = contentImages; + this.keyNodes = keyNodes; } @Override protected void addNotify() { super.addNotify(); - setKeys(contentImages); + setKeys(keyNodes); } @Override @@ -399,8 +412,7 @@ class ThumbnailViewChildren extends Children.Keys { @Override protected Node[] createNodes(Node wrapped) { if (wrapped != null) { - final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped); - thumb.setIconSize(iconSize); + final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, thumbSize); return new Node[]{thumb}; } else { return new Node[]{}; From eb57361b6f1f30f02315e2eade9a8fda0fdbe82f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 9 Jun 2017 11:14:11 +0200 Subject: [PATCH 09/16] suppress compile type warnings --- .../corecomponents/ResultViewerPersistence.java | 2 +- .../corecomponents/ThumbnailViewChildren.java | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java index 148d3aca74..7d03ec65eb 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java @@ -172,7 +172,7 @@ final class ResultViewerPersistence { } /** - * Encapsulate the property sort order and sort rank into onde data bag. + * Encapsulate the property, sort order, and sort rank into one data bag. */ static class SortCriterion { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index d9a002ae71..5fb210ee0b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -32,6 +32,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.SortOrder; @@ -163,17 +164,25 @@ class ThumbnailViewChildren extends Children.Keys { } private Comparator getCriterionComparator(SortCriterion criterion) { + @SuppressWarnings("unchecked") Comparator c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()), Comparator.nullsFirst(Comparator.naturalOrder())); return criterion.getSortOrder() == SortOrder.ASCENDING ? c : c.reversed(); } + @SuppressWarnings("rawtypes") private Comparable getPropertyValue(Node node, Node.Property prop) { for (Node.PropertySet ps : node.getPropertySets()) { for (Node.Property p : ps.getProperties()) { if (p.equals(prop)) { try { - return (Comparable) p.getValue(); + if (p.getValue() instanceof Comparable) { + return (Comparable) p.getValue(); + } else { + //if the value is not comparable use its string representation + return p.getValue().toString(); + } + } catch (IllegalAccessException | InvocationTargetException ex) { Exceptions.printStackTrace(ex); } @@ -221,7 +230,7 @@ class ThumbnailViewChildren extends Children.Keys { */ private class ThumbnailViewNode extends FilterNode { - private final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); + private final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); From dc23cec4a6cf0ca72decf3282937ab2e259e0316 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 9 Jun 2017 13:26:49 +0200 Subject: [PATCH 10/16] more cleanup --- .../DataResultViewerThumbnail.java | 11 ++- .../corecomponents/ThumbnailViewChildren.java | 71 +++++++++++-------- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 5d53ddad09..eac5a78220 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -78,7 +78,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { private int curPage; private int totalPages; private int curPageImages; - private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; + private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; private final PageUpdater pageUpdater = new PageUpdater(); private TableFilterNode tfn; private ThumbnailViewChildren tvc; @@ -296,10 +296,10 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { break; } - if (iconSize != newIconSize) { - iconSize = newIconSize; + if (thumbSize != newIconSize) { + thumbSize = newIconSize; Node root = em.getRootContext(); - ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(iconSize); + ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); @@ -391,8 +391,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { * produce ThumbnailPageNodes with ThumbnailViewNode children * from the child nodes of the given node. */ - tvc = new ThumbnailViewChildren(givenNode); - tvc.setThumbsSize(iconSize); + tvc = new ThumbnailViewChildren(givenNode,thumbSize); final Node root = new AbstractNode(tvc); pageUpdater.setRoot(root); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 5fb210ee0b..2521f5a44e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -32,7 +32,6 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.SortOrder; @@ -53,6 +52,7 @@ import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadS import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; +import static org.sleuthkit.autopsy.corecomponents.Bundle.*; /** * Complementary class to ThumbnailViewNode. Children node factory. Wraps around @@ -66,6 +66,9 @@ import org.sleuthkit.datamodel.Content; */ class ThumbnailViewChildren extends Children.Keys { + @NbBundle.Messages("ThumbnailViewChildren.progress.cancelling=(Cancelling)") + private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling(); + private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName()); static final int IMAGES_PER_PAGE = 200; @@ -73,17 +76,19 @@ class ThumbnailViewChildren extends Children.Keys { private final HashMap> pages = new HashMap<>(); private int totalImages = 0; private int totalPages = 0; - private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; + private int thumbSize; /** * The constructor * - * @param parent The node which is the parent of this children. + * @param parent The node which is the parent of this children. + * @param thumbSize The hight and/or width of the thumbnails in pixels. */ - ThumbnailViewChildren(Node parent) { + ThumbnailViewChildren(Node parent, int thumbSize) { super(true); //support lazy loading this.parent = parent; + this.thumbSize = thumbSize; } @Override @@ -170,7 +175,7 @@ class ThumbnailViewChildren extends Children.Keys { return criterion.getSortOrder() == SortOrder.ASCENDING ? c : c.reversed(); } - @SuppressWarnings("rawtypes") + @SuppressWarnings("rawtypes") private Comparable getPropertyValue(Node node, Node.Property prop) { for (Node.PropertySet ps : node.getPropertySets()) { for (Node.Property p : ps.getProperties()) { @@ -232,11 +237,15 @@ class ThumbnailViewChildren extends Children.Keys { private final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName()); - private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); + private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); //NOI18N private SoftReference thumbCache = null; private int thumbSize; + int getThumbSize() { + return thumbSize; + } + private ThumbnailLoadTask thumbTask; private Timer timer; @@ -291,6 +300,27 @@ class ThumbnailViewChildren extends Children.Keys { thumbTask = null; } + private void completionCallback(ThumbnailLoadTask task) { + try { + thumbCache = new SoftReference<>(task.get()); + fireIconChange(); + } catch (CancellationException ex) { + //Task was cancelled, do nothing + } catch (InterruptedException | ExecutionException ex) { + if (ex.getCause() instanceof CancellationException) { + } else { + logger.log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS + } + } finally { + + if (timer != null) { + timer.stop(); + timer = null; + } + thumbTask = null; + } + } + private class ThumbnailLoadTask extends SwingWorker { private final Content content; @@ -316,34 +346,19 @@ class ThumbnailViewChildren extends Children.Keys { } private void cancel() { - SwingUtilities.invokeLater(() -> progressHandle.setDisplayName(progressText + " (Cancelling)")); + SwingUtilities.invokeLater(() -> progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX)); } @Override protected void done() { super.done(); - try { - thumbCache = new SoftReference<>(super.get()); - fireIconChange(); - } catch (CancellationException ex) { - //Task was cancelled, do nothing - } catch (InterruptedException | ExecutionException ex) { - if (ex.getCause() instanceof CancellationException) { - } else { - logger.log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS + synchronized (progressHandle) { + if (started) { + progressHandle.finish(); } - } finally { - synchronized (progressHandle) { - if (started) { - progressHandle.finish(); - } - } - if (timer != null) { - timer.stop(); - timer = null; - } - thumbTask = null; } + + completionCallback(); } } @@ -362,7 +377,7 @@ class ThumbnailViewChildren extends Children.Keys { private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node, Content content) { if (executor.isShutdown() == false) { - ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content, node.thumbSize); + ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content, node.getThumbSize()); tasks.add(task); executor.submit(task); return task; From 26801b657be0071ca9a8c5ceda05203e7b99a34b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 9 Jun 2017 15:26:59 +0200 Subject: [PATCH 11/16] cleanup and refactoring --- .../corecomponents/ThumbnailViewChildren.java | 229 ++++++++---------- 1 file changed, 105 insertions(+), 124 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 2521f5a44e..148e383e70 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.corecomponents; +import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.awt.Image; import java.awt.Toolkit; @@ -26,7 +27,6 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -34,6 +34,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; @@ -52,30 +54,31 @@ import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadS import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; -import static org.sleuthkit.autopsy.corecomponents.Bundle.*; /** * Complementary class to ThumbnailViewNode. Children node factory. Wraps around * original data result children nodes of the passed in parent node, and creates - * filter nodes for the supported children nodes, adding the bitmap data. If - * original nodes are lazy loaded, this will support lazy loading. Currently, we - * add a page node hierarchy to divide children nodes into "pages". + * filter nodes for the supported children nodes, adding the thumbnail. If + * original nodes are lazy loaded, this will support lazy loading. We add a page + * node hierarchy to divide children nodes into "pages". * * Filter-node like class, but adds additional hierarchy (pages) as parents of * the filtered nodes. */ class ThumbnailViewChildren extends Children.Keys { - @NbBundle.Messages("ThumbnailViewChildren.progress.cancelling=(Cancelling)") - private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling(); - private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName()); + @NbBundle.Messages("ThumbnailViewChildren.progress.cancelling=(Cancelling)") + private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling(); static final int IMAGES_PER_PAGE = 200; + + private final ExecutorService executor = Executors.newFixedThreadPool(4, + new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build()); + private final List tasks = new ArrayList<>(); + private final Node parent; - private final HashMap> pages = new HashMap<>(); - private int totalImages = 0; - private int totalPages = 0; + private final List> pages = new ArrayList<>(); private int thumbSize; /** @@ -95,62 +98,37 @@ class ThumbnailViewChildren extends Children.Keys { protected void addNotify() { super.addNotify(); - setupKeys(); - } + /* + * TODO: When lazy loading of original nodes is fixed, we should be + * asking the datamodel for the children instead and not counting the + * children nodes (which might not be preloaded at this point). + */ + // get list of supported children sorted by persisted criteria + final List suppContent = + Stream.of(parent.getChildren().getNodes()) + .filter(ThumbnailViewChildren::isSupported) + .sorted(getComparator()) + .collect(Collectors.toList()); - int getTotalPages() { - return totalPages; - } - - int getTotalImages() { - return totalImages; - } - - private void setupKeys() { - //divide the supported content into buckets - totalImages = 0; - //TODO when lazy loading of original nodes is fixed - //we should be asking the datamodel for the children instead - //and not counting the children nodes (which might not be preloaded at this point) - final List suppContent = new ArrayList<>(); - for (Node child : parent.getChildren().getNodes()) { - if (isSupported(child)) { - ++totalImages; - suppContent.add(child); - } - } - //sort suppContent! - Collections.sort(suppContent, getComparator()); - - if (totalImages == 0) { + if (suppContent.isEmpty()) { + //if there are no images, there is nothing more to do return; } - totalPages = 0; - if (totalImages < IMAGES_PER_PAGE) { - totalPages = 1; - } else { - totalPages = totalImages / IMAGES_PER_PAGE; - if (totalPages % totalImages != 0) { - ++totalPages; - } - } + //divide the supported content into buckets + pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE)); - int prevImages = 0; - for (int page = 1; page <= totalPages; ++page) { - int toAdd = Math.min(IMAGES_PER_PAGE, totalImages - prevImages); - List pageContent = suppContent.subList(prevImages, prevImages + toAdd); - pages.put(page, pageContent); - prevImages += toAdd; - } - - Integer[] pageNums = new Integer[totalPages]; - for (int i = 0; i < totalPages; ++i) { - pageNums[i] = i + 1; - } - setKeys(pageNums); + //the keys are just the indices into the pages list. + setKeys(IntStream.rangeClosed(0, pages.size()).boxed().collect(Collectors.toList())); } + /** + * Get a comparator for the child nodes loadeded from the persisted + * sort criteria. The comparator is a composite one that applies all the sort + * criteria at once. + * + * @return A Coparator used to sort the child nodes. + */ private synchronized Comparator getComparator() { Comparator comp = (node1, node2) -> 0; @@ -201,12 +179,11 @@ class ThumbnailViewChildren extends Children.Keys { protected void removeNotify() { super.removeNotify(); pages.clear(); - totalImages = 0; } @Override protected Node[] createNodes(Integer pageNum) { - return new Node[]{new ThumbnailPageNode(pageNum)}; + return new Node[]{new ThumbnailPageNode(pageNum, pages.get(pageNum))}; } @@ -229,6 +206,23 @@ class ThumbnailViewChildren extends Children.Keys { } } + synchronized void cancelLoadingThumbnails() { + tasks.forEach(ThumbnailLoadTask::cancel); + tasks.clear(); + executor.shutdownNow(); + } + + private synchronized ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node, Content content) { + if (executor.isShutdown() == false) { + ThumbnailLoadTask task = new ThumbnailLoadTask(node, content, node.getThumbSize()); + tasks.add(task); + executor.submit(task); + return task; + } else { + return null; + } + } + /** * Node that wraps around original node and adds the thumbnail representing * the image/video. @@ -241,6 +235,7 @@ class ThumbnailViewChildren extends Children.Keys { private SoftReference thumbCache = null; private int thumbSize; + private final Content content; int getThumbSize() { return thumbSize; @@ -258,6 +253,7 @@ class ThumbnailViewChildren extends Children.Keys { private ThumbnailViewNode(Node wrappedNode, int thumbSize) { super(wrappedNode, FilterNode.Children.LEAF); this.thumbSize = thumbSize; + this.content = this.getLookup().lookup(Content.class); } @Override @@ -278,7 +274,7 @@ class ThumbnailViewChildren extends Children.Keys { if (thumbnail != null) { return thumbnail; } else { - final Content content = this.getLookup().lookup(Content.class); + if (content == null) { return ImageUtils.getDefaultThumbnail(); } @@ -321,83 +317,64 @@ class ThumbnailViewChildren extends Children.Keys { } } - private class ThumbnailLoadTask extends SwingWorker { + } - private final Content content; - private final ProgressHandle progressHandle; - private volatile boolean started = false; - private final String progressText; - private final int thumbSize; + private class ThumbnailLoadTask extends SwingWorker { - ThumbnailLoadTask(Content content, int thumbSize) { - this.content = content; - progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); - progressHandle = ProgressHandle.createHandle(progressText); - this.thumbSize = thumbSize; - } + private final ThumbnailViewNode node; - @Override - protected Image doInBackground() throws Exception { - synchronized (progressHandle) { - progressHandle.start(); - started = true; - } - return ImageUtils.getThumbnail(content, thumbSize); - } - - private void cancel() { - SwingUtilities.invokeLater(() -> progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX)); - } - - @Override - protected void done() { - super.done(); - synchronized (progressHandle) { - if (started) { - progressHandle.finish(); - } - } - - completionCallback(); - } + private final Content content; + private final ProgressHandle progressHandle; + private volatile boolean started = false; + private final String progressText; + private final int thumbSize; + ThumbnailLoadTask(ThumbnailViewNode node, Content content, int thumbSize) { + this.node = node; + this.content = content; + progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); + progressHandle = ProgressHandle.createHandle(progressText); + this.thumbSize = thumbSize; } - } - private final ExecutorService executor = Executors.newFixedThreadPool(4, - new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build()); + @Override + protected Image doInBackground() throws Exception { + synchronized (progressHandle) { + progressHandle.start(); + started = true; + } + return ImageUtils.getThumbnail(content, thumbSize); + } - private final List tasks = new ArrayList<>(); + private void cancel() { + SwingUtilities.invokeLater(() -> progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX)); + } - synchronized void cancelLoadingThumbnails() { - tasks.forEach(ThumbnailViewNode.ThumbnailLoadTask::cancel); - tasks.clear(); - executor.shutdownNow(); - } + @Override + protected void done() { + super.done(); + synchronized (progressHandle) { + if (started) { + progressHandle.finish(); + } + } - private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node, Content content) { - if (executor.isShutdown() == false) { - ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(content, node.getThumbSize()); - tasks.add(task); - executor.submit(task); - return task; - } else { - return null; + node.completionCallback(this); } } /** - * Node representing page node, a parent of image nodes, with a name showing - * children range + * Node representing a page of thumbnails, a parent of image nodes, with a + * name showing children range */ private class ThumbnailPageNode extends AbstractNode { - ThumbnailPageNode(Integer pageNum) { - super(new ThumbnailPageNodeChildren(pages.get(pageNum)), Lookups.singleton(pageNum)); - setName(Integer.toString(pageNum)); - int from = 1 + ((pageNum - 1) * IMAGES_PER_PAGE); - int showImages = Math.min(IMAGES_PER_PAGE, totalImages - (from - 1)); - int to = from + showImages - 1; + private ThumbnailPageNode(Integer pageNum, List childNodes) { + + super(new ThumbnailPageNodeChildren(childNodes), Lookups.singleton(pageNum)); + setName(Integer.toString(pageNum + 1)); + int from = 1 + (pageNum * IMAGES_PER_PAGE); + int to = from + ((ThumbnailPageNodeChildren) getChildren()).getChildCount() - 1; setDisplayName(from + "-" + to); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS @@ -433,6 +410,10 @@ class ThumbnailViewChildren extends Children.Keys { setKeys(Collections.emptyList()); } + int getChildCount() { + return keyNodes.size(); + } + @Override protected Node[] createNodes(Node wrapped) { if (wrapped != null) { From 66b210a3df4bb6714674d1557e8aa23ff940e110 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 9 Jun 2017 16:19:44 +0200 Subject: [PATCH 12/16] adjust cancellation checks, especially adding important ones in VideoUtils.generateVideoThumbnail --- .../autopsy/coreutils/ImageUtils.java | 36 ++++-- .../autopsy/coreutils/VideoUtils.java | 110 ++++++++++-------- 2 files changed, 85 insertions(+), 61 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 0480ffc5a5..6ac6f35b24 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -654,19 +654,25 @@ public class ImageUtils { @Override protected javafx.scene.image.Image call() throws Exception { - if (isGIF(file)) { - return readImage(); - } if (isCancelled()) { return null; } + if (isGIF(file)) { + return readImage(); + } // If a thumbnail file is already saved locally, just read that. if (cacheFile != null) { synchronized (cacheFile) { if (cacheFile.exists()) { try { + if (isCancelled()) { + return null; + } BufferedImage cachedThumbnail = ImageIO.read(cacheFile); + if (isCancelled()) { + return null; + } if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) { return SwingFXUtils.toFXImage(cachedThumbnail, null); } @@ -678,15 +684,14 @@ public class ImageUtils { } } - if (isCancelled()) { - return null; - } - //There was no correctly-sized cached thumbnail so make one. BufferedImage thumbnail = null; if (VideoUtils.isVideoThumbnailSupported(file)) { if (OPEN_CV_LOADED) { updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName())); + if (isCancelled()) { + return null; + } thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); } if (null == thumbnail) { @@ -698,6 +703,9 @@ public class ImageUtils { } } else { + if (isCancelled()) { + return null; + } //read the image into a buffered image. //TODO: I don't like this, we just converted it from BufferedIamge to fx Image -jm BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null); @@ -712,6 +720,9 @@ public class ImageUtils { } //resize, or if that fails, crop it try { + if (isCancelled()) { + return null; + } thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); } catch (IllegalArgumentException | OutOfMemoryError e) { // if resizing does not work due to extreme aspect ratio or oom, crop the image instead. @@ -723,6 +734,9 @@ public class ImageUtils { final int cropHeight = Math.min(iconSize, height); final int cropWidth = Math.min(iconSize, width); try { + if (isCancelled()) { + return null; + } if (isCancelled()) { return null; } @@ -841,10 +855,6 @@ public class ImageUtils { } } //fall through to default image reading code if there was an error - if (isCancelled()) { - return null; - } - return getImageProperty(file, "ImageIO could not read {0}: ", imageReader -> { imageReader.addIIOReadProgressListener(ReadImageTaskBase.this); @@ -858,6 +868,9 @@ public class ImageUtils { BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0)); param.setDestination(bufferedImage); try { + if (isCancelled()) { + return null; + } bufferedImage = imageReader.read(0, param); //should always be same bufferedImage object } catch (IOException iOException) { LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS @@ -887,6 +900,7 @@ public class ImageUtils { public boolean isCancelled() { if (Thread.interrupted()) { this.cancel(true); + return true; } return super.isCancelled(); } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java index 871444fab1..81670a878c 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java @@ -107,6 +107,9 @@ public class VideoUtils { progress.start(100); try { Files.createParentDirs(tempFile); + if (Thread.interrupted()) { + return null; + } ContentUtils.writeToFile(file, tempFile, progress, null, true); } catch (IOException ex) { LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS @@ -114,62 +117,69 @@ public class VideoUtils { progress.finish(); } } - VideoCapture videoFile = new VideoCapture(); // will contain the video - - if (!videoFile.open(tempFile.toString())) { - LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS - return null; - } - double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second - double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames - if (fps <= 0 || totalFrames <= 0) { - LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS - return null; - } - double milliseconds = 1000 * (totalFrames / fps); //total milliseconds - - double timestamp = Math.min(milliseconds, 500); //default time to check for is 500ms, unless the files is extremely small - - int framkeskip = Double.valueOf(Math.floor((milliseconds - timestamp) / (THUMB_COLUMNS * THUMB_ROWS))).intValue(); - - Mat imageMatrix = new Mat(); BufferedImage bufferedImage = null; - for (int x = 0; x < THUMB_COLUMNS; x++) { - for (int y = 0; y < THUMB_ROWS; y++) { - if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) { - LOGGER.log(Level.WARNING, "Error seeking to " + timestamp + "ms in {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS - break; // if we can't set the time, return black for that frame - } - //read the frame into the image/matrix - if (!videoFile.read(imageMatrix)) { - LOGGER.log(Level.WARNING, "Error reading frames at " + timestamp + "ms from {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS - break; //if the image for some reason is bad, return black for that frame - } + try { - if (bufferedImage == null) { - bufferedImage = new BufferedImage(imageMatrix.cols() * THUMB_COLUMNS, imageMatrix.rows() * THUMB_ROWS, BufferedImage.TYPE_3BYTE_BGR); - } - - byte[] data = new byte[imageMatrix.rows() * imageMatrix.cols() * (int) (imageMatrix.elemSize())]; - imageMatrix.get(0, 0, data); //copy the image to data - - //todo: this looks like we are swapping the first and third channels. so we can use BufferedImage.TYPE_3BYTE_BGR - if (imageMatrix.channels() == 3) { - for (int k = 0; k < data.length; k += 3) { - byte temp = data[k]; - data[k] = data[k + 2]; - data[k + 2] = temp; - } - } - - bufferedImage.getRaster().setDataElements(imageMatrix.cols() * x, imageMatrix.rows() * y, imageMatrix.cols(), imageMatrix.rows(), data); + if (!videoFile.open(tempFile.toString())) { + LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS + return null; } + double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second + double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames + if (fps <= 0 || totalFrames <= 0) { + LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS + return null; + } + double milliseconds = 1000 * (totalFrames / fps); //total milliseconds + + double timestamp = Math.min(milliseconds, 500); //default time to check for is 500ms, unless the files is extremely small + + int framkeskip = Double.valueOf(Math.floor((milliseconds - timestamp) / (THUMB_COLUMNS * THUMB_ROWS))).intValue(); + + Mat imageMatrix = new Mat(); + + for (int x = 0; x < THUMB_COLUMNS; x++) { + for (int y = 0; y < THUMB_ROWS; y++) { + if (Thread.interrupted()) { + return null; + } + if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) { + LOGGER.log(Level.WARNING, "Error seeking to " + timestamp + "ms in {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS + break; // if we can't set the time, return black for that frame + } + //read the frame into the image/matrix + if (!videoFile.read(imageMatrix)) { + LOGGER.log(Level.WARNING, "Error reading frames at " + timestamp + "ms from {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS + break; //if the image for some reason is bad, return black for that frame + } + + if (bufferedImage == null) { + bufferedImage = new BufferedImage(imageMatrix.cols() * THUMB_COLUMNS, imageMatrix.rows() * THUMB_ROWS, BufferedImage.TYPE_3BYTE_BGR); + } + + byte[] data = new byte[imageMatrix.rows() * imageMatrix.cols() * (int) (imageMatrix.elemSize())]; + imageMatrix.get(0, 0, data); //copy the image to data + + //todo: this looks like we are swapping the first and third channels. so we can use BufferedImage.TYPE_3BYTE_BGR + if (imageMatrix.channels() == 3) { + for (int k = 0; k < data.length; k += 3) { + byte temp = data[k]; + data[k] = data[k + 2]; + data[k + 2] = temp; + } + } + + bufferedImage.getRaster().setDataElements(imageMatrix.cols() * x, imageMatrix.rows() * y, imageMatrix.cols(), imageMatrix.rows(), data); + } + } + } finally { + videoFile.release(); // close the file} + } + if (Thread.interrupted()) { + return null; } - - videoFile.release(); // close the file - return bufferedImage == null ? null : ScalrWrapper.resizeFast(bufferedImage, iconSize); } } From f2fe144e1bb3eaac9cefe1157b32fd719602ab33 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Sun, 11 Jun 2017 10:59:06 +0200 Subject: [PATCH 13/16] more refactoring ThumbnailLoadTask no longer a swingworker --- .../corecomponents/ThumbnailViewChildren.java | 135 ++++++++---------- 1 file changed, 59 insertions(+), 76 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 148e383e70..590ac5db16 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -28,17 +28,18 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import javax.swing.SortOrder; import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import javax.swing.Timer; import org.apache.commons.lang3.StringUtils; import org.netbeans.api.progress.ProgressHandle; @@ -75,7 +76,7 @@ class ThumbnailViewChildren extends Children.Keys { private final ExecutorService executor = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build()); - private final List tasks = new ArrayList<>(); + private final List tasks = new ArrayList<>(); private final Node parent; private final List> pages = new ArrayList<>(); @@ -119,12 +120,12 @@ class ThumbnailViewChildren extends Children.Keys { pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE)); //the keys are just the indices into the pages list. - setKeys(IntStream.rangeClosed(0, pages.size()).boxed().collect(Collectors.toList())); + setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList())); } /** - * Get a comparator for the child nodes loadeded from the persisted - * sort criteria. The comparator is a composite one that applies all the sort + * Get a comparator for the child nodes loadeded from the persisted sort + * criteria. The comparator is a composite one that applies all the sort * criteria at once. * * @return A Coparator used to sort the child nodes. @@ -207,14 +208,14 @@ class ThumbnailViewChildren extends Children.Keys { } synchronized void cancelLoadingThumbnails() { - tasks.forEach(ThumbnailLoadTask::cancel); - tasks.clear(); + tasks.forEach(task -> task.cancel(Boolean.TRUE)); executor.shutdownNow(); + tasks.clear(); } - private synchronized ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node, Content content) { + private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) { if (executor.isShutdown() == false) { - ThumbnailLoadTask task = new ThumbnailLoadTask(node, content, node.getThumbSize()); + ThumbnailViewNode.ThumbnailLoadTask task = node.createLoadTask(); tasks.add(task); executor.submit(task); return task; @@ -237,12 +238,8 @@ class ThumbnailViewChildren extends Children.Keys { private int thumbSize; private final Content content; - int getThumbSize() { - return thumbSize; - } - private ThumbnailLoadTask thumbTask; - private Timer timer; + private Timer waitSpinnerTimer; /** * The constructor @@ -265,6 +262,10 @@ class ThumbnailViewChildren extends Children.Keys { @NbBundle.Messages({"# {0} - file name", "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"}) synchronized public Image getIcon(int type) { + if (content == null) { + return ImageUtils.getDefaultThumbnail(); + } + Image thumbnail = null; if (thumbCache != null) { @@ -275,16 +276,13 @@ class ThumbnailViewChildren extends Children.Keys { return thumbnail; } else { - if (content == null) { - return ImageUtils.getDefaultThumbnail(); - } - if (thumbTask == null || thumbTask.isDone()) { - thumbTask = loadThumbnail(ThumbnailViewNode.this, content); + if (thumbTask == null) { + thumbTask = loadThumbnail(ThumbnailViewNode.this); } - if (timer == null) { - timer = new Timer(1, actionEvent -> fireIconChange()); - timer.start(); + if (waitSpinnerTimer == null) { + waitSpinnerTimer = new Timer(1, actionEvent -> fireIconChange()); + waitSpinnerTimer.start(); } return waitingIcon; } @@ -296,70 +294,55 @@ class ThumbnailViewChildren extends Children.Keys { thumbTask = null; } - private void completionCallback(ThumbnailLoadTask task) { - try { - thumbCache = new SoftReference<>(task.get()); - fireIconChange(); - } catch (CancellationException ex) { - //Task was cancelled, do nothing - } catch (InterruptedException | ExecutionException ex) { - if (ex.getCause() instanceof CancellationException) { - } else { - logger.log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS - } - } finally { - - if (timer != null) { - timer.stop(); - timer = null; - } - thumbTask = null; - } + ThumbnailViewNode.ThumbnailLoadTask createLoadTask() { + return new ThumbnailLoadTask(); } - } + private class ThumbnailLoadTask extends FutureTask { - private class ThumbnailLoadTask extends SwingWorker { + private final ProgressHandle progressHandle; + private final String progressText; - private final ThumbnailViewNode node; - - private final Content content; - private final ProgressHandle progressHandle; - private volatile boolean started = false; - private final String progressText; - private final int thumbSize; - - ThumbnailLoadTask(ThumbnailViewNode node, Content content, int thumbSize) { - this.node = node; - this.content = content; - progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); - progressHandle = ProgressHandle.createHandle(progressText); - this.thumbSize = thumbSize; - } - - @Override - protected Image doInBackground() throws Exception { - synchronized (progressHandle) { + ThumbnailLoadTask() { + super(() -> ImageUtils.getThumbnail(content, thumbSize)); + progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); + progressHandle = ProgressHandle.createHandle(progressText); progressHandle.start(); - started = true; } - return ImageUtils.getThumbnail(content, thumbSize); - } - private void cancel() { - SwingUtilities.invokeLater(() -> progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX)); - } + void cancel(Boolean mayInterrupt) { + progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX); + super.cancel(mayInterrupt); + } - @Override - protected void done() { - super.done(); - synchronized (progressHandle) { - if (started) { + @Override + public boolean isCancelled() { + return super.isCancelled() || Thread.interrupted(); //To change body of generated methods, choose Tools | Templates. + } + + @Override + protected void done() { + SwingUtilities.invokeLater(() -> { progressHandle.finish(); - } - } + if (waitSpinnerTimer != null) { + waitSpinnerTimer.stop(); + waitSpinnerTimer = null; + } - node.completionCallback(this); + try { + if (isCancelled() == false) { + thumbCache = new SoftReference<>(get()); + fireIconChange(); + } + } catch (CancellationException ex) { + //Task was cancelled, do nothing + } catch (InterruptedException | ExecutionException ex) { + if (false == (ex.getCause() instanceof CancellationException)) { + logger.log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS + } + } + }); + } } } From e3747238c67a72c5d181425ede20b4bad81e255e Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 12 Jun 2017 14:26:57 +0200 Subject: [PATCH 14/16] tweaks to properly remove finished progressHandles --- .../corecomponents/ThumbnailViewChildren.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 590ac5db16..0192240285 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -43,6 +43,7 @@ import javax.swing.SwingUtilities; import javax.swing.Timer; import org.apache.commons.lang3.StringUtils; import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.FilterNode; @@ -74,7 +75,7 @@ class ThumbnailViewChildren extends Children.Keys { private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling(); static final int IMAGES_PER_PAGE = 200; - private final ExecutorService executor = Executors.newFixedThreadPool(4, + private final ExecutorService executor = Executors.newFixedThreadPool(3, new ThreadFactoryBuilder().setNameFormat("Thumbnail-Loader-%d").build()); private final List tasks = new ArrayList<>(); @@ -215,7 +216,7 @@ class ThumbnailViewChildren extends Children.Keys { private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) { if (executor.isShutdown() == false) { - ThumbnailViewNode.ThumbnailLoadTask task = node.createLoadTask(); + ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask(); tasks.add(task); executor.submit(task); return task; @@ -275,7 +276,6 @@ class ThumbnailViewChildren extends Children.Keys { if (thumbnail != null) { return thumbnail; } else { - if (thumbTask == null) { thumbTask = loadThumbnail(ThumbnailViewNode.this); @@ -294,36 +294,38 @@ class ThumbnailViewChildren extends Children.Keys { thumbTask = null; } - ThumbnailViewNode.ThumbnailLoadTask createLoadTask() { - return new ThumbnailLoadTask(); - } - private class ThumbnailLoadTask extends FutureTask { private final ProgressHandle progressHandle; private final String progressText; + private boolean cancelled = false; ThumbnailLoadTask() { super(() -> ImageUtils.getThumbnail(content, thumbSize)); progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName()); - progressHandle = ProgressHandle.createHandle(progressText); + + progressHandle = ProgressHandleFactory.createSystemHandle(progressText); + progressHandle.setInitialDelay(500); progressHandle.start(); } - void cancel(Boolean mayInterrupt) { - progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX); + synchronized void cancel(Boolean mayInterrupt) { + cancelled = true; +// progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX); + progressHandle.suspend(progressText + " " + CANCELLING_POSTIX); super.cancel(mayInterrupt); } @Override - public boolean isCancelled() { - return super.isCancelled() || Thread.interrupted(); //To change body of generated methods, choose Tools | Templates. + synchronized public boolean isCancelled() { + return cancelled || super.isCancelled(); //To change body of generated methods, choose Tools | Templates. } @Override - protected void done() { + synchronized protected void done() { + progressHandle.finish(); SwingUtilities.invokeLater(() -> { - progressHandle.finish(); + if (waitSpinnerTimer != null) { waitSpinnerTimer.stop(); waitSpinnerTimer = null; From 52d1076befa02fdce2df3c6c8ecf5e191711f94c Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 12 Jun 2017 14:39:19 +0200 Subject: [PATCH 15/16] more comments and cleanup --- .../corecomponents/ThumbnailViewChildren.java | 71 +++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 0192240285..bc16b97c35 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -58,11 +57,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; /** - * Complementary class to ThumbnailViewNode. Children node factory. Wraps around - * original data result children nodes of the passed in parent node, and creates - * filter nodes for the supported children nodes, adding the thumbnail. If - * original nodes are lazy loaded, this will support lazy loading. We add a page - * node hierarchy to divide children nodes into "pages". + * Wraps around original data result children nodes of the passed in parent + * node, and creates filter nodes for the supported children nodes, adding the + * thumbnail. If original nodes are lazy loaded, this will support lazy loading. + * We add a page node hierarchy to divide children nodes into "pages". * * Filter-node like class, but adds additional hierarchy (pages) as parents of * the filtered nodes. @@ -125,14 +123,14 @@ class ThumbnailViewChildren extends Children.Keys { } /** - * Get a comparator for the child nodes loadeded from the persisted sort + * Get a comparator for the child nodes loaded from the persisted sort * criteria. The comparator is a composite one that applies all the sort * criteria at once. * - * @return A Coparator used to sort the child nodes. + * @return A Comparator used to sort the child nodes. */ private synchronized Comparator getComparator() { - Comparator comp = (node1, node2) -> 0; + Comparator comp = (node1, node2) -> 0; //eveything is equal. if (!(parent instanceof TableFilterNode)) { return comp; @@ -143,11 +141,17 @@ class ThumbnailViewChildren extends Children.Keys { return sortCriteria.stream() .map(this::getCriterionComparator) .collect(Collectors.reducing(Comparator::thenComparing)) - .orElse(comp); - + .orElse(comp); // default to unordered if nothing is persisted } } + /** + * Make a comparator from the given criterion + * + * @param criterion The criterion to make a comparator for. + * + * @return The comparator for the given criterion. + */ private Comparator getCriterionComparator(SortCriterion criterion) { @SuppressWarnings("unchecked") Comparator c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()), @@ -155,6 +159,14 @@ class ThumbnailViewChildren extends Children.Keys { return criterion.getSortOrder() == SortOrder.ASCENDING ? c : c.reversed(); } + /** + * Get the value of the given property from the given node. + * + * @param node The node to get the value from. + * @param prop The property to get the value of. + * + * @return The value of the property in the node. + */ @SuppressWarnings("rawtypes") private Comparable getPropertyValue(Node node, Node.Property prop) { for (Node.PropertySet ps : node.getPropertySets()) { @@ -267,31 +279,32 @@ class ThumbnailViewChildren extends Children.Keys { return ImageUtils.getDefaultThumbnail(); } - Image thumbnail = null; - if (thumbCache != null) { - thumbnail = thumbCache.get(); + Image thumbnail = thumbCache.get(); + if (thumbnail != null) { + return thumbnail; + } } - if (thumbnail != null) { - return thumbnail; - } else { - if (thumbTask == null) { - thumbTask = loadThumbnail(ThumbnailViewNode.this); + if (thumbTask == null) { + thumbTask = loadThumbnail(ThumbnailViewNode.this); - } - if (waitSpinnerTimer == null) { - waitSpinnerTimer = new Timer(1, actionEvent -> fireIconChange()); - waitSpinnerTimer.start(); - } - return waitingIcon; } + if (waitSpinnerTimer == null) { + waitSpinnerTimer = new Timer(1, actionEvent -> fireIconChange()); + waitSpinnerTimer.start(); + } + return waitingIcon; } synchronized void setThumbSize(int iconSize) { this.thumbSize = iconSize; thumbCache = null; - thumbTask = null; + if (thumbTask != null) { + thumbTask.cancel(true); + thumbTask = null; + } + } private class ThumbnailLoadTask extends FutureTask { @@ -309,11 +322,11 @@ class ThumbnailViewChildren extends Children.Keys { progressHandle.start(); } - synchronized void cancel(Boolean mayInterrupt) { + @Override + synchronized public boolean cancel(boolean mayInterrupt) { cancelled = true; -// progressHandle.setDisplayName(progressText + " " + CANCELLING_POSTIX); progressHandle.suspend(progressText + " " + CANCELLING_POSTIX); - super.cancel(mayInterrupt); + return super.cancel(mayInterrupt); } @Override From f37346c1dc04e3f7a586f0cb7deb245facfe1a9e Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 12 Jun 2017 14:53:06 +0200 Subject: [PATCH 16/16] remove unused imports --- .../autopsy/corecomponents/DataResultViewerThumbnail.java | 1 - Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java | 1 - 2 files changed, 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index eac5a78220..6d5cd54f5b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -24,7 +24,6 @@ import java.awt.Dialog; import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 6ac6f35b24..eb6a16e6e0 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -40,7 +40,6 @@ import java.util.List; import static java.util.Objects.nonNull; import java.util.SortedSet; import java.util.TreeSet; -import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor;