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.Exceptions;
 
   51 import org.openide.util.NbBundle;
 
   52 import org.openide.util.lookup.Lookups;
 
   69 class ThumbnailViewChildren 
extends Children.Keys<Integer> {
 
   71     private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
 
   73     @NbBundle.Messages(
"ThumbnailViewChildren.progress.cancelling=(Cancelling)")
 
   74     private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling();
 
   75     static final int IMAGES_PER_PAGE = 200;
 
   77     private final ExecutorService executor = Executors.newFixedThreadPool(3,
 
   78             new ThreadFactoryBuilder().setNameFormat(
"Thumbnail-Loader-%d").build());
 
   79     private final List<ThumbnailViewNode.ThumbnailLoadTask> tasks = 
new ArrayList<>();
 
   81     private final Node parent;
 
   82     private final List<List<Node>> pages = 
new ArrayList<>();
 
   83     private int thumbSize;
 
   91     ThumbnailViewChildren(Node parent, 
int thumbSize) {
 
   95         this.thumbSize = thumbSize;
 
   99     protected void addNotify() {
 
  108         final List<Node> suppContent
 
  109                 = Stream.of(parent.getChildren().getNodes())
 
  110                         .filter(ThumbnailViewChildren::isSupported)
 
  111                         .sorted(getComparator())
 
  112                         .collect(Collectors.toList());
 
  114         if (suppContent.isEmpty()) {
 
  120         pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
 
  123         setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
 
  133     private synchronized Comparator<Node> getComparator() {
 
  134         Comparator<Node> comp = (node1, node2) -> 0; 
 
  136         if (!(parent instanceof TableFilterNode)) {
 
  139             List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
 
  148             return sortCriteria.stream()
 
  149                     .map(this::getCriterionComparator)
 
  150                     .collect(Collectors.reducing(Comparator::thenComparing))
 
  164     private Comparator<Node> getCriterionComparator(SortCriterion criterion) {
 
  165         @SuppressWarnings(
"unchecked")
 
  166         Comparator<Node> c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()),
 
  167                 Comparator.nullsFirst(Comparator.naturalOrder()));
 
  168         switch (criterion.getSortOrder()) {
 
  186     @SuppressWarnings(
"rawtypes")
 
  187     private Comparable getPropertyValue(Node node, Node.Property<?> prop) {
 
  188         for (Node.PropertySet ps : node.getPropertySets()) {
 
  189             for (Node.Property<?> p : ps.getProperties()) {
 
  190                 if (p.equals(prop)) {
 
  192                         if (p.getValue() instanceof Comparable) {
 
  193                             return (Comparable) p.getValue();
 
  196                             return p.getValue().toString();
 
  199                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  200                         Exceptions.printStackTrace(ex);
 
  209     protected void removeNotify() {
 
  210         super.removeNotify();
 
  215     protected Node[] createNodes(Integer pageNum) {
 
  216         return new Node[]{
new ThumbnailPageNode(pageNum, pages.get(pageNum))};
 
  220     private static boolean isSupported(Node node) {
 
  222             Content content = node.getLookup().lookup(AbstractFile.class);
 
  223             if (content != null) {
 
  224                 return ImageUtils.thumbnailSupported(content);
 
  230     public void setThumbsSize(
int thumbSize) {
 
  231         this.thumbSize = thumbSize;
 
  232         for (Node page : getNodes()) {
 
  233             for (Node node : page.getChildren().getNodes()) {
 
  234                 ((ThumbnailViewNode) node).setThumbSize(thumbSize);
 
  239     synchronized void cancelLoadingThumbnails() {
 
  240         tasks.forEach(task -> task.cancel(Boolean.TRUE));
 
  241         executor.shutdownNow();
 
  245     private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
 
  246         if (executor.isShutdown() == 
false) {
 
  247             ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
 
  249             executor.submit(task);
 
  264         private final Image 
waitingIcon = Toolkit.getDefaultToolkit().createImage(
ThumbnailViewNode.class.getResource(
"/org/sleuthkit/autopsy/images/working_spinner.gif")); 
 
  280             super(wrappedNode, FilterNode.Children.LEAF);
 
  282             this.content = this.getLookup().lookup(AbstractFile.class);
 
  287             return StringUtils.abbreviate(super.getDisplayName(), 18);
 
  291         @NbBundle.Messages({
"# {0} - file name",
 
  292             "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
 
  294             if (content == null) {
 
  298             if (thumbCache != null) {
 
  299                 Image thumbnail = thumbCache.get();
 
  300                 if (thumbnail != null) {
 
  305             if (thumbTask == null) {
 
  309             if (waitSpinnerTimer == null) {
 
  310                 waitSpinnerTimer = 
new Timer(1, actionEvent -> fireIconChange());
 
  311                 waitSpinnerTimer.start();
 
  316         synchronized void setThumbSize(
int iconSize) {
 
  317             this.thumbSize = iconSize;
 
  319             if (thumbTask != null) {
 
  333                 super(
new Callable<Image>() {  
 
  334                     public Image call() {
 
  339                 progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName());
 
  341                 progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
 
  342                 progressHandle.setInitialDelay(500);
 
  343                 progressHandle.start();
 
  347             synchronized public boolean cancel(
boolean mayInterrupt) {
 
  349                 progressHandle.suspend(progressText + 
" " + CANCELLING_POSTIX);
 
  350                 return super.cancel(mayInterrupt);
 
  355                 return cancelled || super.isCancelled(); 
 
  359             synchronized protected void done() {
 
  360                 progressHandle.finish();
 
  361                 SwingUtilities.invokeLater(() -> {
 
  363                     if (waitSpinnerTimer != null) {
 
  364                         waitSpinnerTimer.stop();
 
  365                         waitSpinnerTimer = null;
 
  370                             thumbCache = 
new SoftReference<>(
get());
 
  373                     } 
catch (CancellationException ex) {
 
  375                     } 
catch (InterruptedException | ExecutionException ex) {
 
  376                         if (
false == (ex.getCause() instanceof CancellationException)) {
 
  377                             logger.log(Level.SEVERE, 
"Error getting thumbnail icon for " + content.getName(), ex); 
 
  394             setName(Integer.toString(pageNum + 1));
 
  395             int from = 1 + (pageNum * IMAGES_PER_PAGE);
 
  397             setDisplayName(from + 
"-" + to);
 
  399             this.setIconBaseWithExtension(
"org/sleuthkit/autopsy/images/Folder-icon.png"); 
 
  428             super.removeNotify();
 
  429             setKeys(Collections.emptyList());
 
  432         int getChildCount() {
 
  433             return keyNodes.size();
 
  438             if (wrapped != null) {
 
  440                 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