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.awt.image.BufferedImage;
 
   26 import java.io.IOException;
 
   27 import java.lang.ref.SoftReference;
 
   28 import java.lang.reflect.InvocationTargetException;
 
   29 import java.util.ArrayList;
 
   30 import java.util.Collections;
 
   31 import java.util.Comparator;
 
   32 import java.util.List;
 
   33 import java.util.concurrent.Callable;
 
   34 import java.util.concurrent.CancellationException;
 
   35 import java.util.concurrent.ExecutionException;
 
   36 import java.util.concurrent.ExecutorService;
 
   37 import java.util.concurrent.Executors;
 
   38 import java.util.concurrent.FutureTask;
 
   39 import java.util.logging.Level;
 
   40 import java.util.stream.Collectors;
 
   41 import java.util.stream.IntStream;
 
   42 import java.util.stream.Stream;
 
   43 import javax.imageio.ImageIO;
 
   44 import javax.swing.SwingUtilities;
 
   45 import javax.swing.Timer;
 
   46 import org.apache.commons.lang3.StringUtils;
 
   47 import org.netbeans.api.progress.ProgressHandle;
 
   48 import org.netbeans.api.progress.ProgressHandleFactory;
 
   49 import org.openide.nodes.AbstractNode;
 
   50 import org.openide.nodes.Children;
 
   51 import org.openide.nodes.FilterNode;
 
   52 import org.openide.nodes.Node;
 
   53 import org.openide.util.NbBundle;
 
   54 import org.openide.util.lookup.Lookups;
 
   71 class ThumbnailViewChildren 
