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(); } }