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.NbBundle;
51 import org.openide.util.lookup.Lookups;
68 class ThumbnailViewChildren
extends Children.Keys<Integer> {
70 private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
72 @NbBundle.Messages(
"ThumbnailViewChildren.progress.cancelling=(Cancelling)")
73 private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling();
74 static final int IMAGES_PER_PAGE = 200;
76 private final ExecutorService executor = Executors.newFixedThreadPool(3,
77 new ThreadFactoryBuilder().setNameFormat(
"Thumbnail-Loader-%d").build());
78 private final List<ThumbnailViewNode.ThumbnailLoadTask> tasks =
new ArrayList<>();
80 private final Node parent;
81 private final List<List<Node>> pages =
new ArrayList<>();
82 private int thumbSize;
90 ThumbnailViewChildren(Node parent,
int thumbSize) {
94 this.thumbSize = thumbSize;
98 protected void addNotify() {
107 final List<Node> suppContent
108 = Stream.of(parent.getChildren().getNodes())
109 .filter(ThumbnailViewChildren::isSupported)
110 .sorted(getComparator())
111 .collect(Collectors.toList());
113 if (suppContent.isEmpty()) {
119 pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
122 setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
132 private synchronized Comparator<Node> getComparator() {
133 Comparator<Node> comp = (node1, node2) -> 0;
135 if (!(parent instanceof TableFilterNode)) {
138 List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
147 return sortCriteria.stream()
148 .map(this::getCriterionComparator)
149 .collect(Collectors.reducing(Comparator::thenComparing))
163 private Comparator<Node> getCriterionComparator(SortCriterion criterion) {
164 @SuppressWarnings(
"unchecked")
165 Comparator<Node> c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()),
166 Comparator.nullsFirst(Comparator.naturalOrder()));
167 switch (criterion.getSortOrder()) {
185 @SuppressWarnings(
"rawtypes")
186 private Comparable getPropertyValue(Node node, Node.Property<?> prop) {
187 for (Node.PropertySet ps : node.getPropertySets()) {
188 for (Node.Property<?> p : ps.getProperties()) {
189 if (p.equals(prop)) {
191 if (p.getValue() instanceof Comparable) {
192 return (Comparable) p.getValue();
195 return p.getValue().toString();
198 }
catch (IllegalAccessException | InvocationTargetException ex) {
199 logger.log(Level.WARNING,
"Error getting value for thumbnail children", ex);
208 protected void removeNotify() {
209 super.removeNotify();
214 protected Node[] createNodes(Integer pageNum) {
215 return new Node[]{
new ThumbnailPageNode(pageNum, pages.get(pageNum))};
219 private static boolean isSupported(Node node) {
221 Content content = node.getLookup().lookup(AbstractFile.class);
222 if (content != null) {
223 return ImageUtils.thumbnailSupported(content);
229 public void setThumbsSize(
int thumbSize) {
230 this.thumbSize = thumbSize;
231 for (Node page : getNodes()) {
232 for (Node node : page.getChildren().getNodes()) {
233 ((ThumbnailViewNode) node).setThumbSize(thumbSize);
238 synchronized void cancelLoadingThumbnails() {
239 tasks.forEach(task -> task.cancel(Boolean.TRUE));
240 executor.shutdownNow();
244 private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
245 if (executor.isShutdown() ==
false) {
246 ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
248 executor.submit(task);
263 private final Image
waitingIcon = Toolkit.getDefaultToolkit().createImage(
ThumbnailViewNode.class.getResource(
"/org/sleuthkit/autopsy/images/working_spinner.gif"));
279 super(wrappedNode, FilterNode.Children.LEAF);
281 this.content = this.getLookup().lookup(AbstractFile.class);
286 return StringUtils.abbreviate(super.getDisplayName(), 18);
290 @NbBundle.Messages({
"# {0} - file name",
291 "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
293 if (content == null) {
297 if (thumbCache != null) {
298 Image thumbnail = thumbCache.get();
299 if (thumbnail != null) {
304 if (thumbTask == null) {
308 if (waitSpinnerTimer == null) {
309 waitSpinnerTimer =
new Timer(1, actionEvent -> fireIconChange());
310 waitSpinnerTimer.start();
315 synchronized void setThumbSize(
int iconSize) {
316 this.thumbSize = iconSize;
318 if (thumbTask != null) {
332 super(
new Callable<Image>() {
333 public Image call() {
338 progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName());
340 progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
341 progressHandle.setInitialDelay(500);
342 progressHandle.start();
346 synchronized public boolean cancel(
boolean mayInterrupt) {
348 progressHandle.suspend(progressText +
" " + CANCELLING_POSTIX);
349 return super.cancel(mayInterrupt);
354 return cancelled || super.isCancelled();
358 synchronized protected void done() {
359 progressHandle.finish();
360 SwingUtilities.invokeLater(() -> {
362 if (waitSpinnerTimer != null) {
363 waitSpinnerTimer.stop();
364 waitSpinnerTimer = null;
369 thumbCache =
new SoftReference<>(
get());
372 }
catch (CancellationException ex) {
374 }
catch (InterruptedException | ExecutionException ex) {
375 if (
false == (ex.getCause() instanceof CancellationException)) {
376 logger.log(Level.SEVERE,
"Error getting thumbnail icon for " + content.getName(), ex);
393 setName(Integer.toString(pageNum + 1));
394 int from = 1 + (pageNum * IMAGES_PER_PAGE);
396 setDisplayName(from +
"-" + to);
398 this.setIconBaseWithExtension(
"org/sleuthkit/autopsy/images/Folder-icon.png");
427 super.removeNotify();
428 setKeys(Collections.emptyList());
431 int getChildCount() {
432 return keyNodes.size();
437 if (wrapped != null) {
439 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