19 package org.sleuthkit.autopsy.corecomponents;
 
   21 import com.google.common.collect.Lists;
 
   22 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
   23 import java.awt.Image;
 
   24 import java.awt.Toolkit;
 
   25 import java.lang.ref.SoftReference;
 
   26 import java.lang.reflect.InvocationTargetException;
 
   27 import java.util.ArrayList;
 
   28 import java.util.Collections;
 
   29 import java.util.Comparator;
 
   30 import java.util.List;
 
   31 import java.util.concurrent.Callable;
 
   32 import java.util.concurrent.CancellationException;
 
   33 import java.util.concurrent.ExecutionException;
 
   34 import java.util.concurrent.ExecutorService;
 
   35 import java.util.concurrent.Executors;
 
   36 import java.util.concurrent.FutureTask;
 
   37 import java.util.logging.Level;
 
   38 import java.util.stream.Collectors;
 
   39 import java.util.stream.IntStream;
 
   40 import java.util.stream.Stream;
 
   41 import javax.swing.SwingUtilities;
 
   42 import javax.swing.Timer;
 
   43 import org.apache.commons.lang3.StringUtils;
 
   44 import org.netbeans.api.progress.ProgressHandle;
 
   45 import org.netbeans.api.progress.ProgressHandleFactory;
 
   46 import org.openide.nodes.AbstractNode;
 
   47 import org.openide.nodes.Children;
 
   48 import org.openide.nodes.FilterNode;
 
   49 import org.openide.nodes.Node;
 
   50 import org.openide.util.NbBundle;
 
   51 import org.openide.util.lookup.Lookups;
 
   68 class ThumbnailViewChildren 
