Autopsy  4.4
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 java.awt.Image;
22 import java.awt.Toolkit;
23 import java.awt.event.ActionEvent;
24 import java.lang.ref.SoftReference;
25 import java.lang.reflect.InvocationTargetException;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.concurrent.ExecutionException;
32 import java.util.logging.Level;
33 import java.util.stream.Collectors;
34 import javax.swing.SortOrder;
35 import javax.swing.SwingWorker;
36 import javax.swing.Timer;
37 import org.apache.commons.lang3.StringUtils;
38 import org.netbeans.api.progress.ProgressHandle;
39 import org.openide.nodes.AbstractNode;
40 import org.openide.nodes.Children;
41 import org.openide.nodes.FilterNode;
42 import org.openide.nodes.Node;
43 import org.openide.util.Exceptions;
44 import org.openide.util.NbBundle;
45 import org.openide.util.lookup.Lookups;
49 import org.sleuthkit.datamodel.Content;
50 import static org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.loadSortCriteria;
51 
62 class ThumbnailViewChildren extends Children.Keys<Integer> {
63 
64  private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
65 
66  static final int IMAGES_PER_PAGE = 200;
67  private final Node parent;
68  private final HashMap<Integer, List<Node>> pages = new HashMap<>();
69  private int totalImages = 0;
70  private int totalPages = 0;
71  private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
72 
79  ThumbnailViewChildren(Node arg, int iconSize) {
80  super(true); //support lazy loading
81 
82  this.parent = arg;
83  this.iconSize = iconSize;
84  }
85 
86  @Override
87  protected void addNotify() {
88  super.addNotify();
89 
90  setupKeys();
91  }
92 
93  int getTotalPages() {
94  return totalPages;
95  }
96 
97  int getTotalImages() {
98  return totalImages;
99  }
100 
101  private void setupKeys() {
102  //divide the supported content into buckets
103  totalImages = 0;
104  //TODO when lazy loading of original nodes is fixed
105  //we should be asking the datamodel for the children instead
106  //and not counting the children nodes (which might not be preloaded at this point)
107  final List<Node> suppContent = new ArrayList<>();
108  for (Node child : parent.getChildren().getNodes()) {
109  if (isSupported(child)) {
110  ++totalImages;
111  suppContent.add(child);
112  }
113  }
114  //sort suppContent!
115  Collections.sort(suppContent, getComparator());
116 
117  if (totalImages == 0) {
118  return;
119  }
120 
121  totalPages = 0;
122  if (totalImages < IMAGES_PER_PAGE) {
123  totalPages = 1;
124  } else {
125  totalPages = totalImages / IMAGES_PER_PAGE;
126  if (totalPages % totalImages != 0) {
127  ++totalPages;
128  }
129  }
130 
131  int prevImages = 0;
132  for (int page = 1; page <= totalPages; ++page) {
133  int toAdd = Math.min(IMAGES_PER_PAGE, totalImages - prevImages);
134  List<Node> pageContent = suppContent.subList(prevImages, prevImages + toAdd);
135  pages.put(page, pageContent);
136  prevImages += toAdd;
137  }
138 
139  Integer[] pageNums = new Integer[totalPages];
140  for (int i = 0; i < totalPages; ++i) {
141  pageNums[i] = i + 1;
142  }
143  setKeys(pageNums);
144  }
145 
146  private synchronized Comparator<Node> getComparator() {
147  Comparator<Node> comp = (node1, node2) -> 0;
148 
149  if (!(parent instanceof TableFilterNode)) {
150  return comp;
151  } else {
152  List<SortCriterion> sortCriteria = loadSortCriteria((TableFilterNode) parent);
153 
154  //make a comparatator that will sort the nodes.
155  return sortCriteria.stream()
156  .map(this::getCriterionComparator)
157  .collect(Collectors.reducing(Comparator::thenComparing))
158  .orElse(comp);
159 
160  }
161  }
162 
163  private Comparator<Node> getCriterionComparator(SortCriterion criterion) {
164  Comparator<Node> c = Comparator.comparing(node -> getPropertyValue(node, criterion.getProperty()),
165  Comparator.nullsFirst(Comparator.naturalOrder()));
166  return criterion.getSortOrder() == SortOrder.ASCENDING ? c : c.reversed();
167  }
168 
169  private Comparable getPropertyValue(Node node, Node.Property<?> prop) {
170  for (Node.PropertySet ps : node.getPropertySets()) {
171  for (Node.Property<?> p : ps.getProperties()) {
172  if (p.equals(prop)) {
173  try {
174  return (Comparable) p.getValue();
175  } catch (IllegalAccessException | InvocationTargetException ex) {
176  Exceptions.printStackTrace(ex);
177  }
178  }
179  }
180  }
181  return null;
182  }
183 
184  @Override
185  protected void removeNotify() {
186  super.removeNotify();
187  pages.clear();
188  totalImages = 0;
189  }
190 
191  @Override
192  protected Node[] createNodes(Integer pageNum) {
193  final ThumbnailPageNode pageNode = new ThumbnailPageNode(pageNum);
194  return new Node[]{pageNode};
195 
196  }
197 
198  static boolean isSupported(Node node) {
199  if (node != null) {
200  Content content = node.getLookup().lookup(Content.class);
201  if (content != null) {
202  return ImageUtils.thumbnailSupported(content);
203  }
204  }
205  return false;
206  }
207 
208  public void setIconSize(int iconSize) {
209  this.iconSize = iconSize;
210 
211  }
212 
217  static class ThumbnailViewNode extends FilterNode {
218 
219  private static final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif"));
220  private SoftReference<Image> iconCache = null;
221  private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
222  private SwingWorker<Image, Object> swingWorker;
223  private Timer timer;
224 
228  ThumbnailViewNode(Node arg, int iconSize) {
229  super(arg, Children.LEAF);
230  this.iconSize = iconSize;
231  }
232 
233  @Override
234  public String getDisplayName() {
235  return StringUtils.abbreviate(super.getDisplayName(), 18);
236  }
237 
238  @Override
239  @NbBundle.Messages(value = {"# {0} - file name", "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
240  public Image getIcon(int type) {
241  Image icon = null;
242  if (iconCache != null) {
243  icon = iconCache.get();
244  }
245  if (icon != null) {
246  return icon;
247  } else {
248  final Content content = this.getLookup().lookup(Content.class);
249  if (content == null) {
250  return ImageUtils.getDefaultThumbnail();
251  }
252  if (swingWorker == null || swingWorker.isDone()) {
253  swingWorker = new SwingWorker<Image, Object>() {
254  private final ProgressHandle progressHandle = ProgressHandle.createHandle(Bundle.ThumbnailViewNode_progressHandle_text(content.getName()));
255 
256  @Override
257  protected Image doInBackground() throws Exception {
258  progressHandle.start();
259  return ImageUtils.getThumbnail(content, iconSize);
260  }
261 
262  @Override
263  protected void done() {
264  super.done();
265  try {
266  iconCache = new SoftReference<>(super.get());
267  fireIconChange();
268  } catch (InterruptedException | ExecutionException ex) {
269  Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS
270  } finally {
271  progressHandle.finish();
272  if (timer != null) {
273  timer.stop();
274  timer = null;
275  }
276  swingWorker = null;
277  }
278  }
279  };
280  swingWorker.execute();
281  }
282  if (timer == null) {
283  timer = new Timer(100, (ActionEvent e) -> {
284  fireIconChange();
285  });
286  timer.start();
287  }
288  return waitingIcon;
289  }
290  }
291 
292  public void setIconSize(int iconSize) {
293  this.iconSize = iconSize;
294  iconCache = null;
295  swingWorker = null;
296  }
297  }
298 
303  private class ThumbnailPageNode extends AbstractNode {
304 
305  ThumbnailPageNode(Integer pageNum) {
306  super(new ThumbnailPageNodeChildren(pages.get(pageNum)), Lookups.singleton(pageNum));
307  setName(Integer.toString(pageNum));
308  int from = 1 + ((pageNum - 1) * IMAGES_PER_PAGE);
309  int showImages = Math.min(IMAGES_PER_PAGE, totalImages - (from - 1));
310  int to = from + showImages - 1;
311  setDisplayName(from + "-" + to);
312 
313  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS
314 
315  }
316  }
317 
318 //TODO insert node at beginning pressing which goes back to page view
319  private class ThumbnailPageNodeChildren extends Children.Keys<Node> {
320 
321  //wrapped original nodes
322  private List<Node> contentImages = null;
323 
324  ThumbnailPageNodeChildren(List<Node> contentImages) {
325  super(true);
326 
327  this.contentImages = contentImages;
328  }
329 
330  @Override
331  protected void addNotify() {
332  super.addNotify();
333 
334  setKeys(contentImages);
335  }
336 
337  @Override
338  protected void removeNotify() {
339  super.removeNotify();
340 
341  setKeys(new ArrayList<Node>());
342  }
343 
344  @Override
345  protected Node[] createNodes(Node wrapped) {
346  if (wrapped != null) {
347  final ThumbnailViewNode thumb = new ThumbnailViewNode(wrapped, iconSize);
348  return new Node[]{thumb};
349  } else {
350  return new Node[]{};
351  }
352  }
353  }
354 }

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