19 package org.sleuthkit.autopsy.filesearch;
 
   21 import java.awt.Component;
 
   22 import java.awt.Cursor;
 
   23 import java.awt.GridLayout;
 
   24 import java.awt.event.ActionEvent;
 
   25 import java.awt.event.ActionListener;
 
   26 import java.beans.PropertyChangeEvent;
 
   27 import java.beans.PropertyChangeListener;
 
   28 import java.util.ArrayList;
 
   29 import java.util.Collection;
 
   30 import java.util.Collections;
 
   31 import java.util.List;
 
   32 import java.util.concurrent.CancellationException;
 
   33 import java.util.concurrent.ExecutionException;
 
   34 import java.util.logging.Level;
 
   35 import javax.swing.JLabel;
 
   36 import javax.swing.JPanel;
 
   37 import javax.swing.SwingWorker;
 
   38 import javax.swing.border.EmptyBorder;
 
   39 import org.openide.DialogDisplayer;
 
   40 import org.openide.NotifyDescriptor;
 
   41 import org.openide.nodes.Node;
 
   42 import org.openide.util.NbBundle;
 
   59 @SuppressWarnings(
"PMD.SingularField") 
 
   60 class FileSearchPanel extends javax.swing.JPanel {
 
   62     private static final Logger logger = Logger.getLogger(FileSearchPanel.class.getName());
 
   63     private static final long serialVersionUID = 1L;
 
   64     private final List<FileSearchFilter> filters = 
new ArrayList<>();
 
   65     private static int resultWindowCount = 0; 
 
   66     private static final MimeTypeFilter mimeTypeFilter = 
new MimeTypeFilter();
 
   67     private static final DataSourceFilter dataSourceFilter = 
new DataSourceFilter();
 
   68     private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, 
"FileSearchPanel.emptyWhereClause.text");
 
   69     private static SwingWorker<TableFilterNode, Void> searchWorker = null;
 
   80         customizeComponents();
 
   86     private void customizeComponents() {
 
   88         JLabel label = 
new JLabel(NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.custComp.label.text"));
 
   89         label.setAlignmentX(Component.LEFT_ALIGNMENT);
 
   90         label.setBorder(
new EmptyBorder(0, 0, 10, 0));
 
   92         JPanel panel1 = 
new JPanel();
 
   93         panel1.setLayout(
new GridLayout(1, 2));
 
   94         panel1.add(
new JLabel(
""));
 
   95         JPanel panel2 = 
new JPanel();
 
   96         panel2.setLayout(
new GridLayout(1, 2, 20, 0));
 
   97         JPanel panel3 = 
new JPanel();
 
   98         panel3.setLayout(
new GridLayout(1, 2, 20, 0));
 
   99         JPanel panel4 = 
new JPanel();
 
  100         panel4.setLayout(
new GridLayout(1, 2, 20, 0));
 
  101         JPanel panel5 = 
new JPanel();
 
  102         panel5.setLayout(
new GridLayout(1, 2, 20, 0));
 
  105         NameSearchFilter nameFilter = 
new NameSearchFilter();
 
  106         SizeSearchFilter sizeFilter = 
new SizeSearchFilter();
 
  107         DateSearchFilter dateFilter = 
new DateSearchFilter();
 
  108         KnownStatusSearchFilter knowStatusFilter = 
new KnownStatusSearchFilter();
 
  109         DeletedFileSearchFilter deleltedFilter = 
new DeletedFileSearchFilter();
 
  110         HashSearchFilter hashFilter = 
new HashSearchFilter();
 
  111         panel2.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.filterTitle.name"), nameFilter));
 
  113         panel3.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.filterTitle.metadata"), sizeFilter));
 
  114         panel2.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.filterTitle.metadata"), dateFilter));
 
  115         panel3.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.filterTitle.knownStatus"), knowStatusFilter));
 
  117         panel5.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"HashSearchPanel.md5CheckBox.text"), hashFilter));
 
  118         panel4.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.filterTitle.metadata"), mimeTypeFilter));
 
  119         panel4.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"DataSourcePanel.dataSourceCheckBox.text"), dataSourceFilter));
 
  120         panel5.add(
new FilterArea(NbBundle.getMessage(
this.getClass(), 
"DeletedFilesSearchPanel.deletedCheckbox.text"), deleltedFilter));   
 
  122         filterPanel.add(panel1);
 
  123         filterPanel.add(panel2);
 
  124         filterPanel.add(panel3);
 
  125         filterPanel.add(panel4);
 
  126         filterPanel.add(panel5);
 
  128         filters.add(nameFilter);
 
  129         filters.add(sizeFilter);
 
  130         filters.add(dateFilter);
 
  131         filters.add(knowStatusFilter);
 
  132         filters.add(hashFilter);
 
  133         filters.add(mimeTypeFilter);
 
  134         filters.add(dataSourceFilter);
 
  135         filters.add(deleltedFilter);
 
  137         for (FileSearchFilter filter : this.getFilters()) {
 
  138             filter.addPropertyChangeListener(
new PropertyChangeListener() {
 
  140                 public void propertyChange(PropertyChangeEvent evt) {
 
  141                     searchButton.setEnabled(isValidSearch());
 
  145         addListenerToAll(
new ActionListener() {
 
  147             public void actionPerformed(ActionEvent e) {
 
  151         searchButton.setEnabled(isValidSearch());
 
  159     void setDataSourceFilter(
long dataSourceId) {
 
  160         dataSourceFilter.setSelectedDataSource(dataSourceId);
 
  166     private boolean isValidSearch() {
 
  167         boolean enabled = 
false;
 
  168         for (FileSearchFilter filter : this.getFilters()) {
 
  169             if (filter.isEnabled()) {
 
  171                 if (!filter.isValid()) {
 
  172                     errorLabel.setText(filter.getLastError());
 
  177         errorLabel.setText(
"");
 
  185     @NbBundle.Messages({
"FileSearchPanel.emptyNode.display.text=No results found.",
 
  186         "FileSearchPanel.searchingNode.display.text=Performing file search by attributes. Please wait.",
 
  187         "FileSearchPanel.searchingPath.text=File Search In Progress",
 
  188         "FileSearchPanel.cancelledSearch.text=Search Was Cancelled"})
 
  189     private void search() {
 
  190         if (searchWorker != null && searchWorker.isDone()) {
 
  191             searchWorker.cancel(
true);
 
  194             if (this.isValidSearch()) {
 
  196                 Case currentCase = Case.getCurrentCaseThrows(); 
 
  197                  Node emptyNode = 
new TableFilterNode(
new EmptyNode(Bundle.FileSearchPanel_searchingNode_display_text()), 
true);
 
  198                 String title = NbBundle.getMessage(this.getClass(), 
"FileSearchPanel.search.results.title", ++resultWindowCount);
 
  199                 String pathText = Bundle.FileSearchPanel_searchingPath_text();
 
  200                 final DataResultTopComponent searchResultWin = DataResultTopComponent.createInstance(title, pathText,
 
  202                 searchResultWin.requestActive(); 
 
  203                 searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  204                 searchWorker = 
new SwingWorker<TableFilterNode, Void>() {
 
  205                     List<AbstractFile> contentList = null;
 
  208                     protected TableFilterNode doInBackground() throws Exception {
 
  210                             SleuthkitCase tskDb = currentCase.getSleuthkitCase();
 
  211                             contentList = tskDb.findAllFilesWhere(getQuery());
 
  213                         } 
catch (TskCoreException ex) {
 
  214                             Logger logger = Logger.getLogger(this.getClass().getName());
 
  215                             logger.log(Level.WARNING, 
"Error while trying to get the number of matches.", ex); 
 
  217                         if (contentList == null) {
 
  218                             contentList = Collections.<AbstractFile>emptyList();
 
  220                         if (contentList.isEmpty()) {
 
  221                             return new TableFilterNode(
new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), 
true);
 
  223                         SearchNode sn = 
new SearchNode(contentList);
 
  224                         return new TableFilterNode(sn, 
true, sn.getName());
 
  228                     protected void done() {
 
  229                         String pathText = NbBundle.getMessage(this.getClass(), 
"FileSearchPanel.search.results.pathText");
 
  231                             TableFilterNode tableFilterNode = 
get();
 
  232                             if (tableFilterNode == null) {  
 
  233                                 tableFilterNode = 
new TableFilterNode(
new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), 
true);
 
  235                             if (searchResultWin != null && searchResultWin.isOpened()) {
 
  236                                 searchResultWin.setNode(tableFilterNode);
 
  237                                 searchResultWin.requestActive(); 
 
  245                             if (contentList.size() > 10000) {
 
  247                                 String msg = NbBundle.getMessage(this.getClass(), 
"FileSearchPanel.search.results.msg", contentList.size());
 
  248                                 String details = NbBundle.getMessage(this.getClass(), 
"FileSearchPanel.search.results.details");
 
  249                                 MessageNotifyUtil.Notify.info(msg, details);
 
  251                         } 
catch (InterruptedException | ExecutionException ex) {
 
  252                             logger.log(Level.SEVERE, 
"Error while performing file search by attributes", ex);
 
  253                         } 
catch (CancellationException ex) {
 
  254                             if (searchResultWin != null && searchResultWin.isOpened()) {
 
  255                                 Node emptyNode = 
new TableFilterNode(
new EmptyNode(Bundle.FileSearchPanel_cancelledSearch_text()), 
true);
 
  256                                 searchResultWin.setNode(emptyNode);
 
  257                                 pathText = Bundle.FileSearchPanel_cancelledSearch_text();
 
  259                             logger.log(Level.INFO, 
"File search by attributes was cancelled", ex);
 
  261                             if (searchResultWin != null && searchResultWin.isOpened()) {
 
  262                                 searchResultWin.setPath(pathText);
 
  263                                 searchResultWin.requestActive(); 
 
  264                                 searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  269                 if (searchResultWin != null && searchResultWin.isOpened()) {
 
  270                     searchResultWin.addPropertyChangeListener(
new PropertyChangeListener() {
 
  272                         public void propertyChange(PropertyChangeEvent evt) {
 
  273                             if (evt.getPropertyName().equals(
"tcClosed") && !searchWorker.isDone() && evt.getOldValue() == null) {
 
  274                                 searchWorker.cancel(
true);
 
  275                                 logger.log(Level.INFO, 
"User has closed the results window while search was in progress, search will be cancelled");
 
  280                 searchWorker.execute();
 
  282                 throw new FilterValidationException(
 
  283                         NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.search.exception.noFilterSelected.msg"));
 
  285         } 
catch (FilterValidationException | NoCurrentCaseException ex) {
 
  286             NotifyDescriptor d = 
new NotifyDescriptor.Message(
 
  287                     NbBundle.getMessage(
this.getClass(), 
"FileSearchPanel.search.validationErr.msg", ex.getMessage()));
 
  288             DialogDisplayer.getDefault().notify(d);
 
  302     private String getQuery() throws FilterValidationException {
 
  307         for (FileSearchFilter f : this.getEnabledFilters()) {
 
  308             String result = f.getPredicate();
 
  309             if (!result.isEmpty()) {
 
  311                     query += 
" AND (" + result + 
")"; 
 
  313                     query += 
" (" + result + 
")"; 
 
  319         if (query.isEmpty()) {
 
  320             throw new FilterValidationException(EMPTY_WHERE_CLAUSE);
 
  325     private Collection<FileSearchFilter> getFilters() {
 
  329     private Collection<FileSearchFilter> getEnabledFilters() {
 
  330         Collection<FileSearchFilter> enabledFilters = 
new ArrayList<>();
 
  332         for (FileSearchFilter f : this.getFilters()) {
 
  334                 enabledFilters.add(f);
 
  338         return enabledFilters;
 
  345     void resetCaseDependentFilters() {
 
  346         dataSourceFilter.resetDataSourceFilter();
 
  347         mimeTypeFilter.resetMimeTypeFilter();
 
  350     void addListenerToAll(ActionListener l) {
 
  351         searchButton.addActionListener(l);
 
  352         for (FileSearchFilter fsf : getFilters()) {
 
  353             fsf.addActionListener(l);
 
  357     void addCloseListener(ActionListener l) {
 
  358         closeButton.addActionListener(l);
 
  366     @SuppressWarnings(
"unchecked")
 
  368     private 
void initComponents() {
 
  369         java.awt.GridBagConstraints gridBagConstraints;
 
  371         filterPanel = 
new javax.swing.JPanel();
 
  372         buttonPanel = 
new javax.swing.JPanel();
 
  373         searchButton = 
new javax.swing.JButton();
 
  374         closeButton = 
new javax.swing.JButton();
 
  375         errorLabel = 
new javax.swing.JLabel();
 
  377         setPreferredSize(
new java.awt.Dimension(300, 300));
 
  378         setLayout(
new java.awt.GridBagLayout());
 
  380         filterPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
 
  381         filterPanel.setPreferredSize(
new java.awt.Dimension(300, 400));
 
  382         filterPanel.setLayout(
new javax.swing.BoxLayout(filterPanel, javax.swing.BoxLayout.Y_AXIS));
 
  383         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  384         gridBagConstraints.gridx = 0;
 
  385         gridBagConstraints.gridy = 0;
 
  386         gridBagConstraints.gridwidth = 2;
 
  387         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
 
  388         gridBagConstraints.weightx = 1.0;
 
  389         gridBagConstraints.weighty = 1.0;
 
  390         add(filterPanel, gridBagConstraints);
 
  392         buttonPanel.setLayout(
new java.awt.GridLayout(1, 0, 3, 0));
 
  394         searchButton.setText(
org.openide.util.NbBundle.getMessage(FileSearchPanel.class, 
"FileSearchPanel.searchButton.text")); 
 
  395         buttonPanel.add(searchButton);
 
  397         closeButton.setText(
org.openide.util.NbBundle.getMessage(FileSearchPanel.class, 
"FileSearchPanel.closeButton.text")); 
 
  398         buttonPanel.add(closeButton);
 
  400         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  401         gridBagConstraints.gridx = 1;
 
  402         gridBagConstraints.gridy = 1;
 
  403         gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
 
  404         gridBagConstraints.insets = 
new java.awt.Insets(0, 0, 5, 5);
 
  405         add(buttonPanel, gridBagConstraints);
 
  407         errorLabel.setForeground(
new java.awt.Color(255, 51, 51));
 
  408         errorLabel.setText(
org.openide.util.NbBundle.getMessage(FileSearchPanel.class, 
"FileSearchPanel.errorLabel.text")); 
 
  409         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  410         gridBagConstraints.gridx = 0;
 
  411         gridBagConstraints.gridy = 1;
 
  412         gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
 
  413         gridBagConstraints.weightx = 1.0;
 
  414         add(errorLabel, gridBagConstraints);
 
  418     private javax.swing.JPanel buttonPanel;
 
  419     private javax.swing.JButton closeButton;
 
  420     private javax.swing.JLabel errorLabel;
 
  421     private javax.swing.JPanel filterPanel;
 
  422     private javax.swing.JButton searchButton;