diff --git a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties
index 7bd6347392..3e3370b6ea 100644
--- a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties
@@ -56,3 +56,4 @@ ResultsPanel.currentPageLabel.text=Page: -
ResultsPanel.pageControlsLabel.text=Pages:
ResultsPanel.gotoPageLabel.text=Go to Page:
ResultsPanel.pageSizeLabel.text=Page size:
+ResultsPanel.duplicatesList.border.title=Duplicate Files
diff --git a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED
index fba8b03be5..f1e2fd8ee9 100644
--- a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED
@@ -165,4 +165,5 @@ ResultsPanel.invalidPageNumber.title=Invalid Page Number
ResultsPanel.pageControlsLabel.text=Pages:
ResultsPanel.gotoPageLabel.text=Go to Page:
ResultsPanel.pageSizeLabel.text=Page size:
+ResultsPanel.duplicatesList.border.title=Duplicate Files
SearchNode.getName.text=Search Result
diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java
index fcdde6ac26..6319bb0564 100644
--- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java
+++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java
@@ -345,7 +345,8 @@ class FileSearch {
*/
@NbBundle.Messages({"# {0} - file name",
"FileSearch.genVideoThumb.progress.text=extracting temporary file {0}"})
- static VideoThumbnailsWrapper getVideoThumbnails(AbstractFile file) {
+ static VideoThumbnailsWrapper getVideoThumbnails(ResultFile resultFile) {
+ AbstractFile file = resultFile.getAbstractFile();
//Currently this method always creates the thumbnails
java.io.File tempFile;
try {
@@ -357,7 +358,7 @@ class FileSearch {
0,
0,
0};
- return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file);
+ return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, resultFile);
}
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
ProgressHandle progress = ProgressHandle.createHandle(Bundle.FileSearch_genVideoThumb_progress_text(file.getName()));
@@ -370,7 +371,7 @@ class FileSearch {
0,
0,
0};
- return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file);
+ return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, resultFile);
}
ContentUtils.writeToFile(file, tempFile, progress, null, true);
} catch (IOException ex) {
@@ -390,7 +391,7 @@ class FileSearch {
0,
0,
0};
- return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file);
+ return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, resultFile);
}
double fps = videoFile.get(5); // gets frame per second
double totalFrames = videoFile.get(7); // gets total frames
@@ -401,7 +402,7 @@ class FileSearch {
0,
0,
0};
- return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file);
+ return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, resultFile);
}
if (Thread.interrupted()) {
int[] framePositions = new int[]{
@@ -409,7 +410,7 @@ class FileSearch {
0,
0,
0};
- return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, file);
+ return new VideoThumbnailsWrapper(createDefaultThumbnailList(), framePositions, resultFile);
}
double duration = 1000 * (totalFrames / fps); //total milliseconds
@@ -464,11 +465,11 @@ class FileSearch {
bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
if (Thread.interrupted()) {
- return new VideoThumbnailsWrapper(videoThumbnails, framePositions, file);
+ return new VideoThumbnailsWrapper(videoThumbnails, framePositions, resultFile);
}
videoThumbnails.add(ScalrWrapper.resizeFast(bufferedImage, ImageUtils.ICON_SIZE_LARGE));
}
- return new VideoThumbnailsWrapper(videoThumbnails, framePositions, file);
+ return new VideoThumbnailsWrapper(videoThumbnails, framePositions, resultFile);
} finally {
videoFile.release(); // close the file}
}
diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form
index b184ce210a..273804c88c 100644
--- a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form
@@ -17,15 +17,16 @@
-
+
-
+
-
+
+
@@ -60,7 +61,7 @@
-
+
@@ -228,9 +229,82 @@
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java
index cb815fe4d2..69861a0354 100644
--- a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java
@@ -22,6 +22,8 @@ import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
+import javax.swing.DefaultListModel;
+import javax.swing.DefaultListSelectionModel;
import javax.swing.JOptionPane;
import javax.swing.JSpinner;
import javax.swing.SwingUtilities;
@@ -37,6 +39,7 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail;
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.TskCoreException;
/**
* Panel for displaying of file discovery results and handling the paging of
@@ -60,6 +63,7 @@ public class ResultsPanel extends javax.swing.JPanel {
private int groupSize = 0;
private PageWorker pageWorker;
private final List thumbnailWorkers = new ArrayList<>();
+ private final DefaultListModel duplicatesListModel = new DefaultListModel<>();
/**
* Creates new form ResultsPanel.
@@ -71,6 +75,20 @@ public class ResultsPanel extends javax.swing.JPanel {
tableViewer = new DataResultViewerTable(explorerManager);
videoThumbnailViewer = new VideoThumbnailViewer();
// Disable manual editing of page size spinner
+ videoThumbnailViewer.addListSelectionListener((e) -> {
+ SwingUtilities.invokeLater(() -> {
+ duplicatesListModel.removeAllElements();
+ for (AbstractFile file : getSelectedDuplicates()) {
+ String name;
+ try {
+ name = file.getUniquePath();
+ } catch (TskCoreException ingored) {
+ name = file.getParentPath() + "/" + file.getName();
+ }
+ duplicatesListModel.addElement(name);
+ }
+ });
+ });
((JSpinner.DefaultEditor) pageSizeSpinner.getEditor()).getTextField().setEditable(false);
}
@@ -82,6 +100,13 @@ public class ResultsPanel extends javax.swing.JPanel {
return videoThumbnailViewer.getSelectedFile();
}
+ private List getSelectedDuplicates() {
+ if (resultType == FileSearchData.FileType.VIDEO) {
+ return videoThumbnailViewer.getDuplicatesForSelected();
+ }
+ return new ArrayList<>();
+ }
+
/**
* Subscribe and respond to PageRetrievedEvents.
*
@@ -226,6 +251,10 @@ public class ResultsPanel extends javax.swing.JPanel {
javax.swing.JLabel gotoPageLabel = new javax.swing.JLabel();
gotoPageField = new javax.swing.JTextField();
javax.swing.JLabel pageSizeLabel = new javax.swing.JLabel();
+ resultsSplitPane = new javax.swing.JSplitPane();
+ duplicatesPanel = new javax.swing.JPanel();
+ duplicatesScrollPane = new javax.swing.JScrollPane();
+ duplicatesList = new javax.swing.JList<>();
resultsViewerPanel = new javax.swing.JPanel();
pagingPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
@@ -307,7 +336,7 @@ public class ResultsPanel extends javax.swing.JPanel {
.addComponent(pageSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pageSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(81, Short.MAX_VALUE))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pagingPanelLayout.setVerticalGroup(
pagingPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -327,21 +356,49 @@ public class ResultsPanel extends javax.swing.JPanel {
.addGap(4, 4, 4))
);
+ resultsSplitPane.setDividerLocation(250);
+ resultsSplitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
+ resultsSplitPane.setResizeWeight(0.9);
+ resultsSplitPane.setPreferredSize(new java.awt.Dimension(777, 125));
+
+ duplicatesList.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(ResultsPanel.class, "ResultsPanel.duplicatesList.border.title"))); // NOI18N
+ duplicatesList.setModel(duplicatesListModel);
+ duplicatesScrollPane.setViewportView(duplicatesList);
+
+ javax.swing.GroupLayout duplicatesPanelLayout = new javax.swing.GroupLayout(duplicatesPanel);
+ duplicatesPanel.setLayout(duplicatesPanelLayout);
+ duplicatesPanelLayout.setHorizontalGroup(
+ duplicatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 775, Short.MAX_VALUE)
+ .addGroup(duplicatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(duplicatesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 775, Short.MAX_VALUE))
+ );
+ duplicatesPanelLayout.setVerticalGroup(
+ duplicatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 52, Short.MAX_VALUE)
+ .addGroup(duplicatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(duplicatesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 52, Short.MAX_VALUE))
+ );
+
+ resultsSplitPane.setRightComponent(duplicatesPanel);
+
resultsViewerPanel.setLayout(new java.awt.BorderLayout());
+ resultsSplitPane.setLeftComponent(resultsViewerPanel);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pagingPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(resultsViewerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(resultsSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(pagingPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0)
- .addComponent(resultsViewerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 62, Short.MAX_VALUE))
+ .addComponent(resultsSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 199, Short.MAX_VALUE)
+ .addGap(0, 0, 0))
);
}// //GEN-END:initComponents
@@ -418,11 +475,15 @@ public class ResultsPanel extends javax.swing.JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel currentPageLabel;
+ private javax.swing.JList duplicatesList;
+ private javax.swing.JPanel duplicatesPanel;
+ private javax.swing.JScrollPane duplicatesScrollPane;
private javax.swing.JTextField gotoPageField;
private javax.swing.JButton nextPageButton;
private javax.swing.JSpinner pageSizeSpinner;
private javax.swing.JPanel pagingPanel;
private javax.swing.JButton previousPageButton;
+ private javax.swing.JSplitPane resultsSplitPane;
private javax.swing.JPanel resultsViewerPanel;
// End of variables declaration//GEN-END:variables
@@ -437,7 +498,7 @@ public class ResultsPanel extends javax.swing.JPanel {
@Override
protected Void doInBackground() throws Exception {
- thumbnailWrapper = FileSearch.getVideoThumbnails(file.getAbstractFile());
+ thumbnailWrapper = FileSearch.getVideoThumbnails(file);
return null;
}
diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java
index f46dbff906..0c01230855 100644
--- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java
@@ -18,6 +18,8 @@
*/
package org.sleuthkit.autopsy.filequery;
+import java.util.ArrayList;
+import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.event.ListSelectionListener;
import org.sleuthkit.datamodel.AbstractFile;
@@ -51,13 +53,21 @@ public class VideoThumbnailViewer extends javax.swing.JPanel {
* Get the AbstractFile associated with the selected thumbnails.
*
* @return The AbstractFile associated with the selected thumbnails, or null
- * if no thumnails are selected.
+ * if no thumbnails are selected.
*/
AbstractFile getSelectedFile() {
if (thumbnailList.getSelectedIndex() == -1) {
return null;
} else {
- return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getAbstractFile();
+ return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getAbstractFile();
+ }
+ }
+
+ List getDuplicatesForSelected() {
+ if (thumbnailList.getSelectedIndex() == -1) {
+ return new ArrayList<>();
+ } else {
+ return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getDuplicates();
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java
index 2d8747b492..73bd37457a 100644
--- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java
+++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java
@@ -21,17 +21,16 @@ package org.sleuthkit.autopsy.filequery;
import java.awt.Image;
import java.util.Collections;
import java.util.List;
-import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Class to wrap all the information necessary for video thumbnails to be
* displayed.
*/
-public class VideoThumbnailsWrapper {
+final class VideoThumbnailsWrapper {
private final List thumbnails;
- private final AbstractFile abstractFile;
+ private final ResultFile resultFile;
private final int[] timeStamps;
/**
@@ -41,24 +40,24 @@ public class VideoThumbnailsWrapper {
* video.
* @param timeStamps An array containing the time in milliseconds into the
* video that each thumbnail created for.
- * @param file The AbstractFile which represents the video file which
+ * @param file The ResultFile which represents the video file which
* the thumbnails were created for.
*/
- public VideoThumbnailsWrapper(List thumbnails, int[] timeStamps, AbstractFile file) {
+ VideoThumbnailsWrapper(List thumbnails, int[] timeStamps, ResultFile file) {
this.thumbnails = thumbnails;
this.timeStamps = timeStamps;
- this.abstractFile = file;
+ this.resultFile = file;
}
/**
- * Get the AbstractFile which represents the video file which the thumbnails
+ * Get the ResultFile which represents the video file which the thumbnails
* were created for.
*
- * @return The AbstractFile which represents the video file which the
+ * @return The ResultFile which represents the video file which the
* thumbnails were created for.
*/
- AbstractFile getAbstractFile() {
- return abstractFile;
+ ResultFile getResultFile() {
+ return resultFile;
}
/**
@@ -79,9 +78,9 @@ public class VideoThumbnailsWrapper {
*/
String getFilePath() {
try {
- return abstractFile.getUniquePath();
+ return resultFile.getAbstractFile().getUniquePath();
} catch (TskCoreException ingored) {
- return abstractFile.getParentPath() + "/" + abstractFile.getName();
+ return resultFile.getAbstractFile().getParentPath() + "/" + resultFile.getAbstractFile().getName();
}
}