Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ThumbnailViewChildren.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2011-19 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.corecomponents;
20
21import com.google.common.collect.Lists;
22import com.google.common.util.concurrent.ThreadFactoryBuilder;
23import java.awt.Image;
24import java.awt.Toolkit;
25import java.awt.image.BufferedImage;
26import java.io.IOException;
27import java.lang.ref.SoftReference;
28import java.lang.reflect.InvocationTargetException;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32import java.util.List;
33import java.util.concurrent.Callable;
34import java.util.concurrent.CancellationException;
35import java.util.concurrent.ExecutionException;
36import java.util.concurrent.ExecutorService;
37import java.util.concurrent.Executors;
38import java.util.concurrent.FutureTask;
39import java.util.logging.Level;
40import java.util.stream.Collectors;
41import java.util.stream.IntStream;
42import java.util.stream.Stream;
43import javax.imageio.ImageIO;
44import javax.swing.SwingUtilities;
45import javax.swing.Timer;
46import org.apache.commons.lang3.StringUtils;
47import org.netbeans.api.progress.ProgressHandle;
48import org.netbeans.api.progress.ProgressHandleFactory;
49import org.openide.nodes.AbstractNode;
50import org.openide.nodes.Children;
51import org.openide.nodes.FilterNode;
52import org.openide.nodes.Node;
53import org.openide.util.NbBundle;
54import org.openide.util.lookup.Lookups;
55import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion;
56import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadSortCriteria;
57import org.sleuthkit.autopsy.coreutils.ImageUtils;
58import org.sleuthkit.autopsy.coreutils.Logger;
59import org.sleuthkit.datamodel.AbstractFile;
60import org.sleuthkit.datamodel.Content;
61
71class ThumbnailViewChildren extends Children.Keys<Integer> {
72
73 private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
74
75 @NbBundle.Messages("ThumbnailViewChildren.progress.cancelling=(Cancelling)")
76 private static final String CANCELLING_POSTIX = Bundle.ThumbnailViewChildren_progress_cancelling();
77
78 private static Image waitingIcon;
79
80 static final int IMAGES_PER_PAGE = 200;
81
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<>();
85
86 private final Node parent;
87 private final List<List<Node>> pages = new ArrayList<>();
88 private int thumbSize;
89
90
94 private static Image getWaitingIcon() {
95 if (waitingIcon == null) {
96 String imgPath = "/org/sleuthkit/autopsy/images/working_spinner.gif";
97 try {
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);
101 }
102 }
103
104 return waitingIcon;
105 }
106
113 ThumbnailViewChildren(Node parent, int thumbSize) {
114 super(true); //support lazy loading
115
116 this.parent = parent;
117 this.thumbSize = thumbSize;
118 }
119
120 @Override
121 protected void addNotify() {
122 super.addNotify();
123
124 /*
125 * TODO: When lazy loading of original nodes is fixed, we should be
126 * asking the datamodel for the children instead and not counting the
127 * children nodes (which might not be preloaded at this point).
128 */
129 // get list of supported children sorted by persisted criteria
130 final List<Node> suppContent
131 = Stream.of(parent.getChildren().getNodes())
132 .filter(ThumbnailViewChildren::isSupported)
133 .sorted(getComparator())
134 .collect(Collectors.toList());
135
136 if (suppContent.isEmpty()) {
137 //if there are no images, there is nothing more to do
138 return;
139 }
140
141 //divide the supported content into buckets
142 pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
143
144 //the keys are just the indices into the pages list.
145 setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
146 }
147
155 private synchronized Comparator<Node> getComparator() {
156 Comparator<Node> comp = (node1, node2) -> 0; //eveything is equal.
157
158 if (!(parent instanceof TableFilterNode)) {
159 return comp;
160 } else {
161 List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
162
170 return sortCriteria.stream()
171 .map(this::getCriterionComparator)
172 .collect(Collectors.reducing(Comparator::thenComparing))
173 .orElse(comp); // default to unordered if nothing is persisted
174 }
175 }
176
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()));// Null values go first, unless reversed below.
190 switch (criterion.getSortOrder()) {
191 case DESCENDING:
192 case UNSORTED:
193 return c.reversed();
194 case ASCENDING:
195 default:
196 return c;
197 }
198 }
199
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)) {
213 try {
214 if (p.getValue() instanceof Comparable) {
215 return (Comparable) p.getValue();
216 } else {
217 //if the value is not comparable use its string representation
218 return p.getValue().toString();
219 }
220
221 } catch (IllegalAccessException | InvocationTargetException ex) {
222 logger.log(Level.WARNING, "Error getting value for thumbnail children", ex);
223 }
224 }
225 }
226 }
227 return null;
228 }
229
230 @Override
231 protected void removeNotify() {
232 super.removeNotify();
233 pages.clear();
234 }
235
236 @Override
237 protected Node[] createNodes(Integer pageNum) {
238 return new Node[]{new ThumbnailPageNode(pageNum, pages.get(pageNum))};
239
240 }
241
242 private static boolean isSupported(Node node) {
243 if (node != null) {
244 Content content = node.getLookup().lookup(AbstractFile.class);
245 if (content != null) {
246 return ImageUtils.thumbnailSupported(content);
247 }
248 }
249 return false;
250 }
251
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);
257 }
258 }
259 }
260
261 synchronized void cancelLoadingThumbnails() {
262 tasks.forEach(task -> task.cancel(Boolean.TRUE));
263 executor.shutdownNow();
264 tasks.clear();
265 }
266
267 private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
268 if (executor.isShutdown() == false) {
269 ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
270 tasks.add(task);
271 executor.submit(task);
272 return task;
273 } else {
274 return null;
275 }
276 }
277
282 private class ThumbnailViewNode extends FilterNode {
283
284 private final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName());
285
286 private SoftReference<Image> thumbCache = null;
287 private int thumbSize;
288 private final Content content;
289
291 private Timer waitSpinnerTimer;
292
299 private ThumbnailViewNode(Node wrappedNode, int thumbSize) {
300 super(wrappedNode, FilterNode.Children.LEAF);
301 this.thumbSize = thumbSize;
302 this.content = this.getLookup().lookup(AbstractFile.class);
303 }
304
305 @Override
306 public String getDisplayName() {
307 return StringUtils.abbreviate(super.getDisplayName(), 18);
308 }
309
310 @Override
311 @NbBundle.Messages({"# {0} - file name",
312 "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
313 synchronized public Image getIcon(int type) {
314 if (content == null) {
316 }
317
318 if (thumbCache != null) {
319 Image thumbnail = thumbCache.get();
320 if (thumbnail != null) {
321 return thumbnail;
322 }
323 }
324
325 if (thumbTask == null) {
326 thumbTask = loadThumbnail(ThumbnailViewNode.this);
327
328 }
329 if (waitSpinnerTimer == null) {
330 waitSpinnerTimer = new Timer(1, actionEvent -> fireIconChange());
331 waitSpinnerTimer.start();
332 }
333
334 return getWaitingIcon();
335 }
336
337 synchronized void setThumbSize(int iconSize) {
338 this.thumbSize = iconSize;
339 thumbCache = null;
340 if (thumbTask != null) {
341 thumbTask.cancel(true);
342 thumbTask = null;
343 }
344
345 }
346
347 private class ThumbnailLoadTask extends FutureTask<Image> {
348
349 private final ProgressHandle progressHandle;
350 private final String progressText;
351 private boolean cancelled = false;
352
353 ThumbnailLoadTask() {
354 super(new Callable<Image>() { //Does not work as lambda expression in dependent projects in IDE
355 public Image call() {
357 }
358 });
359 //super(() -> ImageUtils.getThumbnail(content, thumbSize));
360 progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName());
361
362 progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
363 progressHandle.setInitialDelay(500);
364 progressHandle.start();
365 }
366
367 @Override
368 synchronized public boolean cancel(boolean mayInterrupt) {
369 cancelled = true;
370 progressHandle.suspend(progressText + " " + CANCELLING_POSTIX);
371 return super.cancel(mayInterrupt);
372 }
373
374 @Override
375 synchronized public boolean isCancelled() {
376 return cancelled || super.isCancelled(); //To change body of generated methods, choose Tools | Templates.
377 }
378
379 @Override
380 synchronized protected void done() {
381 progressHandle.finish();
382 SwingUtilities.invokeLater(() -> {
383
384 if (waitSpinnerTimer != null) {
385 waitSpinnerTimer.stop();
386 waitSpinnerTimer = null;
387 }
388
389 try {
390 if (isCancelled() == false) {
391 thumbCache = new SoftReference<>(get());
392 fireIconChange();
393 }
394 } catch (CancellationException ex) {
395 //Task was cancelled, do nothing
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); //NON-NLS
399 }
400 }
401 });
402 }
403 }
404 }
405
410 private class ThumbnailPageNode extends AbstractNode {
411
412 private ThumbnailPageNode(Integer pageNum, List<Node> childNodes) {
413
414 super(new ThumbnailPageNodeChildren(childNodes), Lookups.singleton(pageNum));
415 setName(Integer.toString(pageNum + 1));
416 int from = 1 + (pageNum * IMAGES_PER_PAGE);
417 int to = from + ((ThumbnailPageNodeChildren) getChildren()).getChildCount() - 1;
418 setDisplayName(from + "-" + to);
419
420 this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS
421 }
422 }
423
429 private class ThumbnailPageNodeChildren extends Children.Keys<Node> {
430
431 /*
432 * wrapped original nodes
433 */
434 private List<Node> keyNodes = null;
435
436 ThumbnailPageNodeChildren(List<Node> keyNodes) {
437 super(true);
438 this.keyNodes = keyNodes;
439 }
440
441 @Override
442 protected void addNotify() {
443 super.addNotify();
444 setKeys(keyNodes);
445 }
446
447 @Override
448 protected void removeNotify() {
449 super.removeNotify();
450 setKeys(Collections.emptyList());
451 }
452
453 int getChildCount() {
454 return keyNodes.size();
455 }
456
457 @Override
458 protected Node[] createNodes(Node wrapped) {
459 if (wrapped != null) {
460 final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, thumbSize);
461 return new Node[]{thumb};
462 } else {
463 return new Node[]{};
464 }
465 }
466 }
467}
static boolean thumbnailSupported(Content content)
static BufferedImage getThumbnail(Content content, int iconSize)
synchronized static Logger getLogger(String name)
Definition Logger.java:124

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.