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