Autopsy  4.20.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.awt.image.BufferedImage;
26 import java.io.IOException;
27 import java.lang.ref.SoftReference;
28 import java.lang.reflect.InvocationTargetException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.List;
33 import java.util.concurrent.Callable;
34 import java.util.concurrent.CancellationException;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.FutureTask;
39 import java.util.logging.Level;
40 import java.util.stream.Collectors;
41 import java.util.stream.IntStream;
42 import java.util.stream.Stream;
43 import javax.imageio.ImageIO;
44 import javax.swing.SwingUtilities;
45 import javax.swing.Timer;
46 import org.apache.commons.lang3.StringUtils;
47 import org.netbeans.api.progress.ProgressHandle;
48 import org.netbeans.api.progress.ProgressHandleFactory;
49 import org.openide.nodes.AbstractNode;
50 import org.openide.nodes.Children;
51 import org.openide.nodes.FilterNode;
52 import org.openide.nodes.Node;
53 import org.openide.util.NbBundle;
54 import org.openide.util.lookup.Lookups;
56 import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadSortCriteria;
59 import org.sleuthkit.datamodel.AbstractFile;
60 import org.sleuthkit.datamodel.Content;
61 
71 class 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 
354  super(new Callable<Image>() { //Does not work as lambda expression in dependent projects in IDE
355  public Image call() {
356  return ImageUtils.getThumbnail(content, thumbSize);
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 }
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static BufferedImage getThumbnail(Content content, int iconSize)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.