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

Copyright © 2012-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.