Autopsy  4.5.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-17 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.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;
55 import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadSortCriteria;
58 import org.sleuthkit.datamodel.AbstractFile;
59 import org.sleuthkit.datamodel.Content;
60 
70 class ThumbnailViewChildren extends Children.Keys<Integer> {
71 
72  private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
73 
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;
77 
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<>();
81 
82  private final Node parent;
83  private final List<List<Node>> pages = new ArrayList<>();
84  private int thumbSize;
85 
92  ThumbnailViewChildren(Node parent, int thumbSize) {
93  super(true); //support lazy loading
94 
95  this.parent = parent;
96  this.thumbSize = thumbSize;
97  }
98 
99  @Override
100  protected void addNotify() {
101  super.addNotify();
102 
103  /*
104  * TODO: When lazy loading of original nodes is fixed, we should be
105  * asking the datamodel for the children instead and not counting the
106  * children nodes (which might not be preloaded at this point).
107  */
108  // get list of supported children sorted by persisted criteria
109  final List<Node> suppContent
110  = Stream.of(parent.getChildren().getNodes())
111  .filter(ThumbnailViewChildren::isSupported)
112  .sorted(getComparator())
113  .collect(Collectors.toList());
114 
115  if (suppContent.isEmpty()) {
116  //if there are no images, there is nothing more to do
117  return;
118  }
119 
120  //divide the supported content into buckets
121  pages.addAll(Lists.partition(suppContent, IMAGES_PER_PAGE));
122 
123  //the keys are just the indices into the pages list.
124  setKeys(IntStream.range(0, pages.size()).boxed().collect(Collectors.toList()));
125  }
126 
134  private synchronized Comparator<Node> getComparator() {
135  Comparator<Node> comp = (node1, node2) -> 0; //eveything is equal.
136 
137  if (!(parent instanceof TableFilterNode)) {
138  return comp;
139  } else {
140  List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
141 
142  //make a comparatator that will sort the nodes.
143  return sortCriteria.stream()
144  .map(this::getCriterionComparator)
145  .collect(Collectors.reducing(Comparator::thenComparing))
146  .orElse(comp); // default to unordered if nothing is persisted
147  }
148  }
149 
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();
162  }
163 
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)) {
177  try {
178  if (p.getValue() instanceof Comparable) {
179  return (Comparable) p.getValue();
180  } else {
181  //if the value is not comparable use its string representation
182  return p.getValue().toString();
183  }
184 
185  } catch (IllegalAccessException | InvocationTargetException ex) {
186  Exceptions.printStackTrace(ex);
187  }
188  }
189  }
190  }
191  return null;
192  }
193 
194  @Override
195  protected void removeNotify() {
196  super.removeNotify();
197  pages.clear();
198  }
199 
200  @Override
201  protected Node[] createNodes(Integer pageNum) {
202  return new Node[]{new ThumbnailPageNode(pageNum, pages.get(pageNum))};
203 
204  }
205 
206  private static boolean isSupported(Node node) {
207  if (node != null) {
208  Content content = node.getLookup().lookup(AbstractFile.class);
209  if (content != null) {
210  return ImageUtils.thumbnailSupported(content);
211  }
212  }
213  return false;
214  }
215 
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);
221  }
222  }
223  }
224 
225  synchronized void cancelLoadingThumbnails() {
226  tasks.forEach(task -> task.cancel(Boolean.TRUE));
227  executor.shutdownNow();
228  tasks.clear();
229  }
230 
231  private synchronized ThumbnailViewNode.ThumbnailLoadTask loadThumbnail(ThumbnailViewNode node) {
232  if (executor.isShutdown() == false) {
233  ThumbnailViewNode.ThumbnailLoadTask task = node.new ThumbnailLoadTask();
234  tasks.add(task);
235  executor.submit(task);
236  return task;
237  } else {
238  return null;
239  }
240  }
241 
246  private class ThumbnailViewNode extends FilterNode {
247 
248  private final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName());
249 
250  private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif")); //NOI18N
251 
252  private SoftReference<Image> thumbCache = null;
253  private int thumbSize;
254  private final Content content;
255 
257  private Timer waitSpinnerTimer;
258 
265  private ThumbnailViewNode(Node wrappedNode, int thumbSize) {
266  super(wrappedNode, FilterNode.Children.LEAF);
267  this.thumbSize = thumbSize;
268  this.content = this.getLookup().lookup(AbstractFile.class);
269  }
270 
271  @Override
272  public String getDisplayName() {
273  return StringUtils.abbreviate(super.getDisplayName(), 18);
274  }
275 
276  @Override
277  @NbBundle.Messages({"# {0} - file name",
278  "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
279  synchronized public Image getIcon(int type) {
280  if (content == null) {
282  }
283 
284  if (thumbCache != null) {
285  Image thumbnail = thumbCache.get();
286  if (thumbnail != null) {
287  return thumbnail;
288  }
289  }
290 
291  if (thumbTask == null) {
292  thumbTask = loadThumbnail(ThumbnailViewNode.this);
293 
294  }
295  if (waitSpinnerTimer == null) {
296  waitSpinnerTimer = new Timer(1, actionEvent -> fireIconChange());
297  waitSpinnerTimer.start();
298  }
299  return waitingIcon;
300  }
301 
302  synchronized void setThumbSize(int iconSize) {
303  this.thumbSize = iconSize;
304  thumbCache = null;
305  if (thumbTask != null) {
306  thumbTask.cancel(true);
307  thumbTask = null;
308  }
309 
310  }
311 
312  private class ThumbnailLoadTask extends FutureTask<Image> {
313 
314  private final ProgressHandle progressHandle;
315  private final String progressText;
316  private boolean cancelled = false;
317 
319  super(new Callable<Image>() { //Does not work as lambda expression in dependent projects in IDE
320  public Image call() {
321  return ImageUtils.getThumbnail(content, thumbSize);
322  }
323  });
324  //super(() -> ImageUtils.getThumbnail(content, thumbSize));
325  progressText = Bundle.ThumbnailViewNode_progressHandle_text(content.getName());
326 
327  progressHandle = ProgressHandleFactory.createSystemHandle(progressText);
328  progressHandle.setInitialDelay(500);
329  progressHandle.start();
330  }
331 
332  @Override
333  synchronized public boolean cancel(boolean mayInterrupt) {
334  cancelled = true;
335  progressHandle.suspend(progressText + " " + CANCELLING_POSTIX);
336  return super.cancel(mayInterrupt);
337  }
338 
339  @Override
340  synchronized public boolean isCancelled() {
341  return cancelled || super.isCancelled(); //To change body of generated methods, choose Tools | Templates.
342  }
343 
344  @Override
345  synchronized protected void done() {
346  progressHandle.finish();
347  SwingUtilities.invokeLater(() -> {
348 
349  if (waitSpinnerTimer != null) {
350  waitSpinnerTimer.stop();
351  waitSpinnerTimer = null;
352  }
353 
354  try {
355  if (isCancelled() == false) {
356  thumbCache = new SoftReference<>(get());
357  fireIconChange();
358  }
359  } catch (CancellationException ex) {
360  //Task was cancelled, do nothing
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); //NON-NLS
364  }
365  }
366  });
367  }
368  }
369  }
370 
375  private class ThumbnailPageNode extends AbstractNode {
376 
377  private ThumbnailPageNode(Integer pageNum, List<Node> childNodes) {
378 
379  super(new ThumbnailPageNodeChildren(childNodes), Lookups.singleton(pageNum));
380  setName(Integer.toString(pageNum + 1));
381  int from = 1 + (pageNum * IMAGES_PER_PAGE);
382  int to = from + ((ThumbnailPageNodeChildren) getChildren()).getChildCount() - 1;
383  setDisplayName(from + "-" + to);
384 
385  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS
386  }
387  }
388 
394  private class ThumbnailPageNodeChildren extends Children.Keys<Node> {
395 
396  /*
397  * wrapped original nodes
398  */
399  private List<Node> keyNodes = null;
400 
401  ThumbnailPageNodeChildren(List<Node> keyNodes) {
402  super(true);
403  this.keyNodes = keyNodes;
404  }
405 
406  @Override
407  protected void addNotify() {
408  super.addNotify();
409  setKeys(keyNodes);
410  }
411 
412  @Override
413  protected void removeNotify() {
414  super.removeNotify();
415  setKeys(Collections.emptyList());
416  }
417 
418  int getChildCount() {
419  return keyNodes.size();
420  }
421 
422  @Override
423  protected Node[] createNodes(Node wrapped) {
424  if (wrapped != null) {
425  final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, thumbSize);
426  return new Node[]{thumb};
427  } else {
428  return new Node[]{};
429  }
430  }
431  }
432 }
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static BufferedImage getThumbnail(Content content, int iconSize)

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