extends Children.Keys<Integer> {
 
   73     private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
 
   75     @NbBundle.Messages(
"ThumbnailViewChildren.progress.cancelling=(Cancelling)")
 
   76     private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling();
 
   78     private static Image waitingIcon;
 
   80     static final int IMAGES_PER_PAGE = 200;
 
   82     private final ExecutorService executor = Executors.newFixedThreadPool(3,
 
   83             new ThreadFactoryBuilder().setNameFormat(
"Thumbnail-Loader-%d").build());
 
   84     private final List<ThumbnailViewNode.ThumbnailLoadTask> tasks = 
new ArrayList<>();
 
   86     private final Node parent;
 
   87     private final List<List<Node>> pages = 
new ArrayList<>();
 
   88     private int thumbSize;
 
   94     private static Image getWaitingIcon() {
 
   95         if (waitingIcon == null) {
 
   96             String imgPath = 
"/org/sleuthkit/autopsy/images/working_spinner.gif";
 
   98                 waitingIcon = ImageIO.
read(ThumbnailViewNode.class.getResource(imgPath));    
 
   99             } 
catch (IOException ex) {
 
  100                 logger.log(Level.WARNING, 
"There was an error loading image: " + imgPath, ex);
 
  113     ThumbnailViewChildren(Node parent, 
int thumbSize) {
 
  116         this.parent = parent;
 
  117         this.thumbSize = thumbSize;
 
  121     protected void addNotify() {
 
  130         final List<Node> suppContent
 
  131                 = Stream.of(parent.getChildren().getNodes())
 
  132                         .filter(ThumbnailViewChildren::isSupported)
 
  133                         .sorted(getComparator())
 
  134                         .collect(Collectors.toList());
 
  136         if (suppContent.isEmpty()) {
 
  142         pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
 
  145         setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
 
  155     private synchronized Comparator<Node> getComparator() {
 
  156         Comparator<Node> comp = (node1, node2) -> 0; 
 
  158         if (!(parent instanceof TableFilterNode)) {
 
  161             List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
 
  170             return sortCriteria.stream()
 
  171                     .map(this::getCriterionComparator)
 
  172                     .collect(Collectors.reducing(Comparator::thenComparing))
 
  186     private Comparator<Node> getCriterionComparator(SortCriterion criterion) {
 
  187         @SuppressWarnings(
"unchecked")
 
  188         Comparator<Node> c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()),
 
  189                 Comparator.nullsFirst(Comparator.naturalOrder()));
 
  190         switch (criterion.getSortOrder()) {
 
  208     @SuppressWarnings(
"rawtypes")
 
  209     private Comparable getPropertyValue(Node node, Node.Property<?> prop) {
 
  210         for (Node.PropertySet ps : node.getPropertySets()) {
 
  211             for (Node.Property<?> p : ps.getProperties()) {
 
  212                 if (p.equals(prop)) {
 
  214                         if (p.getValue() instanceof Comparable) {
 
  215                             return (Comparable) p.getValue();
 
  218                             return p.getValue().toString();
 
  221                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  222                         logger.log(Level.WARNING, 
"Error getting value for thumbnail children", ex);
 
  231     protected void removeNotify() {
 
  232         super.removeNotify();
 
  237     protected Node[] createNodes(Integer pageNum) {
 
  238         return new Node[]{
new ThumbnailPageNode(pageNum, pages.get(pageNum))};
 
  242     private static boolean isSupported(Node node) {
 
  244             Content content = node.getLookup().lookup(AbstractFile.class);
 
  245             if (content != null) {
 
  246                 return ImageUtils.thumbnailSupported(content);
 
  252     public void setThumbsSize(
int thumbSize) {
 
  253         this.thumbSize = thumbSize;
 
  254         for (Node page : getNodes()) {
 
  255             for (Node node : page.getChildren().getNodes()) {
 
  256                 ((ThumbnailViewNode) node).setThumbSize(thumbSize);
 
  261     synchronized void cancelLoadingThumbnails() {
 
  262         tasks.forEach(task -> task.cancel(Boolean.TRUE));
 
  263         executor.shutdownNow();
 
  267     private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
 
  268         if (executor.isShutdown() == 
false) {
 
  269             ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
 
  271             executor.submit(task);
 
  300             super(wrappedNode, FilterNode.Children.LEAF);
 
  302             this.content = this.getLookup().lookup(
AbstractFile.class);
 
  307             return StringUtils.abbreviate(super.getDisplayName(), 18);
 
  311         @NbBundle.Messages({
"# {0} - file name",
 
  312             "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
 
  314             if (content == null) {
 
  318             if (thumbCache != null) {
 
  319                 Image thumbnail = thumbCache.get();
 
  320                 if (thumbnail != null) {
 
  325             if (thumbTask == null) {
 
  329             if (waitSpinnerTimer == null) {
 
  330                 waitSpinnerTimer = 
new Timer(1, actionEvent -> fireIconChange());
 
  331                 waitSpinnerTimer.start();
 
  334             return getWaitingIcon();
 
  337         synchronized void setThumbSize(
int iconSize) {
 
  338             this.thumbSize = iconSize;
 
  340             if (thumbTask != null) {
 
  354                 super(
new Callable<Image>() {  
 
  355                     public Image call() {
 
  360                 progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.
getName());
 
  362                 progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
 
  363                 progressHandle.setInitialDelay(500);
 
  364                 progressHandle.start();
 
  368             synchronized public boolean cancel(
boolean mayInterrupt) {
 
  370                 progressHandle.suspend(progressText + 
" " + CANCELLING_POSTIX);
 
  371                 return super.cancel(mayInterrupt);
 
  376                 return cancelled || super.isCancelled(); 
 
  380             synchronized protected void done() {
 
  381                 progressHandle.finish();
 
  382                 SwingUtilities.invokeLater(() -> {
 
  384                     if (waitSpinnerTimer != null) {
 
  385                         waitSpinnerTimer.stop();
 
  386                         waitSpinnerTimer = null;
 
  391                             thumbCache = 
new SoftReference<>(
get());
 
  394                     } 
catch (CancellationException ex) {
 
  396                     } 
catch (InterruptedException | ExecutionException ex) {
 
  397                         if (
false == (ex.getCause() instanceof CancellationException)) {
 
  398                             logger.log(Level.SEVERE, 
"Error getting thumbnail icon for " + content.
getName(), ex); 
 
  415             setName(Integer.toString(pageNum + 1));
 
  416             int from = 1 + (pageNum * IMAGES_PER_PAGE);
 
  418             setDisplayName(from + 
"-" + to);
 
  420             this.setIconBaseWithExtension(
"org/sleuthkit/autopsy/images/Folder-icon.png"); 
 
  449             super.removeNotify();
 
  450             setKeys(Collections.emptyList());
 
  453         int getChildCount() {
 
  454             return keyNodes.size();
 
  459             if (wrapped != null) {
 
  461                 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)
int read(byte[] buf, long offset, long len)
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