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