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)
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