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.SortOrder;
42 import javax.swing.SwingUtilities;
43 import javax.swing.Timer;
44 import org.apache.commons.lang3.StringUtils;
45 import org.netbeans.api.progress.ProgressHandle;
46 import org.netbeans.api.progress.ProgressHandleFactory;
47 import org.openide.nodes.AbstractNode;
48 import org.openide.nodes.Children;
49 import org.openide.nodes.FilterNode;
50 import org.openide.nodes.Node;
51 import org.openide.util.Exceptions;
52 import org.openide.util.NbBundle;
53 import org.openide.util.lookup.Lookups;
70 class ThumbnailViewChildren
extends Children.Keys<Integer> {
72 private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
74 @NbBundle.Messages(
"ThumbnailViewChildren.progress.cancelling=(Cancelling)")
75 private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling();
76 static final int IMAGES_PER_PAGE = 200;
78 private final ExecutorService executor = Executors.newFixedThreadPool(3,
79 new ThreadFactoryBuilder().setNameFormat(
"Thumbnail-Loader-%d").build());
80 private final List<ThumbnailViewNode.ThumbnailLoadTask> tasks =
new ArrayList<>();
82 private final Node parent;
83 private final List<List<Node>> pages =
new ArrayList<>();
84 private int thumbSize;
92 ThumbnailViewChildren(Node parent,
int thumbSize) {
96 this.thumbSize = thumbSize;
100 protected void addNotify() {
109 final List<Node> suppContent
110 = Stream.of(parent.getChildren().getNodes())
111 .filter(ThumbnailViewChildren::isSupported)
112 .sorted(getComparator())
113 .collect(Collectors.toList());
115 if (suppContent.isEmpty()) {
121 pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
124 setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
134 private synchronized Comparator<Node> getComparator() {
135 Comparator<Node> comp = (node1, node2) -> 0;
137 if (!(parent instanceof TableFilterNode)) {
140 List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
143 return sortCriteria.stream()
144 .map(this::getCriterionComparator)
145 .collect(Collectors.reducing(Comparator::thenComparing))
157 private Comparator<Node> getCriterionComparator(SortCriterion criterion) {
158 @SuppressWarnings(
"unchecked")
159 Comparator<Node> c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()),
160 Comparator.nullsFirst(Comparator.naturalOrder()));
161 return criterion.getSortOrder() == SortOrder.ASCENDING ? c : c.reversed();
172 @SuppressWarnings("rawtypes")
173 private Comparable getPropertyValue(Node node, Node.Property<?> prop) {
174 for (Node.PropertySet ps : node.getPropertySets()) {
175 for (Node.Property<?> p : ps.getProperties()) {
176 if (p.equals(prop)) {
178 if (p.getValue() instanceof Comparable) {
179 return (Comparable) p.getValue();
182 return p.getValue().toString();
185 }
catch (IllegalAccessException | InvocationTargetException ex) {
186 Exceptions.printStackTrace(ex);
195 protected void removeNotify() {
196 super.removeNotify();
201 protected Node[] createNodes(Integer pageNum) {
202 return new Node[]{
new ThumbnailPageNode(pageNum, pages.get(pageNum))};
206 private static boolean isSupported(Node node) {
208 Content content = node.getLookup().lookup(AbstractFile.class);
209 if (content != null) {
210 return ImageUtils.thumbnailSupported(content);
216 public void setThumbsSize(
int thumbSize) {
217 this.thumbSize = thumbSize;
218 for (Node page : getNodes()) {
219 for (Node node : page.getChildren().getNodes()) {
220 ((ThumbnailViewNode) node).setThumbSize(thumbSize);
225 synchronized void cancelLoadingThumbnails() {
226 tasks.forEach(task -> task.cancel(Boolean.TRUE));
227 executor.shutdownNow();
231 private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
232 if (executor.isShutdown() ==
false) {
233 ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
235 executor.submit(task);
250 private final Image
waitingIcon = Toolkit.getDefaultToolkit().createImage(
ThumbnailViewNode.class.getResource(
"/org/sleuthkit/autopsy/images/working_spinner.gif"));
266 super(wrappedNode, FilterNode.Children.LEAF);
268 this.content = this.getLookup().lookup(AbstractFile.class);
273 return StringUtils.abbreviate(super.getDisplayName(), 18);
277 @NbBundle.Messages({
"# {0} - file name",
278 "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
280 if (content == null) {
284 if (thumbCache != null) {
285 Image thumbnail = thumbCache.get();
286 if (thumbnail != null) {
291 if (thumbTask == null) {
295 if (waitSpinnerTimer == null) {
296 waitSpinnerTimer =
new Timer(1, actionEvent -> fireIconChange());
297 waitSpinnerTimer.start();
302 synchronized void setThumbSize(
int iconSize) {
303 this.thumbSize = iconSize;
305 if (thumbTask != null) {
319 super(
new Callable<Image>() {
320 public Image call() {
325 progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName());
327 progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
328 progressHandle.setInitialDelay(500);
329 progressHandle.start();
333 synchronized public boolean cancel(
boolean mayInterrupt) {
335 progressHandle.suspend(progressText +
" " + CANCELLING_POSTIX);
336 return super.cancel(mayInterrupt);
341 return cancelled || super.isCancelled();
345 synchronized protected void done() {
346 progressHandle.finish();
347 SwingUtilities.invokeLater(() -> {
349 if (waitSpinnerTimer != null) {
350 waitSpinnerTimer.stop();
351 waitSpinnerTimer = null;
356 thumbCache =
new SoftReference<>(
get());
359 }
catch (CancellationException ex) {
361 }
catch (InterruptedException | ExecutionException ex) {
362 if (
false == (ex.getCause() instanceof CancellationException)) {
363 logger.log(Level.SEVERE,
"Error getting thumbnail icon for " + content.getName(), ex);
380 setName(Integer.toString(pageNum + 1));
381 int from = 1 + (pageNum * IMAGES_PER_PAGE);
383 setDisplayName(from +
"-" + to);
385 this.setIconBaseWithExtension(
"org/sleuthkit/autopsy/images/Folder-icon.png");
414 super.removeNotify();
415 setKeys(Collections.emptyList());
418 int getChildCount() {
419 return keyNodes.size();
424 if (wrapped != null) {
426 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