Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
FileSearch.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019-2020 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.filequery;
20 
21 import com.google.common.cache.Cache;
22 import com.google.common.cache.CacheBuilder;
23 import com.google.common.io.Files;
24 import java.awt.Image;
25 import java.awt.image.BufferedImage;
26 import java.io.BufferedReader;
27 import java.io.File;
28 import java.io.FileReader;
29 import java.io.FileWriter;
30 import java.io.IOException;
31 import java.io.Reader;
32 import java.nio.file.Paths;
33 import java.sql.ResultSet;
34 import java.sql.SQLException;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.LinkedHashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Objects;
46 import java.util.Set;
47 import java.util.logging.Level;
48 import javax.imageio.ImageIO;
49 import org.apache.commons.io.FileUtils;
50 import org.apache.commons.io.FilenameUtils;
51 import org.apache.commons.lang.StringUtils;
52 import org.imgscalr.Scalr;
53 import org.netbeans.api.progress.ProgressHandle;
54 import org.opencv.core.Mat;
55 import org.opencv.highgui.VideoCapture;
56 import org.openide.util.Lookup;
57 import org.openide.util.NbBundle;
72 import org.sleuthkit.datamodel.AbstractFile;
73 import org.sleuthkit.datamodel.BlackboardArtifact;
74 import org.sleuthkit.datamodel.BlackboardAttribute;
75 import org.sleuthkit.datamodel.CaseDbAccessManager;
76 import org.sleuthkit.datamodel.Content;
77 import org.sleuthkit.datamodel.ContentTag;
78 import org.sleuthkit.datamodel.SleuthkitCase;
79 import org.sleuthkit.datamodel.TskCoreException;
80 import org.sleuthkit.datamodel.TskData;
89 
93 class FileSearch {
94 
95  private final static Logger logger = Logger.getLogger(FileSearch.class.getName());
96  private static final int MAXIMUM_CACHE_SIZE = 10;
97  private static final String THUMBNAIL_FORMAT = "png"; //NON-NLS
98  private static final String VIDEO_THUMBNAIL_DIR = "video-thumbnails"; //NON-NLS
99  private static final Cache<SearchKey, Map<GroupKey, List<ResultFile>>> searchCache = CacheBuilder.newBuilder()
100  .maximumSize(MAXIMUM_CACHE_SIZE)
101  .build();
102  private static final int PREVIEW_SIZE = 256;
103  private static volatile TextSummarizer summarizerToUse = null;
104  private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
105 
124  static SearchResults runFileSearchDebug(String userName,
125  List<FileSearchFiltering.FileFilter> filters,
126  AttributeType groupAttributeType,
127  FileGroup.GroupSortingAlgorithm groupSortingType,
128  FileSorter.SortingMethod fileSortingMethod,
129  SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
130  // Make a list of attributes that we want to add values for. This ensures the
131  // ResultFile objects will have all needed fields set when it's time to group
132  // and sort them. For example, if we're grouping by central repo frequency, we need
133  // to make sure we've loaded those values before grouping.
134  List<AttributeType> attributesNeededForGroupingOrSorting = new ArrayList<>();
135  attributesNeededForGroupingOrSorting.add(groupAttributeType);
136  attributesNeededForGroupingOrSorting.addAll(fileSortingMethod.getRequiredAttributes());
137 
138  // Run the queries for each filter
139  List<ResultFile> resultFiles = FileSearchFiltering.runQueries(filters, caseDb, centralRepoDb);
140 
141  // Add the data to resultFiles for any attributes needed for sorting and grouping
142  addAttributes(attributesNeededForGroupingOrSorting, resultFiles, caseDb, centralRepoDb);
143 
144  // Collect everything in the search results
145  SearchResults searchResults = new SearchResults(groupSortingType, groupAttributeType, fileSortingMethod);
146  searchResults.add(resultFiles);
147 
148  // Sort and group the results
149  searchResults.sortGroupsAndFiles();
150  Map<GroupKey, List<ResultFile>> resultHashMap = searchResults.toLinkedHashMap();
151  SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
152  synchronized (searchCache) {
153  searchCache.put(searchKey, resultHashMap);
154  }
155  return searchResults;
156  }
157 
176  static Map<GroupKey, Integer> getGroupSizes(String userName,
177  List<FileSearchFiltering.FileFilter> filters,
178  AttributeType groupAttributeType,
179  FileGroup.GroupSortingAlgorithm groupSortingType,
180  FileSorter.SortingMethod fileSortingMethod,
181  SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
182  Map<GroupKey, List<ResultFile>> searchResults = runFileSearch(userName, filters,
183  groupAttributeType, groupSortingType, fileSortingMethod, caseDb, centralRepoDb);
184  LinkedHashMap<GroupKey, Integer> groupSizes = new LinkedHashMap<>();
185  for (GroupKey groupKey : searchResults.keySet()) {
186  groupSizes.put(groupKey, searchResults.get(groupKey).size());
187  }
188  return groupSizes;
189  }
190 
213  static List<ResultFile> getFilesInGroup(String userName,
214  List<FileSearchFiltering.FileFilter> filters,
215  AttributeType groupAttributeType,
216  FileGroup.GroupSortingAlgorithm groupSortingType,
217  FileSorter.SortingMethod fileSortingMethod,
218  GroupKey groupKey,
219  int startingEntry,
220  int numberOfEntries,
221  SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
222  //the group should be in the cache at this point
223  List<ResultFile> filesInGroup = null;
224  SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
225  Map<GroupKey, List<ResultFile>> resultsMap;
226  synchronized (searchCache) {
227  resultsMap = searchCache.getIfPresent(searchKey);
228  }
229  if (resultsMap != null) {
230  filesInGroup = resultsMap.get(groupKey);
231  }
232  List<ResultFile> page = new ArrayList<>();
233  if (filesInGroup == null) {
234  logger.log(Level.INFO, "Group {0} was not cached, performing search to cache all groups again", groupKey);
235  runFileSearch(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod, caseDb, centralRepoDb);
236  synchronized (searchCache) {
237  resultsMap = searchCache.getIfPresent(searchKey.getKeyString());
238  }
239  if (resultsMap != null) {
240  filesInGroup = resultsMap.get(groupKey);
241  }
242  if (filesInGroup == null) {
243  logger.log(Level.WARNING, "Group {0} did not exist in cache or new search results", groupKey);
244  return page; //group does not exist
245  }
246  }
247  // Check that there is data after the starting point
248  if (filesInGroup.size() < startingEntry) {
249  logger.log(Level.WARNING, "Group only contains {0} files, starting entry of {1} is too large.", new Object[]{filesInGroup.size(), startingEntry});
250  return page;
251  }
252  // Add files to the page
253  for (int i = startingEntry; (i < startingEntry + numberOfEntries)
254  && (i < filesInGroup.size()); i++) {
255  page.add(filesInGroup.get(i));
256  }
257  return page;
258  }
259 
268  @NbBundle.Messages({"FileSearch.documentSummary.noPreview=No preview available.",
269  "FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview."})
270  static TextSummary summarize(AbstractFile file) {
271  TextSummary summary = null;
272  TextSummarizer localSummarizer = summarizerToUse;
273  if (localSummarizer == null) {
274  synchronized (searchCache) {
275  if (localSummarizer == null) {
276  localSummarizer = getLocalSummarizer();
277  }
278  }
279  }
280  if (localSummarizer != null) {
281  try {
282  //a summary of length 40 seems to fit without vertical scroll bars
283  summary = localSummarizer.summarize(file, 40);
284  } catch (IOException ex) {
285  return new TextSummary(Bundle.FileSearch_documentSummary_noPreview(), null, 0);
286  }
287  }
288  if (summary == null || StringUtils.isBlank(summary.getSummaryText())) {
289  //summary text was empty grab the beginning of the file
290  summary = getDefaultSummary(file);
291  }
292  return summary;
293  }
294 
295  private static TextSummary getDefaultSummary(AbstractFile file) {
296  Image image = null;
297  int countOfImages = 0;
298  try {
299  Content largestChild = null;
300  for (Content child : file.getChildren()) {
301  if (child instanceof AbstractFile && ImageUtils.isImageThumbnailSupported((AbstractFile) child)) {
302  countOfImages++;
303  if (largestChild == null || child.getSize() > largestChild.getSize()) {
304  largestChild = child;
305  }
306  }
307  }
308  if (largestChild != null) {
309  image = ImageUtils.getThumbnail(largestChild, ImageUtils.ICON_SIZE_LARGE);
310  }
311  } catch (TskCoreException ex) {
312  logger.log(Level.WARNING, "Error getting children for file: " + file.getId(), ex);
313  }
314  image = image == null ? image : image.getScaledInstance(ImageUtils.ICON_SIZE_MEDIUM, ImageUtils.ICON_SIZE_MEDIUM,
315  Image.SCALE_SMOOTH);
316  String summaryText = null;
317  if (file.getMd5Hash() != null) {
318  try {
319  summaryText = getSavedSummary(Paths.get(Case.getCurrentCaseThrows().getCacheDirectory(), "summaries", file.getMd5Hash() + "-default-" + PREVIEW_SIZE + "-translated.txt").toString());
320  } catch (NoCurrentCaseException ex) {
321  logger.log(Level.WARNING, "Unable to retrieve saved summary. No case is open.", ex);
322  }
323  }
324  if (StringUtils.isBlank(summaryText)) {
325  String firstLines = getFirstLines(file);
326  String translatedFirstLines = getTranslatedVersion(firstLines);
327  if (!StringUtils.isBlank(translatedFirstLines)) {
328  summaryText = translatedFirstLines;
329  if (file.getMd5Hash() != null) {
330  try {
331  saveSummary(summaryText, Paths.get(Case.getCurrentCaseThrows().getCacheDirectory(), "summaries", file.getMd5Hash() + "-default-" + PREVIEW_SIZE + "-translated.txt").toString());
332  } catch (NoCurrentCaseException ex) {
333  logger.log(Level.WARNING, "Unable to save translated summary. No case is open.", ex);
334  }
335  }
336  } else {
337  summaryText = firstLines;
338  }
339  }
340  return new TextSummary(summaryText, image, countOfImages);
341  }
342 
352  private static String getTranslatedVersion(String documentString) {
353  try {
354  TextTranslationService translatorInstance = TextTranslationService.getInstance();
355  if (translatorInstance.hasProvider()) {
356  String translatedResult = translatorInstance.translate(documentString);
357  if (translatedResult.isEmpty() == false) {
358  return translatedResult;
359  }
360  }
361  } catch (NoServiceProviderException | TranslationException ex) {
362  logger.log(Level.INFO, "Error translating string for summary", ex);
363  }
364  return null;
365  }
366 
376  private static String getSavedSummary(String summarySavePath) {
377  if (summarySavePath == null) {
378  return null;
379  }
380  File savedFile = new File(summarySavePath);
381  if (savedFile.exists()) {
382  try (BufferedReader bReader = new BufferedReader(new FileReader(savedFile))) {
383  // pass the path to the file as a parameter
384  StringBuilder sBuilder = new StringBuilder();
385  String sCurrentLine = bReader.readLine();
386  while (sCurrentLine != null) {
387  sBuilder.append(sCurrentLine).append('\n');
388  sCurrentLine = bReader.readLine();
389  }
390  return sBuilder.toString();
391  } catch (IOException ingored) {
392  //summary file may not exist or may be incomplete in which case return null so a summary can be generated
393  return null; //no saved summary was able to be found
394  }
395  } else {
396  try { //if the file didn't exist make sure the parent directories exist before we move on to creating a summary
397  Files.createParentDirs(savedFile);
398  } catch (IOException ex) {
399  logger.log(Level.WARNING, "Unable to create summaries directory in case folder for file at: " + summarySavePath, ex);
400  }
401  return null; //no saved summary was able to be found
402  }
403 
404  }
405 
412  private static void saveSummary(String summary, String summarySavePath) {
413  if (summarySavePath == null) {
414  return; //can't save a summary if we don't have a path
415  }
416  try (FileWriter myWriter = new FileWriter(summarySavePath)) {
417  myWriter.write(summary);
418  } catch (IOException ex) {
419  logger.log(Level.WARNING, "Unable to save summary at: " + summarySavePath, ex);
420  }
421  }
422 
430  private static String getFirstLines(AbstractFile file) {
431  TextExtractor extractor;
432  try {
433  extractor = TextExtractorFactory.getExtractor(file, null);
434  } catch (TextExtractorFactory.NoTextExtractorFound ignored) {
435  //no extractor found, use Strings Extractor
436  extractor = TextExtractorFactory.getStringsExtractor(file, null);
437  }
438 
439  try (Reader reader = extractor.getReader()) {
440  char[] cbuf = new char[PREVIEW_SIZE];
441  reader.read(cbuf, 0, PREVIEW_SIZE);
442  return new String(cbuf);
443  } catch (IOException ex) {
444  return Bundle.FileSearch_documentSummary_noBytes();
445  } catch (TextExtractor.InitReaderException ex) {
446  return Bundle.FileSearch_documentSummary_noPreview();
447  }
448  }
449 
457  private static TextSummarizer getLocalSummarizer() {
458  Collection<? extends TextSummarizer> summarizers
459  = Lookup.getDefault().lookupAll(TextSummarizer.class
460  );
461  if (!summarizers.isEmpty()) {
462  summarizerToUse = summarizers.iterator().next();
463  return summarizerToUse;
464  }
465  return null;
466  }
467 
485  private static Map<GroupKey, List<ResultFile>> runFileSearch(String userName,
486  List<FileSearchFiltering.FileFilter> filters,
487  AttributeType groupAttributeType,
488  FileGroup.GroupSortingAlgorithm groupSortingType,
489  FileSorter.SortingMethod fileSortingMethod,
490  SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
491 
492  // Make a list of attributes that we want to add values for. This ensures the
493  // ResultFile objects will have all needed fields set when it's time to group
494  // and sort them. For example, if we're grouping by central repo frequency, we need
495  // to make sure we've loaded those values before grouping.
496  List<AttributeType> attributesNeededForGroupingOrSorting = new ArrayList<>();
497  attributesNeededForGroupingOrSorting.add(groupAttributeType);
498  attributesNeededForGroupingOrSorting.addAll(fileSortingMethod.getRequiredAttributes());
499 
500  // Run the queries for each filter
501  List<ResultFile> resultFiles = FileSearchFiltering.runQueries(filters, caseDb, centralRepoDb);
502 
503  // Add the data to resultFiles for any attributes needed for sorting and grouping
504  addAttributes(attributesNeededForGroupingOrSorting, resultFiles, caseDb, centralRepoDb);
505 
506  // Collect everything in the search results
507  SearchResults searchResults = new SearchResults(groupSortingType, groupAttributeType, fileSortingMethod);
508  searchResults.add(resultFiles);
509  Map<GroupKey, List<ResultFile>> resultHashMap = searchResults.toLinkedHashMap();
510  SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
511  synchronized (searchCache) {
512  searchCache.put(searchKey, resultHashMap);
513  }
514  // Return a version of the results in general Java objects
515  return resultHashMap;
516  }
517 
531  private static void addAttributes(List<AttributeType> attrs, List<ResultFile> resultFiles, SleuthkitCase caseDb, CentralRepository centralRepoDb)
532  throws FileSearchException {
533  for (AttributeType attr : attrs) {
534  attr.addAttributeToResultFiles(resultFiles, caseDb, centralRepoDb);
535  }
536  }
537 
545  private static void computeFrequency(Set<String> hashesToLookUp, List<ResultFile> currentFiles, CentralRepository centralRepoDb) {
546 
547  if (hashesToLookUp.isEmpty()) {
548  return;
549  }
550 
551  String hashes = String.join("','", hashesToLookUp);
552  hashes = "'" + hashes + "'";
553  try {
554  CorrelationAttributeInstance.Type attributeType = centralRepoDb.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
555  String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType);
556 
557  String selectClause = " value, COUNT(value) FROM "
558  + "(SELECT DISTINCT case_id, value FROM " + tableName
559  + " WHERE value IN ("
560  + hashes
561  + ")) AS foo GROUP BY value";
562 
563  FrequencyCallback callback = new FrequencyCallback(currentFiles);
564  centralRepoDb.processSelectClause(selectClause, callback);
565 
566  } catch (CentralRepoException ex) {
567  logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
568  }
569 
570  }
571 
572  private static String createSetNameClause(List<ResultFile> files,
573  int artifactTypeID, int setNameAttrID) throws FileSearchException {
574 
575  // Concatenate the object IDs in the list of files
576  String objIdList = ""; // NON-NLS
577  for (ResultFile file : files) {
578  if (!objIdList.isEmpty()) {
579  objIdList += ","; // NON-NLS
580  }
581  objIdList += "\'" + file.getFirstInstance().getId() + "\'"; // NON-NLS
582  }
583 
584  // Get pairs of (object ID, set name) for all files in the list of files that have
585  // the given artifact type.
586  return "blackboard_artifacts.obj_id AS object_id, blackboard_attributes.value_text AS set_name "
587  + "FROM blackboard_artifacts "
588  + "INNER JOIN blackboard_attributes ON blackboard_artifacts.artifact_id=blackboard_attributes.artifact_id "
589  + "WHERE blackboard_attributes.artifact_type_id=\'" + artifactTypeID + "\' "
590  + "AND blackboard_attributes.attribute_type_id=\'" + setNameAttrID + "\' "
591  + "AND blackboard_artifacts.obj_id IN (" + objIdList + ") "; // NON-NLS
592  }
593 
599  private static BufferedImage getDefaultVideoThumbnail() {
600  try {
601  return ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));//NON-NLS
602  } catch (IOException ex) {
603  logger.log(Level.SEVERE, "Failed to load 'failed to create video' placeholder.", ex); //NON-NLS
604  }
605  return null;
606  }
607 
617  @NbBundle.Messages({"# {0} - file name",
618  "FileSearch.genVideoThumb.progress.text=extracting temporary file {0}"})
619  static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
620  AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
621  String cacheDirectory;
622  try {
623  cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
624  } catch (NoCurrentCaseException ex) {
625  cacheDirectory = null;
626  logger.log(Level.WARNING, "Unable to get cache directory, video thumbnails will not be saved", ex);
627  }
628  if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
629  java.io.File tempFile;
630  try {
631  tempFile = getVideoFileInTempDir(file);
632  } catch (NoCurrentCaseException ex) {
633  logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS
634  int[] framePositions = new int[]{
635  0,
636  0,
637  0,
638  0};
639  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
640  return;
641  }
642  if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
643  ProgressHandle progress = ProgressHandle.createHandle(Bundle.FileSearch_genVideoThumb_progress_text(file.getName()));
644  progress.start(100);
645  try {
646  Files.createParentDirs(tempFile);
647  if (Thread.interrupted()) {
648  int[] framePositions = new int[]{
649  0,
650  0,
651  0,
652  0};
653  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
654  return;
655  }
656  ContentUtils.writeToFile(file, tempFile, progress, null, true);
657  } catch (IOException ex) {
658  logger.log(Level.WARNING, "Error extracting temporary file for " + file.getParentPath() + "/" + file.getName(), ex); //NON-NLS
659  } finally {
660  progress.finish();
661  }
662  }
663  VideoCapture videoFile = new VideoCapture(); // will contain the video
664  BufferedImage bufferedImage = null;
665 
666  try {
667  if (!videoFile.open(tempFile.toString())) {
668  logger.log(Level.WARNING, "Error opening {0} for preview generation.", file.getParentPath() + "/" + file.getName()); //NON-NLS
669  int[] framePositions = new int[]{
670  0,
671  0,
672  0,
673  0};
674  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
675  return;
676  }
677  double fps = videoFile.get(5); // gets frame per second
678  double totalFrames = videoFile.get(7); // gets total frames
679  if (fps <= 0 || totalFrames <= 0) {
680  logger.log(Level.WARNING, "Error getting fps or total frames for {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
681  int[] framePositions = new int[]{
682  0,
683  0,
684  0,
685  0};
686  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
687  return;
688  }
689  if (Thread.interrupted()) {
690  int[] framePositions = new int[]{
691  0,
692  0,
693  0,
694  0};
695  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
696  return;
697  }
698 
699  double duration = 1000 * (totalFrames / fps); //total milliseconds
700 
701  int[] framePositions = new int[]{
702  (int) (duration * .01),
703  (int) (duration * .25),
704  (int) (duration * .5),
705  (int) (duration * .75),};
706 
707  Mat imageMatrix = new Mat();
708  List<Image> videoThumbnails = new ArrayList<>();
709  if (cacheDirectory == null || file.getMd5Hash() == null) {
710  cacheDirectory = null;
711  } else {
712  try {
713  FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
714  } catch (IOException ex) {
715  cacheDirectory = null;
716  logger.log(Level.WARNING, "Unable to make video thumbnails directory, thumbnails will not be saved", ex);
717  }
718  }
719  for (int i = 0; i < framePositions.length; i++) {
720  if (!videoFile.set(0, framePositions[i])) {
721  logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
722  // If we can't set the time, continue to the next frame position and try again.
723 
724  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
725  if (cacheDirectory != null) {
726  try {
727  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
728  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
729  } catch (IOException ex) {
730  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
731  }
732  }
733  continue;
734  }
735  // Read the frame into the image/matrix.
736  if (!videoFile.read(imageMatrix)) {
737  logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
738  // If the image is bad for some reason, continue to the next frame position and try again.
739  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
740  if (cacheDirectory != null) {
741  try {
742  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
743  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
744  } catch (IOException ex) {
745  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
746  }
747  }
748 
749  continue;
750  }
751  // If the image is empty, return since no buffered image can be created.
752  if (imageMatrix.empty()) {
753  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
754  if (cacheDirectory != null) {
755  try {
756  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
757  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
758  } catch (IOException ex) {
759  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
760  }
761  }
762  continue;
763  }
764 
765  int matrixColumns = imageMatrix.cols();
766  int matrixRows = imageMatrix.rows();
767 
768  // Convert the matrix that contains the frame to a buffered image.
769  if (bufferedImage == null) {
770  bufferedImage = new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
771  }
772 
773  byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
774  imageMatrix.get(0, 0, data); //copy the image to data
775 
776  if (imageMatrix.channels() == 3) {
777  for (int k = 0; k < data.length; k += 3) {
778  byte temp = data[k];
779  data[k] = data[k + 2];
780  data[k + 2] = temp;
781  }
782  }
783 
784  bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
785  if (Thread.interrupted()) {
786  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
787  try {
788  FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
789  } catch (IOException ex) {
790  logger.log(Level.WARNING, "Unable to delete directory for cancelled video thumbnail process", ex);
791  }
792  return;
793  }
794  BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
795  //We are height limited here so it can be wider than it can be tall.Scalr maintains the aspect ratio.
796  videoThumbnails.add(thumbnail);
797  if (cacheDirectory != null) {
798  try {
799  ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
800  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
801  } catch (IOException ex) {
802  logger.log(Level.WARNING, "Unable to save video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
803  }
804  }
805  }
806  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
807  } finally {
808  videoFile.release(); // close the file}
809  }
810  } else {
811  loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
812  }
813  }
814 
825  private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
826  int[] framePositions = new int[4];
827  List<Image> videoThumbnails = new ArrayList<>();
828  int thumbnailNumber = 0;
829  String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
830  for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
831  try {
832  videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
833  } catch (IOException ex) {
834  videoThumbnails.add(failedVideoThumbImage);
835  logger.log(Level.WARNING, "Unable to read saved video thumbnail " + fileName + " for " + md5, ex);
836  }
837  int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
838  framePositions[thumbnailNumber] = framePos;
839  thumbnailNumber++;
840  }
841  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
842  }
843 
850  private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
851  List<Image> videoThumbnails = new ArrayList<>();
852  videoThumbnails.add(failedVideoThumbImage);
853  videoThumbnails.add(failedVideoThumbImage);
854  videoThumbnails.add(failedVideoThumbImage);
855  videoThumbnails.add(failedVideoThumbImage);
856  return videoThumbnails;
857  }
858 
859  private FileSearch() {
860  // Class should not be instantiated
861  }
862 
866  abstract static class AttributeType {
867 
876  abstract GroupKey getGroupKey(ResultFile file);
877 
888  void addAttributeToResultFiles(List<ResultFile> files, SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
889  // Default is to do nothing
890  }
891  }
892 
896  abstract static class GroupKey implements Comparable<GroupKey> {
897 
904  abstract String getDisplayName();
905 
913  @Override
914  abstract public boolean equals(Object otherKey);
915 
921  @Override
922  abstract public int hashCode();
923 
933  int compareClassNames(GroupKey otherGroupKey) {
934  return this.getClass().getName().compareTo(otherGroupKey.getClass().getName());
935  }
936 
937  @Override
938  public String toString() {
939  return getDisplayName();
940  }
941  }
942 
946  static class FileSizeAttribute extends AttributeType {
947 
948  @Override
949  GroupKey getGroupKey(ResultFile file) {
950  return new FileSizeGroupKey(file);
951  }
952  }
953 
957  private static class FileSizeGroupKey extends GroupKey {
958 
959  private final FileSize fileSize;
960 
961  FileSizeGroupKey(ResultFile file) {
962  if (file.getFileType() == FileType.VIDEO) {
963  fileSize = FileSize.fromVideoSize(file.getFirstInstance().getSize());
964  } else {
965  fileSize = FileSize.fromImageSize(file.getFirstInstance().getSize());
966  }
967  }
968 
969  @Override
970  String getDisplayName() {
971  return getFileSize().toString();
972  }
973 
974  @Override
975  public int compareTo(GroupKey otherGroupKey) {
976  if (otherGroupKey instanceof FileSizeGroupKey) {
977  FileSizeGroupKey otherFileSizeGroupKey = (FileSizeGroupKey) otherGroupKey;
978  return Integer.compare(getFileSize().getRanking(), otherFileSizeGroupKey.getFileSize().getRanking());
979  } else {
980  return compareClassNames(otherGroupKey);
981  }
982  }
983 
984  @Override
985  public boolean equals(Object otherKey) {
986  if (otherKey == this) {
987  return true;
988  }
989 
990  if (!(otherKey instanceof FileSizeGroupKey)) {
991  return false;
992  }
993 
994  FileSizeGroupKey otherFileSizeGroupKey = (FileSizeGroupKey) otherKey;
995  return getFileSize().equals(otherFileSizeGroupKey.getFileSize());
996  }
997 
998  @Override
999  public int hashCode() {
1000  return Objects.hash(getFileSize().getRanking());
1001  }
1002 
1006  FileSize getFileSize() {
1007  return fileSize;
1008  }
1009  }
1010 
1014  static class ParentPathAttribute extends AttributeType {
1015 
1016  @Override
1017  GroupKey getGroupKey(ResultFile file) {
1018  return new ParentPathGroupKey(file);
1019  }
1020  }
1021 
1025  private static class ParentPathGroupKey extends GroupKey {
1026 
1027  private String parentPath;
1028  private Long parentID;
1029 
1030  ParentPathGroupKey(ResultFile file) {
1031  Content parent;
1032  try {
1033  parent = file.getFirstInstance().getParent();
1034  } catch (TskCoreException ignored) {
1035  parent = null;
1036  }
1037  //Find the directory this file is in if it is an embedded file
1038  while (parent != null && parent instanceof AbstractFile && ((AbstractFile) parent).isFile()) {
1039  try {
1040  parent = parent.getParent();
1041  } catch (TskCoreException ignored) {
1042  parent = null;
1043  }
1044  }
1045  setParentPathAndID(parent, file);
1046  }
1047 
1054  private void setParentPathAndID(Content parent, ResultFile file) {
1055  if (parent != null) {
1056  try {
1057  parentPath = parent.getUniquePath();
1058  parentID = parent.getId();
1059  } catch (TskCoreException ignored) {
1060  //catch block left blank purposefully next if statement will handle case when exception takes place as well as when parent is null
1061  }
1062 
1063  }
1064  if (parentPath == null) {
1065  if (file.getFirstInstance().getParentPath() != null) {
1066  parentPath = file.getFirstInstance().getParentPath();
1067  } else {
1068  parentPath = ""; // NON-NLS
1069  }
1070  parentID = -1L;
1071  }
1072  }
1073 
1074  @Override
1075  String getDisplayName() {
1076  return getParentPath();
1077  }
1078 
1079  @Override
1080  public int compareTo(GroupKey otherGroupKey) {
1081  if (otherGroupKey instanceof ParentPathGroupKey) {
1082  ParentPathGroupKey otherParentPathGroupKey = (ParentPathGroupKey) otherGroupKey;
1083  int comparisonResult = getParentPath().compareTo(otherParentPathGroupKey.getParentPath());
1084  if (comparisonResult == 0) {
1085  comparisonResult = getParentID().compareTo(otherParentPathGroupKey.getParentID());
1086  }
1087  return comparisonResult;
1088  } else {
1089  return compareClassNames(otherGroupKey);
1090  }
1091  }
1092 
1093  @Override
1094  public boolean equals(Object otherKey) {
1095  if (otherKey == this) {
1096  return true;
1097  }
1098 
1099  if (!(otherKey instanceof ParentPathGroupKey)) {
1100  return false;
1101  }
1102 
1103  ParentPathGroupKey otherParentPathGroupKey = (ParentPathGroupKey) otherKey;
1104  return getParentPath().equals(otherParentPathGroupKey.getParentPath()) && getParentID().equals(otherParentPathGroupKey.getParentID());
1105  }
1106 
1107  @Override
1108  public int hashCode() {
1109  int hashCode = 11;
1110  hashCode = 61 * hashCode + Objects.hash(getParentPath());
1111  hashCode = 61 * hashCode + Objects.hash(getParentID());
1112  return hashCode;
1113  }
1114 
1118  String getParentPath() {
1119  return parentPath;
1120  }
1121 
1125  Long getParentID() {
1126  return parentID;
1127  }
1128  }
1129 
1133  static class DataSourceAttribute extends AttributeType {
1134 
1135  @Override
1136  GroupKey getGroupKey(ResultFile file) {
1137  return new DataSourceGroupKey(file);
1138  }
1139  }
1140 
1144  private static class DataSourceGroupKey extends GroupKey {
1145 
1146  private final long dataSourceID;
1147  private String displayName;
1148 
1149  @NbBundle.Messages({
1150  "# {0} - Data source name",
1151  "# {1} - Data source ID",
1152  "FileSearch.DataSourceGroupKey.datasourceAndID={0}(ID: {1})",
1153  "# {0} - Data source ID",
1154  "FileSearch.DataSourceGroupKey.idOnly=Data source (ID: {0})"})
1155  DataSourceGroupKey(ResultFile file) {
1156  dataSourceID = file.getFirstInstance().getDataSourceObjectId();
1157 
1158  try {
1159  // The data source should be cached so this won't actually be a database query.
1160  Content ds = file.getFirstInstance().getDataSource();
1161  displayName = Bundle.FileSearch_DataSourceGroupKey_datasourceAndID(ds.getName(), ds.getId());
1162  } catch (TskCoreException ex) {
1163  logger.log(Level.WARNING, "Error looking up data source with ID " + dataSourceID, ex); // NON-NLS
1164  displayName = Bundle.FileSearch_DataSourceGroupKey_idOnly(dataSourceID);
1165  }
1166  }
1167 
1168  @Override
1169  String getDisplayName() {
1170  return displayName;
1171  }
1172 
1173  @Override
1174  public int compareTo(GroupKey otherGroupKey) {
1175  if (otherGroupKey instanceof DataSourceGroupKey) {
1176  DataSourceGroupKey otherDataSourceGroupKey = (DataSourceGroupKey) otherGroupKey;
1177  return Long.compare(getDataSourceID(), otherDataSourceGroupKey.getDataSourceID());
1178  } else {
1179  return compareClassNames(otherGroupKey);
1180  }
1181  }
1182 
1183  @Override
1184  public boolean equals(Object otherKey) {
1185  if (otherKey == this) {
1186  return true;
1187  }
1188 
1189  if (!(otherKey instanceof DataSourceGroupKey)) {
1190  return false;
1191  }
1192 
1193  DataSourceGroupKey otherDataSourceGroupKey = (DataSourceGroupKey) otherKey;
1194  return getDataSourceID() == otherDataSourceGroupKey.getDataSourceID();
1195  }
1196 
1197  @Override
1198  public int hashCode() {
1199  return Objects.hash(getDataSourceID());
1200  }
1201 
1205  long getDataSourceID() {
1206  return dataSourceID;
1207  }
1208  }
1209 
1213  static class FileTypeAttribute extends AttributeType {
1214 
1215  @Override
1216  GroupKey getGroupKey(ResultFile file) {
1217  return new FileTypeGroupKey(file);
1218  }
1219  }
1220 
1224  private static class FileTypeGroupKey extends GroupKey {
1225 
1226  private final FileType fileType;
1227 
1228  FileTypeGroupKey(ResultFile file) {
1229  fileType = file.getFileType();
1230  }
1231 
1232  @Override
1233  String getDisplayName() {
1234  return getFileType().toString();
1235  }
1236 
1237  @Override
1238  public int compareTo(GroupKey otherGroupKey) {
1239  if (otherGroupKey instanceof FileTypeGroupKey) {
1240  FileTypeGroupKey otherFileTypeGroupKey = (FileTypeGroupKey) otherGroupKey;
1241  return Integer.compare(getFileType().getRanking(), otherFileTypeGroupKey.getFileType().getRanking());
1242  } else {
1243  return compareClassNames(otherGroupKey);
1244  }
1245  }
1246 
1247  @Override
1248  public boolean equals(Object otherKey) {
1249  if (otherKey == this) {
1250  return true;
1251  }
1252 
1253  if (!(otherKey instanceof FileTypeGroupKey)) {
1254  return false;
1255  }
1256 
1257  FileTypeGroupKey otherFileTypeGroupKey = (FileTypeGroupKey) otherKey;
1258  return getFileType().equals(otherFileTypeGroupKey.getFileType());
1259  }
1260 
1261  @Override
1262  public int hashCode() {
1263  return Objects.hash(getFileType().getRanking());
1264  }
1265 
1269  FileType getFileType() {
1270  return fileType;
1271  }
1272  }
1273 
1277  static class KeywordListAttribute extends AttributeType {
1278 
1279  @Override
1280  GroupKey getGroupKey(ResultFile file) {
1281  return new KeywordListGroupKey(file);
1282  }
1283 
1284  @Override
1285  void addAttributeToResultFiles(List<ResultFile> files, SleuthkitCase caseDb,
1286  CentralRepository centralRepoDb) throws FileSearchException {
1287 
1288  // Get pairs of (object ID, keyword list name) for all files in the list of files that have
1289  // keyword list hits.
1290  String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(),
1291  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
1292 
1293  SetKeywordListNamesCallback callback = new SetKeywordListNamesCallback(files);
1294  try {
1295  caseDb.getCaseDbAccessManager().select(selectQuery, callback);
1296  } catch (TskCoreException ex) {
1297  throw new FileSearchException("Error looking up keyword list attributes", ex); // NON-NLS
1298  }
1299  }
1300 
1306  private static class SetKeywordListNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1307 
1308  List<ResultFile> resultFiles;
1309 
1315  SetKeywordListNamesCallback(List<ResultFile> resultFiles) {
1316  this.resultFiles = resultFiles;
1317  }
1318 
1319  @Override
1320  public void process(ResultSet rs) {
1321  try {
1322  // Create a temporary map of object ID to ResultFile
1323  Map<Long, ResultFile> tempMap = new HashMap<>();
1324  for (ResultFile file : resultFiles) {
1325  tempMap.put(file.getFirstInstance().getId(), file);
1326  }
1327 
1328  while (rs.next()) {
1329  try {
1330  Long objId = rs.getLong("object_id"); // NON-NLS
1331  String keywordListName = rs.getString("set_name"); // NON-NLS
1332 
1333  tempMap.get(objId).addKeywordListName(keywordListName);
1334 
1335  } catch (SQLException ex) {
1336  logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
1337  }
1338  }
1339  } catch (SQLException ex) {
1340  logger.log(Level.SEVERE, "Failed to get keyword list names", ex); // NON-NLS
1341  }
1342  }
1343  }
1344  }
1345 
1349  private static class KeywordListGroupKey extends GroupKey {
1350 
1351  private final List<String> keywordListNames;
1352  private final String keywordListNamesString;
1353 
1354  @NbBundle.Messages({
1355  "FileSearch.KeywordListGroupKey.noKeywords=None"})
1356  KeywordListGroupKey(ResultFile file) {
1357  keywordListNames = file.getKeywordListNames();
1358 
1359  if (keywordListNames.isEmpty()) {
1360  keywordListNamesString = Bundle.FileSearch_KeywordListGroupKey_noKeywords();
1361  } else {
1362  keywordListNamesString = String.join(",", keywordListNames); // NON-NLS
1363  }
1364  }
1365 
1366  @Override
1367  String getDisplayName() {
1368  return getKeywordListNamesString();
1369  }
1370 
1371  @Override
1372  public int compareTo(GroupKey otherGroupKey) {
1373  if (otherGroupKey instanceof KeywordListGroupKey) {
1374  KeywordListGroupKey otherKeywordListNamesGroupKey = (KeywordListGroupKey) otherGroupKey;
1375 
1376  // Put the empty list at the end
1377  if (getKeywordListNames().isEmpty()) {
1378  if (otherKeywordListNamesGroupKey.getKeywordListNames().isEmpty()) {
1379  return 0;
1380  } else {
1381  return 1;
1382  }
1383  } else if (otherKeywordListNamesGroupKey.getKeywordListNames().isEmpty()) {
1384  return -1;
1385  }
1386 
1387  return getKeywordListNamesString().compareTo(otherKeywordListNamesGroupKey.getKeywordListNamesString());
1388  } else {
1389  return compareClassNames(otherGroupKey);
1390  }
1391  }
1392 
1393  @Override
1394  public boolean equals(Object otherKey) {
1395  if (otherKey == this) {
1396  return true;
1397  }
1398 
1399  if (!(otherKey instanceof KeywordListGroupKey)) {
1400  return false;
1401  }
1402 
1403  KeywordListGroupKey otherKeywordListGroupKey = (KeywordListGroupKey) otherKey;
1404  return getKeywordListNamesString().equals(otherKeywordListGroupKey.getKeywordListNamesString());
1405  }
1406 
1407  @Override
1408  public int hashCode() {
1409  return Objects.hash(getKeywordListNamesString());
1410  }
1411 
1415  List<String> getKeywordListNames() {
1416  return Collections.unmodifiableList(keywordListNames);
1417  }
1418 
1422  String getKeywordListNamesString() {
1423  return keywordListNamesString;
1424  }
1425  }
1426 
1430  static class FrequencyAttribute extends AttributeType {
1431 
1432  static final int BATCH_SIZE = 50; // Number of hashes to look up at one time
1433 
1434  @Override
1435  GroupKey getGroupKey(ResultFile file) {
1436  return new FrequencyGroupKey(file);
1437  }
1438 
1439  @Override
1440  void addAttributeToResultFiles(List<ResultFile> files, SleuthkitCase caseDb,
1441  CentralRepository centralRepoDb) throws FileSearchException {
1442  if (centralRepoDb == null) {
1443  for (ResultFile file : files) {
1444  if (file.getFrequency() == Frequency.UNKNOWN && file.getFirstInstance().getKnown() == TskData.FileKnown.KNOWN) {
1445  file.setFrequency(Frequency.KNOWN);
1446  }
1447  }
1448  } else {
1449  processResultFilesForCR(files, centralRepoDb);
1450  }
1451  }
1452 
1461  private void processResultFilesForCR(List<ResultFile> files,
1462  CentralRepository centralRepoDb) {
1463  List<ResultFile> currentFiles = new ArrayList<>();
1464  Set<String> hashesToLookUp = new HashSet<>();
1465  for (ResultFile file : files) {
1466  if (file.getFirstInstance().getKnown() == TskData.FileKnown.KNOWN) {
1467  file.setFrequency(Frequency.KNOWN);
1468  }
1469  if (file.getFrequency() == Frequency.UNKNOWN
1470  && file.getFirstInstance().getMd5Hash() != null
1471  && !file.getFirstInstance().getMd5Hash().isEmpty()) {
1472  hashesToLookUp.add(file.getFirstInstance().getMd5Hash());
1473  currentFiles.add(file);
1474  }
1475  if (hashesToLookUp.size() >= BATCH_SIZE) {
1476  computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
1477 
1478  hashesToLookUp.clear();
1479  currentFiles.clear();
1480  }
1481  }
1482  computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
1483  }
1484  }
1485 
1490  private static class FrequencyCallback implements InstanceTableCallback {
1491 
1492  private final List<ResultFile> files;
1493 
1494  private FrequencyCallback(List<ResultFile> files) {
1495  this.files = new ArrayList<>(files);
1496  }
1497 
1498  @Override
1499  public void process(ResultSet resultSet) {
1500  try {
1501 
1502  while (resultSet.next()) {
1503  String hash = resultSet.getString(1);
1504  int count = resultSet.getInt(2);
1505  for (Iterator<ResultFile> iterator = files.iterator(); iterator.hasNext();) {
1506  ResultFile file = iterator.next();
1507  if (file.getFirstInstance().getMd5Hash().equalsIgnoreCase(hash)) {
1508  file.setFrequency(Frequency.fromCount(count));
1509  iterator.remove();
1510  }
1511  }
1512  }
1513 
1514  // The files left had no matching entries in the CR, so mark them as unique
1515  for (ResultFile file : files) {
1516  file.setFrequency(Frequency.UNIQUE);
1517  }
1518  } catch (SQLException ex) {
1519  logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
1520  }
1521  }
1522  }
1523 
1527  private static class FrequencyGroupKey extends GroupKey {
1528 
1529  private final Frequency frequency;
1530 
1531  FrequencyGroupKey(ResultFile file) {
1532  frequency = file.getFrequency();
1533  }
1534 
1535  @Override
1536  String getDisplayName() {
1537  return getFrequency().toString();
1538  }
1539 
1540  @Override
1541  public int compareTo(GroupKey otherGroupKey) {
1542  if (otherGroupKey instanceof FrequencyGroupKey) {
1543  FrequencyGroupKey otherFrequencyGroupKey = (FrequencyGroupKey) otherGroupKey;
1544  return Integer.compare(getFrequency().getRanking(), otherFrequencyGroupKey.getFrequency().getRanking());
1545  } else {
1546  return compareClassNames(otherGroupKey);
1547  }
1548  }
1549 
1550  @Override
1551  public boolean equals(Object otherKey) {
1552  if (otherKey == this) {
1553  return true;
1554  }
1555 
1556  if (!(otherKey instanceof FrequencyGroupKey)) {
1557  return false;
1558  }
1559 
1560  FrequencyGroupKey otherFrequencyGroupKey = (FrequencyGroupKey) otherKey;
1561  return getFrequency().equals(otherFrequencyGroupKey.getFrequency());
1562  }
1563 
1564  @Override
1565  public int hashCode() {
1566  return Objects.hash(getFrequency().getRanking());
1567  }
1568 
1572  Frequency getFrequency() {
1573  return frequency;
1574  }
1575  }
1576 
1580  static class HashHitsAttribute extends AttributeType {
1581 
1582  @Override
1583  GroupKey getGroupKey(ResultFile file) {
1584  return new HashHitsGroupKey(file);
1585  }
1586 
1587  @Override
1588  void addAttributeToResultFiles(List<ResultFile> files, SleuthkitCase caseDb,
1589  CentralRepository centralRepoDb) throws FileSearchException {
1590 
1591  // Get pairs of (object ID, hash set name) for all files in the list of files that have
1592  // hash set hits.
1593  String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID(),
1594  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
1595 
1596  HashSetNamesCallback callback = new HashSetNamesCallback(files);
1597  try {
1598  caseDb.getCaseDbAccessManager().select(selectQuery, callback);
1599  } catch (TskCoreException ex) {
1600  throw new FileSearchException("Error looking up hash set attributes", ex); // NON-NLS
1601  }
1602  }
1603 
1608  private static class HashSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1609 
1610  List<ResultFile> resultFiles;
1611 
1617  HashSetNamesCallback(List<ResultFile> resultFiles) {
1618  this.resultFiles = resultFiles;
1619  }
1620 
1621  @Override
1622  public void process(ResultSet rs) {
1623  try {
1624  // Create a temporary map of object ID to ResultFile
1625  Map<Long, ResultFile> tempMap = new HashMap<>();
1626  for (ResultFile file : resultFiles) {
1627  tempMap.put(file.getFirstInstance().getId(), file);
1628  }
1629 
1630  while (rs.next()) {
1631  try {
1632  Long objId = rs.getLong("object_id"); // NON-NLS
1633  String hashSetName = rs.getString("set_name"); // NON-NLS
1634 
1635  tempMap.get(objId).addHashSetName(hashSetName);
1636 
1637  } catch (SQLException ex) {
1638  logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
1639  }
1640  }
1641  } catch (SQLException ex) {
1642  logger.log(Level.SEVERE, "Failed to get hash set names", ex); // NON-NLS
1643  }
1644  }
1645  }
1646  }
1647 
1651  private static class HashHitsGroupKey extends GroupKey {
1652 
1653  private final List<String> hashSetNames;
1654  private final String hashSetNamesString;
1655 
1656  @NbBundle.Messages({
1657  "FileSearch.HashHitsGroupKey.noHashHits=None"})
1658  HashHitsGroupKey(ResultFile file) {
1659  hashSetNames = file.getHashSetNames();
1660 
1661  if (hashSetNames.isEmpty()) {
1662  hashSetNamesString = Bundle.FileSearch_HashHitsGroupKey_noHashHits();
1663  } else {
1664  hashSetNamesString = String.join(",", hashSetNames); // NON-NLS
1665  }
1666  }
1667 
1668  @Override
1669  String getDisplayName() {
1670  return getHashSetNamesString();
1671  }
1672 
1673  @Override
1674  public int compareTo(GroupKey otherGroupKey) {
1675  if (otherGroupKey instanceof HashHitsGroupKey) {
1676  HashHitsGroupKey otherHashHitsGroupKey = (HashHitsGroupKey) otherGroupKey;
1677 
1678  // Put the empty list at the end
1679  if (getHashSetNames().isEmpty()) {
1680  if (otherHashHitsGroupKey.getHashSetNames().isEmpty()) {
1681  return 0;
1682  } else {
1683  return 1;
1684  }
1685  } else if (otherHashHitsGroupKey.getHashSetNames().isEmpty()) {
1686  return -1;
1687  }
1688 
1689  return getHashSetNamesString().compareTo(otherHashHitsGroupKey.getHashSetNamesString());
1690  } else {
1691  return compareClassNames(otherGroupKey);
1692  }
1693  }
1694 
1695  @Override
1696  public boolean equals(Object otherKey) {
1697  if (otherKey == this) {
1698  return true;
1699  }
1700 
1701  if (!(otherKey instanceof HashHitsGroupKey)) {
1702  return false;
1703  }
1704 
1705  HashHitsGroupKey otherHashHitsGroupKey = (HashHitsGroupKey) otherKey;
1706  return getHashSetNamesString().equals(otherHashHitsGroupKey.getHashSetNamesString());
1707  }
1708 
1709  @Override
1710  public int hashCode() {
1711  return Objects.hash(getHashSetNamesString());
1712  }
1713 
1717  List<String> getHashSetNames() {
1718  return Collections.unmodifiableList(hashSetNames);
1719  }
1720 
1724  String getHashSetNamesString() {
1725  return hashSetNamesString;
1726  }
1727  }
1728 
1732  static class InterestingItemAttribute extends AttributeType {
1733 
1734  @Override
1735  GroupKey getGroupKey(ResultFile file) {
1736  return new InterestingItemGroupKey(file);
1737  }
1738 
1739  @Override
1740  void addAttributeToResultFiles(List<ResultFile> files, SleuthkitCase caseDb,
1741  CentralRepository centralRepoDb) throws FileSearchException {
1742 
1743  // Get pairs of (object ID, interesting item set name) for all files in the list of files that have
1744  // interesting file set hits.
1745  String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),
1746  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
1747 
1748  InterestingFileSetNamesCallback callback = new InterestingFileSetNamesCallback(files);
1749  try {
1750  caseDb.getCaseDbAccessManager().select(selectQuery, callback);
1751  } catch (TskCoreException ex) {
1752  throw new FileSearchException("Error looking up interesting file set attributes", ex); // NON-NLS
1753  }
1754  }
1755 
1761  private static class InterestingFileSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1762 
1763  List<ResultFile> resultFiles;
1764 
1771  InterestingFileSetNamesCallback(List<ResultFile> resultFiles) {
1772  this.resultFiles = resultFiles;
1773  }
1774 
1775  @Override
1776  public void process(ResultSet rs) {
1777  try {
1778  // Create a temporary map of object ID to ResultFile
1779  Map<Long, ResultFile> tempMap = new HashMap<>();
1780  for (ResultFile file : resultFiles) {
1781  tempMap.put(file.getFirstInstance().getId(), file);
1782  }
1783 
1784  while (rs.next()) {
1785  try {
1786  Long objId = rs.getLong("object_id"); // NON-NLS
1787  String setName = rs.getString("set_name"); // NON-NLS
1788 
1789  tempMap.get(objId).addInterestingSetName(setName);
1790 
1791  } catch (SQLException ex) {
1792  logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
1793  }
1794  }
1795  } catch (SQLException ex) {
1796  logger.log(Level.SEVERE, "Failed to get interesting file set names", ex); // NON-NLS
1797  }
1798  }
1799  }
1800  }
1801 
1805  private static class InterestingItemGroupKey extends GroupKey {
1806 
1807  private final List<String> interestingItemSetNames;
1808  private final String interestingItemSetNamesString;
1809 
1810  @NbBundle.Messages({
1811  "FileSearch.InterestingItemGroupKey.noSets=None"})
1812  InterestingItemGroupKey(ResultFile file) {
1813  interestingItemSetNames = file.getInterestingSetNames();
1814 
1815  if (interestingItemSetNames.isEmpty()) {
1816  interestingItemSetNamesString = Bundle.FileSearch_InterestingItemGroupKey_noSets();
1817  } else {
1818  interestingItemSetNamesString = String.join(",", interestingItemSetNames); // NON-NLS
1819  }
1820  }
1821 
1822  @Override
1823  String getDisplayName() {
1824  return getInterestingItemSetNamesString();
1825  }
1826 
1827  @Override
1828  public int compareTo(GroupKey otherGroupKey) {
1829  if (otherGroupKey instanceof InterestingItemGroupKey) {
1830  InterestingItemGroupKey otherInterestingItemGroupKey = (InterestingItemGroupKey) otherGroupKey;
1831 
1832  // Put the empty list at the end
1833  if (this.getInterestingItemSetNames().isEmpty()) {
1834  if (otherInterestingItemGroupKey.getInterestingItemSetNames().isEmpty()) {
1835  return 0;
1836  } else {
1837  return 1;
1838  }
1839  } else if (otherInterestingItemGroupKey.getInterestingItemSetNames().isEmpty()) {
1840  return -1;
1841  }
1842 
1843  return getInterestingItemSetNamesString().compareTo(otherInterestingItemGroupKey.getInterestingItemSetNamesString());
1844  } else {
1845  return compareClassNames(otherGroupKey);
1846  }
1847  }
1848 
1849  @Override
1850  public boolean equals(Object otherKey) {
1851  if (otherKey == this) {
1852  return true;
1853  }
1854 
1855  if (!(otherKey instanceof InterestingItemGroupKey)) {
1856  return false;
1857  }
1858 
1859  InterestingItemGroupKey otherInterestingItemGroupKey = (InterestingItemGroupKey) otherKey;
1860  return getInterestingItemSetNamesString().equals(otherInterestingItemGroupKey.getInterestingItemSetNamesString());
1861  }
1862 
1863  @Override
1864  public int hashCode() {
1865  return Objects.hash(getInterestingItemSetNamesString());
1866  }
1867 
1871  List<String> getInterestingItemSetNames() {
1872  return Collections.unmodifiableList(interestingItemSetNames);
1873  }
1874 
1878  String getInterestingItemSetNamesString() {
1880  }
1881  }
1882 
1886  static class ObjectDetectedAttribute extends AttributeType {
1887 
1888  @Override
1889  GroupKey getGroupKey(ResultFile file) {
1890  return new ObjectDetectedGroupKey(file);
1891  }
1892 
1893  @Override
1894  void addAttributeToResultFiles(List<ResultFile> files, SleuthkitCase caseDb,
1895  CentralRepository centralRepoDb) throws FileSearchException {
1896 
1897  // Get pairs of (object ID, object type name) for all files in the list of files that have
1898  // objects detected
1899  String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID(),
1900  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID());
1901 
1902  ObjectDetectedNamesCallback callback = new ObjectDetectedNamesCallback(files);
1903  try {
1904  caseDb.getCaseDbAccessManager().select(selectQuery, callback);
1905  } catch (TskCoreException ex) {
1906  throw new FileSearchException("Error looking up object detected attributes", ex); // NON-NLS
1907  }
1908  }
1909 
1915  private static class ObjectDetectedNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1916 
1917  List<ResultFile> resultFiles;
1918 
1924  ObjectDetectedNamesCallback(List<ResultFile> resultFiles) {
1925  this.resultFiles = resultFiles;
1926  }
1927 
1928  @Override
1929  public void process(ResultSet rs) {
1930  try {
1931  // Create a temporary map of object ID to ResultFile
1932  Map<Long, ResultFile> tempMap = new HashMap<>();
1933  for (ResultFile file : resultFiles) {
1934  tempMap.put(file.getFirstInstance().getId(), file);
1935  }
1936 
1937  while (rs.next()) {
1938  try {
1939  Long objId = rs.getLong("object_id"); // NON-NLS
1940  String setName = rs.getString("set_name"); // NON-NLS
1941 
1942  tempMap.get(objId).addObjectDetectedName(setName);
1943 
1944  } catch (SQLException ex) {
1945  logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
1946  }
1947  }
1948  } catch (SQLException ex) {
1949  logger.log(Level.SEVERE, "Failed to get object detected names", ex); // NON-NLS
1950  }
1951  }
1952  }
1953  }
1954 
1958  private static class ObjectDetectedGroupKey extends GroupKey {
1959 
1960  private final List<String> objectDetectedNames;
1961  private final String objectDetectedNamesString;
1962 
1963  @NbBundle.Messages({
1964  "FileSearch.ObjectDetectedGroupKey.noSets=None"})
1965  ObjectDetectedGroupKey(ResultFile file) {
1966  objectDetectedNames = file.getObjectDetectedNames();
1967 
1968  if (objectDetectedNames.isEmpty()) {
1969  objectDetectedNamesString = Bundle.FileSearch_ObjectDetectedGroupKey_noSets();
1970  } else {
1971  objectDetectedNamesString = String.join(",", objectDetectedNames); // NON-NLS
1972  }
1973  }
1974 
1975  @Override
1976  String getDisplayName() {
1977  return getObjectDetectedNamesString();
1978  }
1979 
1980  @Override
1981  public int compareTo(GroupKey otherGroupKey) {
1982  if (otherGroupKey instanceof ObjectDetectedGroupKey) {
1983  ObjectDetectedGroupKey otherObjectDetectedGroupKey = (ObjectDetectedGroupKey) otherGroupKey;
1984 
1985  // Put the empty list at the end
1986  if (this.getObjectDetectedNames().isEmpty()) {
1987  if (otherObjectDetectedGroupKey.getObjectDetectedNames().isEmpty()) {
1988  return 0;
1989  } else {
1990  return 1;
1991  }
1992  } else if (otherObjectDetectedGroupKey.getObjectDetectedNames().isEmpty()) {
1993  return -1;
1994  }
1995 
1996  return getObjectDetectedNamesString().compareTo(otherObjectDetectedGroupKey.getObjectDetectedNamesString());
1997  } else {
1998  return compareClassNames(otherGroupKey);
1999  }
2000  }
2001 
2002  @Override
2003  public boolean equals(Object otherKey) {
2004  if (otherKey == this) {
2005  return true;
2006  }
2007 
2008  if (!(otherKey instanceof ObjectDetectedGroupKey)) {
2009  return false;
2010  }
2011 
2012  ObjectDetectedGroupKey otherObjectDetectedGroupKey = (ObjectDetectedGroupKey) otherKey;
2013  return getObjectDetectedNamesString().equals(otherObjectDetectedGroupKey.getObjectDetectedNamesString());
2014  }
2015 
2016  @Override
2017  public int hashCode() {
2018  return Objects.hash(getObjectDetectedNamesString());
2019  }
2020 
2024  List<String> getObjectDetectedNames() {
2025  return Collections.unmodifiableList(objectDetectedNames);
2026  }
2027 
2031  String getObjectDetectedNamesString() {
2033  }
2034  }
2035 
2039  static class FileTagAttribute extends AttributeType {
2040 
2041  @Override
2042  GroupKey getGroupKey(ResultFile file) {
2043  return new FileTagGroupKey(file);
2044  }
2045 
2046  @Override
2047  void addAttributeToResultFiles(List<ResultFile> files, SleuthkitCase caseDb,
2048  CentralRepository centralRepoDb) throws FileSearchException {
2049 
2050  try {
2051  for (ResultFile resultFile : files) {
2052  List<ContentTag> contentTags = caseDb.getContentTagsByContent(resultFile.getFirstInstance());
2053 
2054  for (ContentTag tag : contentTags) {
2055  resultFile.addTagName(tag.getName().getDisplayName());
2056  }
2057  }
2058  } catch (TskCoreException ex) {
2059  throw new FileSearchException("Error looking up file tag attributes", ex); // NON-NLS
2060  }
2061  }
2062  }
2063 
2067  private static class SearchKey implements Comparable<SearchKey> {
2068 
2069  private final String keyString;
2070 
2080  SearchKey(String userName, List<FileSearchFiltering.FileFilter> filters,
2081  AttributeType groupAttributeType,
2082  FileGroup.GroupSortingAlgorithm groupSortingType,
2083  FileSorter.SortingMethod fileSortingMethod) {
2084  StringBuilder searchStringBuilder = new StringBuilder();
2085  searchStringBuilder.append(userName);
2086  for (FileSearchFiltering.FileFilter filter : filters) {
2087  searchStringBuilder.append(filter.toString());
2088  }
2089  searchStringBuilder.append(groupAttributeType).append(groupSortingType).append(fileSortingMethod);
2090  keyString = searchStringBuilder.toString();
2091  }
2092 
2093  @Override
2094  public int compareTo(SearchKey otherSearchKey) {
2095  return getKeyString().compareTo(otherSearchKey.getKeyString());
2096  }
2097 
2098  @Override
2099  public boolean equals(Object otherKey) {
2100  if (otherKey == this) {
2101  return true;
2102  }
2103 
2104  if (!(otherKey instanceof SearchKey)) {
2105  return false;
2106  }
2107 
2108  SearchKey otherSearchKey = (SearchKey) otherKey;
2109  return getKeyString().equals(otherSearchKey.getKeyString());
2110  }
2111 
2112  @Override
2113  public int hashCode() {
2114  int hash = 5;
2115  hash = 79 * hash + Objects.hashCode(getKeyString());
2116  return hash;
2117  }
2118 
2122  String getKeyString() {
2123  return keyString;
2124  }
2125  }
2126 
2130  private static class FileTagGroupKey extends GroupKey {
2131 
2132  private final List<String> tagNames;
2133  private final String tagNamesString;
2134 
2135  @NbBundle.Messages({
2136  "FileSearch.FileTagGroupKey.noSets=None"})
2137  FileTagGroupKey(ResultFile file) {
2138  tagNames = file.getTagNames();
2139 
2140  if (tagNames.isEmpty()) {
2141  tagNamesString = Bundle.FileSearch_FileTagGroupKey_noSets();
2142  } else {
2143  tagNamesString = String.join(",", tagNames); // NON-NLS
2144  }
2145  }
2146 
2147  @Override
2148  String getDisplayName() {
2149  return getTagNamesString();
2150  }
2151 
2152  @Override
2153  public int compareTo(GroupKey otherGroupKey) {
2154  if (otherGroupKey instanceof FileTagGroupKey) {
2155  FileTagGroupKey otherFileTagGroupKey = (FileTagGroupKey) otherGroupKey;
2156 
2157  // Put the empty list at the end
2158  if (getTagNames().isEmpty()) {
2159  if (otherFileTagGroupKey.getTagNames().isEmpty()) {
2160  return 0;
2161  } else {
2162  return 1;
2163  }
2164  } else if (otherFileTagGroupKey.getTagNames().isEmpty()) {
2165  return -1;
2166  }
2167 
2168  return getTagNamesString().compareTo(otherFileTagGroupKey.getTagNamesString());
2169  } else {
2170  return compareClassNames(otherGroupKey);
2171  }
2172  }
2173 
2174  @Override
2175  public boolean equals(Object otherKey) {
2176  if (otherKey == this) {
2177  return true;
2178  }
2179 
2180  if (!(otherKey instanceof FileTagGroupKey)) {
2181  return false;
2182  }
2183 
2184  FileTagGroupKey otherFileTagGroupKey = (FileTagGroupKey) otherKey;
2185  return getTagNamesString().equals(otherFileTagGroupKey.getTagNamesString());
2186  }
2187 
2188  @Override
2189  public int hashCode() {
2190  return Objects.hash(getTagNamesString());
2191  }
2192 
2196  List<String> getTagNames() {
2197  return Collections.unmodifiableList(tagNames);
2198  }
2199 
2203  String getTagNamesString() {
2204  return tagNamesString;
2205  }
2206  }
2207 
2211  static class NoGroupingAttribute extends AttributeType {
2212 
2213  @Override
2214  GroupKey getGroupKey(ResultFile file) {
2215  return new NoGroupingGroupKey();
2216  }
2217  }
2218 
2223  private static class NoGroupingGroupKey extends GroupKey {
2224 
2225  NoGroupingGroupKey() {
2226  // Nothing to save - all files will get the same GroupKey
2227  }
2228 
2229  @NbBundle.Messages({
2230  "FileSearch.NoGroupingGroupKey.allFiles=All Files"})
2231  @Override
2232  String getDisplayName() {
2233  return Bundle.FileSearch_NoGroupingGroupKey_allFiles();
2234  }
2235 
2236  @Override
2237  public int compareTo(GroupKey otherGroupKey) {
2238  // As long as the other key is the same type, they are equal
2239  if (otherGroupKey instanceof NoGroupingGroupKey) {
2240  return 0;
2241  } else {
2242  return compareClassNames(otherGroupKey);
2243  }
2244  }
2245 
2246  @Override
2247  public boolean equals(Object otherKey) {
2248  if (otherKey == this) {
2249  return true;
2250  }
2251  // As long as the other key is the same type, they are equal
2252  return otherKey instanceof NoGroupingGroupKey;
2253  }
2254 
2255  @Override
2256  public int hashCode() {
2257  return 0;
2258  }
2259  }
2260 
2264  @NbBundle.Messages({
2265  "FileSearch.GroupingAttributeType.fileType.displayName=File Type",
2266  "FileSearch.GroupingAttributeType.frequency.displayName=Past Occurrences",
2267  "FileSearch.GroupingAttributeType.keywordList.displayName=Keyword",
2268  "FileSearch.GroupingAttributeType.size.displayName=File Size",
2269  "FileSearch.GroupingAttributeType.datasource.displayName=Data Source",
2270  "FileSearch.GroupingAttributeType.parent.displayName=Parent Folder",
2271  "FileSearch.GroupingAttributeType.hash.displayName=Hash Set",
2272  "FileSearch.GroupingAttributeType.interestingItem.displayName=Interesting Item",
2273  "FileSearch.GroupingAttributeType.tag.displayName=Tag",
2274  "FileSearch.GroupingAttributeType.object.displayName=Object Detected",
2275  "FileSearch.GroupingAttributeType.none.displayName=None"})
2276  enum GroupingAttributeType {
2277  FILE_SIZE(new FileSizeAttribute(), Bundle.FileSearch_GroupingAttributeType_size_displayName()),
2278  FREQUENCY(new FrequencyAttribute(), Bundle.FileSearch_GroupingAttributeType_frequency_displayName()),
2279  KEYWORD_LIST_NAME(new KeywordListAttribute(), Bundle.FileSearch_GroupingAttributeType_keywordList_displayName()),
2280  DATA_SOURCE(new DataSourceAttribute(), Bundle.FileSearch_GroupingAttributeType_datasource_displayName()),
2281  PARENT_PATH(new ParentPathAttribute(), Bundle.FileSearch_GroupingAttributeType_parent_displayName()),
2282  HASH_LIST_NAME(new HashHitsAttribute(), Bundle.FileSearch_GroupingAttributeType_hash_displayName()),
2283  INTERESTING_ITEM_SET(new InterestingItemAttribute(), Bundle.FileSearch_GroupingAttributeType_interestingItem_displayName()),
2284  FILE_TAG(new FileTagAttribute(), Bundle.FileSearch_GroupingAttributeType_tag_displayName()),
2285  OBJECT_DETECTED(new ObjectDetectedAttribute(), Bundle.FileSearch_GroupingAttributeType_object_displayName()),
2286  NO_GROUPING(new NoGroupingAttribute(), Bundle.FileSearch_GroupingAttributeType_none_displayName());
2287 
2288  private final AttributeType attributeType;
2289  private final String displayName;
2290 
2291  GroupingAttributeType(AttributeType attributeType, String displayName) {
2292  this.attributeType = attributeType;
2293  this.displayName = displayName;
2294  }
2295 
2296  @Override
2297  public String toString() {
2298  return displayName;
2299  }
2300 
2301  AttributeType getAttributeType() {
2302  return attributeType;
2303  }
2304 
2310  static List<GroupingAttributeType> getOptionsForGrouping() {
2311  return Arrays.asList(FILE_SIZE, FREQUENCY, PARENT_PATH, OBJECT_DETECTED, HASH_LIST_NAME, INTERESTING_ITEM_SET);
2312  }
2313  }
2314 }
static File getVideoFileInTempDir(AbstractFile file)
void setParentPathAndID(Content parent, ResultFile file)

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.