extends Children.Keys<Integer> {
 
   70     private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
 
   72     @NbBundle.Messages(
"ThumbnailViewChildren.progress.cancelling=(Cancelling)")
 
   73     private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling();
 
   74     static final int IMAGES_PER_PAGE = 200;
 
   76     private final ExecutorService executor = Executors.newFixedThreadPool(3,
 
   77             new ThreadFactoryBuilder().setNameFormat(
"Thumbnail-Loader-%d").build());
 
   78     private final List<ThumbnailViewNode.ThumbnailLoadTask> tasks = 
new ArrayList<>();
 
   80     private final Node parent;
 
   81     private final List<List<Node>> pages = 
new ArrayList<>();
 
   82     private int thumbSize;
 
   90     ThumbnailViewChildren(Node parent, 
int thumbSize) {
 
   94         this.thumbSize = thumbSize;
 
   98     protected void addNotify() {
 
  107         final List<Node> suppContent
 
  108                 = Stream.of(parent.getChildren().getNodes())
 
  109                         .filter(ThumbnailViewChildren::isSupported)
 
  110                         .sorted(getComparator())
 
  111                         .collect(Collectors.toList());
 
  113         if (suppContent.isEmpty()) {
 
  119         pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
 
  122         setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
 
  132     private synchronized Comparator<Node> getComparator() {
 
  133         Comparator<Node> comp = (node1, node2) -> 0; 
 
  135         if (!(parent instanceof TableFilterNode)) {
 
  138             List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
 
  147             return sortCriteria.stream()
 
  148                     .map(this::getCriterionComparator)
 
  149                     .collect(Collectors.reducing(Comparator::thenComparing))
 
  163     private Comparator<Node> getCriterionComparator(SortCriterion criterion) {
 
  164         @SuppressWarnings(
"unchecked")
 
  165         Comparator<Node> c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()),
 
  166                 Comparator.nullsFirst(Comparator.naturalOrder()));
 
  167         switch (criterion.getSortOrder()) {
 
  185     @SuppressWarnings(
"rawtypes")
 
  186     private Comparable getPropertyValue(Node node, Node.Property<?> prop) {
 
  187         for (Node.PropertySet ps : node.getPropertySets()) {
 
  188             for (Node.Property<?> p : ps.getProperties()) {
 
  189                 if (p.equals(prop)) {
 
  191                         if (p.getValue() instanceof Comparable) {
 
  192                             return (Comparable) p.getValue();
 
  195                             return p.getValue().toString();
 
  198                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  199                         logger.log(Level.WARNING, 
"Error getting value for thumbnail children", ex);
 
  208     protected void removeNotify() {
 
  209         super.removeNotify();
 
  214     protected Node[] createNodes(Integer pageNum) {
 
  215         return new Node[]{
new ThumbnailPageNode(pageNum, pages.get(pageNum))};
 
  219     private static boolean isSupported(Node node) {
 
  221             Content content = node.getLookup().lookup(AbstractFile.class);
 
  222             if (content != null) {
 
  223                 return ImageUtils.thumbnailSupported(content);
 
  229     public void setThumbsSize(
int thumbSize) {
 
  230         this.thumbSize = thumbSize;
 
  231         for (Node page : getNodes()) {
 
  232             for (Node node : page.getChildren().getNodes()) {
 
  233                 ((ThumbnailViewNode) node).setThumbSize(thumbSize);
 
  238     synchronized void cancelLoadingThumbnails() {
 
  239         tasks.forEach(task -> task.cancel(Boolean.TRUE));
 
  240         executor.shutdownNow();
 
  244     private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
 
  245         if (executor.isShutdown() == 
false) {
 
  246             ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
 
  248             executor.submit(task);
 
  263         private final Image 
waitingIcon = Toolkit.getDefaultToolkit().createImage(
ThumbnailViewNode.class.getResource(
"/org/sleuthkit/autopsy/images/working_spinner.gif")); 
 
  279             super(wrappedNode, FilterNode.Children.LEAF);
 
  281             this.content = this.getLookup().lookup(AbstractFile.class);
 
  286             return StringUtils.abbreviate(super.getDisplayName(), 18);
 
  290         @NbBundle.Messages({
"# {0} - file name",
 
  291             "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
 
  293             if (content == null) {
 
  297             if (thumbCache != null) {
 
  298                 Image thumbnail = thumbCache.get();
 
  299                 if (thumbnail != null) {
 
  304             if (thumbTask == null) {
 
  308             if (waitSpinnerTimer == null) {
 
  309                 waitSpinnerTimer = 
new Timer(1, actionEvent -> fireIconChange());
 
  310                 waitSpinnerTimer.start();
 
  315         synchronized void setThumbSize(
int iconSize) {
 
  316             this.thumbSize = iconSize;
 
  318             if (thumbTask != null) {
 
  332                 super(
new Callable<Image>() {  
 
  333                     public Image call() {
 
  338                 progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName());
 
  340                 progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
 
  341                 progressHandle.setInitialDelay(500);
 
  342                 progressHandle.start();
 
  346             synchronized public boolean cancel(
boolean mayInterrupt) {
 
  348                 progressHandle.suspend(progressText + 
" " + CANCELLING_POSTIX);
 
  349                 return super.cancel(mayInterrupt);
 
  354                 return cancelled || super.isCancelled(); 
 
  358             synchronized protected void done() {
 
  359                 progressHandle.finish();
 
  360                 SwingUtilities.invokeLater(() -> {
 
  362                     if (waitSpinnerTimer != null) {
 
  363                         waitSpinnerTimer.stop();
 
  364                         waitSpinnerTimer = null;
 
  369                             thumbCache = 
new SoftReference<>(
get());
 
  372                     } 
catch (CancellationException ex) {
 
  374                     } 
catch (InterruptedException | ExecutionException ex) {
 
  375                         if (
false == (ex.getCause() instanceof CancellationException)) {
 
  376                             logger.log(Level.SEVERE, 
"Error getting thumbnail icon for " + content.getName(), ex); 
 
  393             setName(Integer.toString(pageNum + 1));
 
  394             int from = 1 + (pageNum * IMAGES_PER_PAGE);
 
  396             setDisplayName(from + 
"-" + to);
 
  398             this.setIconBaseWithExtension(
"org/sleuthkit/autopsy/images/Folder-icon.png"); 
 
  427             super.removeNotify();
 
  428             setKeys(Collections.emptyList());
 
  431         int getChildCount() {
 
  432             return keyNodes.size();
 
  437             if (wrapped != null) {
 
  439                 return new Node[]{thumb};
 
final ProgressHandle progressHandle
 
synchronized boolean cancel(boolean mayInterrupt)
 
Node[] createNodes(Node wrapped)
 
ThumbnailLoadTask thumbTask
 
final String progressText
 
synchronized Image getIcon(int type)
 
ThumbnailViewNode(Node wrappedNode, int thumbSize)
 
static Image getDefaultThumbnail()
 
synchronized static Logger getLogger(String name)
 
static BufferedImage getThumbnail(Content content, int iconSize)
 
ThumbnailPageNode(Integer pageNum, List< Node > childNodes)
 
synchronized boolean isCancelled()
 
SoftReference< Image > thumbCache