Autopsy  4.13.0
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  */
19 package org.sleuthkit.autopsy.corecomponents;
20 
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;
53 import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadSortCriteria;
56 import org.sleuthkit.datamodel.AbstractFile;
57 import org.sleuthkit.datamodel.Content;
58 
68 class ThumbnailViewChildren extends Children.Keys<Integer> {
69 
70  private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
71 
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;
75 
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<>();
79 
80  private final Node parent;
81  private final List<List<Node>> pages = new ArrayList<>();
82  private int thumbSize;
83 
90  ThumbnailViewChildren(Node parent, int thumbSize) {
91  super(true); //support lazy loading
92 
93  this.parent = parent;
94  this.thumbSize = thumbSize;
95  }
96 
97  @Override
98  protected void addNotify() {
99  super.addNotify();
100 
101  /*
102  * TODO: When lazy loading of original nodes is fixed, we should be
103  * asking the datamodel for the children instead and not counting the
104  * children nodes (which might not be preloaded at this point).
105  */
106  // get list of supported children sorted by persisted criteria
107  final List<Node> suppContent
108  = Stream.of(parent.getChildren().getNodes())
109  .filter(ThumbnailViewChildren::isSupported)
110  .sorted(getComparator())
111  .collect(Collectors.toList());
112 
113  if (suppContent.isEmpty()) {
114  //if there are no images, there is nothing more to do
115  return;
116  }
117 
118  //divide the supported content into buckets
119  pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
120 
121  //the keys are just the indices into the pages list.
122  setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
123  }
124 
132  private synchronized Comparator<Node> getComparator() {
133  Comparator<Node> comp = (node1, node2) -> 0; //eveything is equal.
134 
135  if (!(parent instanceof TableFilterNode)) {
136  return comp;
137  } else {
138  List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
139 
147  return sortCriteria.stream()
148  .map(this::getCriterionComparator)
149  .collect(Collectors.reducing(Comparator::thenComparing))
150  .orElse(comp); // default to unordered if nothing is persisted
151  }
152  }
153 
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()));// Null values go first, unless reversed below.
167  switch (criterion.getSortOrder()) {
168  case DESCENDING:
169  case UNSORTED:
170  return c.reversed();
171  case ASCENDING:
172  default:
173  return c;
174  }
175  }
176 
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)) {
190  try {
191  if (p.getValue() instanceof Comparable) {
192  return (Comparable) p.getValue();
193  } else {
194  //if the value is not comparable use its string representation
195  return p.getValue().toString();
196  }
197 
198  } catch (IllegalAccessException | InvocationTargetException ex) {
199  logger.log(Level.WARNING, "Error getting value for thumbnail children", ex);
200  }
201  }
202  }
203  }
204  return null;
205  }
206 
207  @Override
208  protected void removeNotify() {
209  super.removeNotify();
210  pages.clear();
211  }
212 
213  @Override
214  protected Node[] createNodes(Integer pageNum) {
215  return new Node[]{new ThumbnailPageNode(pageNum, pages.get(pageNum))};
216 
217  }
218 
219  private static boolean isSupported(Node node) {
220  if (node != null) {
221  Content content = node.getLookup().lookup(AbstractFile.class);
222  if (content != null) {
223  return ImageUtils.thumbnailSupported(content);
224  }
225  }
226  return false;
227  }
228 
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);
234  }
235  }
236  }
237 
238  synchronized void cancelLoadingThumbnails() {
239  tasks.forEach(task -> task.cancel(Boolean.TRUE));
240  executor.shutdownNow();
241  tasks.clear();
242  }
243 
244  private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
245  if (executor.isShutdown() == false) {
246  ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
247  tasks.add(task);
248  executor.submit(task);
249  return task;
250  } else {
251  return null;
252  }
253  }
254 
259  private class ThumbnailViewNode extends FilterNode {
260 
261  private final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName());
262 
263  private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); //NOI18N
264 
265  private SoftReference<Image> thumbCache = null;
266  private int thumbSize;
267  private final Content content;
268 
270  private Timer waitSpinnerTimer;
271 
278  private ThumbnailViewNode(Node wrappedNode, int thumbSize) {
279  super(wrappedNode, FilterNode.Children.LEAF);
280  this.thumbSize = thumbSize;
281  this.content = this.getLookup().lookup(AbstractFile.class);
282  }
283 
284  @Override
285  public String getDisplayName() {
286  return StringUtils.abbreviate(super.getDisplayName(), 18);
287  }
288 
289  @Override
290  @NbBundle.Messages({"# {0} - file name",
291  "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
292  synchronized public Image getIcon(int type) {
293  if (content == null) {
295  }
296 
297  if (thumbCache != null) {
298  Image thumbnail = thumbCache.get();
299  if (thumbnail != null) {
300  return thumbnail;
301  }
302  }
303 
304  if (thumbTask == null) {
305  thumbTask = loadThumbnail(ThumbnailViewNode.this);
306 
307  }
308  if (waitSpinnerTimer == null) {
309  waitSpinnerTimer = new Timer(1, actionEvent -> fireIconChange());
310  waitSpinnerTimer.start();
311  }
312  return waitingIcon;
313  }
314 
315  synchronized void setThumbSize(int iconSize) {
316  this.thumbSize = iconSize;
317  thumbCache = null;
318  if (thumbTask != null) {
319  thumbTask.cancel(true);
320  thumbTask = null;
321  }
322 
323  }
324 
325  private class ThumbnailLoadTask extends FutureTask<Image> {
326 
327  private final ProgressHandle progressHandle;
328  private final String progressText;
329  private boolean cancelled = false;
330 
332  super(new Callable<Image>() { //Does not work as lambda expression in dependent projects in IDE
333  public Image call() {
334  return ImageUtils.getThumbnail(content, thumbSize);
335  }
336  });
337  //super(() -> ImageUtils.getThumbnail(content, thumbSize));
338  progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName());
339 
340  progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
341  progressHandle.setInitialDelay(500);
342  progressHandle.start();
343  }
344 
345  @Override
346  synchronized public boolean cancel(boolean mayInterrupt) {
347  cancelled = true;
348  progressHandle.suspend(progressText + " " + CANCELLING_POSTIX);
349  return super.cancel(mayInterrupt);
350  }
351 
352  @Override
353  synchronized public boolean isCancelled() {
354  return cancelled || super.isCancelled(); //To change body of generated methods, choose Tools | Templates.
355  }
356 
357  @Override
358  synchronized protected void done() {
359  progressHandle.finish();
360  SwingUtilities.invokeLater(() -> {
361 
362  if (waitSpinnerTimer != null) {
363  waitSpinnerTimer.stop();
364  waitSpinnerTimer = null;
365  }
366 
367  try {
368  if (isCancelled() == false) {
369  thumbCache = new SoftReference<>(get());
370  fireIconChange();
371  }
372  } catch (CancellationException ex) {
373  //Task was cancelled, do nothing
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); //NON-NLS
377  }
378  }
379  });
380  }
381  }
382  }
383 
388  private class ThumbnailPageNode extends AbstractNode {
389 
390  private ThumbnailPageNode(Integer pageNum, List<Node> childNodes) {
391 
392  super(new ThumbnailPageNodeChildren(childNodes), Lookups.singleton(pageNum));
393  setName(Integer.toString(pageNum + 1));
394  int from = 1 + (pageNum * IMAGES_PER_PAGE);
395  int to = from + ((ThumbnailPageNodeChildren) getChildren()).getChildCount() - 1;
396  setDisplayName(from + "-" + to);
397 
398  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS
399  }
400  }
401 
407  private class ThumbnailPageNodeChildren extends Children.Keys<Node> {
408 
409  /*
410  * wrapped original nodes
411  */
412  private List<Node> keyNodes = null;
413 
414  ThumbnailPageNodeChildren(List<Node> keyNodes) {
415  super(true);
416  this.keyNodes = keyNodes;
417  }
418 
419  @Override
420  protected void addNotify() {
421  super.addNotify();
422  setKeys(keyNodes);
423  }
424 
425  @Override
426  protected void removeNotify() {
427  super.removeNotify();
428  setKeys(Collections.emptyList());
429  }
430 
431  int getChildCount() {
432  return keyNodes.size();
433  }
434 
435  @Override
436  protected Node[] createNodes(Node wrapped) {
437  if (wrapped != null) {
438  final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, thumbSize);
439  return new Node[]{thumb};
440  } else {
441  return new Node[]{};
442  }
443  }
444  }
445 }
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static BufferedImage getThumbnail(Content content, int iconSize)

Copyright © 2012-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.