/*
* Autopsy Forensic Browser
*
* Copyright 2011 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* FileSearchPanel.java
*
* Created on Mar 5, 2012, 1:51:50 PM
*/
package org.sleuthkit.autopsy.filesearch;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JLabel;
import javax.swing.border.EmptyBorder;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* FileSearchPanel that present search options
*/
class FileSearchPanel extends javax.swing.JPanel {
private final List<FilterArea> filterAreas = new ArrayList<>();
private static int resultWindowCount = 0; //keep track of result windows so they get unique names
private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, "FileSearchPanel.emptyWhereClause.text");
enum EVENT {
CHECKED
}
/**
* Creates new form FileSearchPanel
*/
public FileSearchPanel() {
initComponents();
customizeComponents();
}
/**
* This method is called from within the constructor to initialize the form.
*/
private void customizeComponents() {
JLabel label = new JLabel(NbBundle.getMessage(this.getClass(), "FileSearchPanel.custComp.label.text"));
label.setAlignmentX(Component.LEFT_ALIGNMENT);
label.setBorder(new EmptyBorder(0, 0, 10, 0));
filterPanel.add(label);
// Create and add filter areas
this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.name"), new NameSearchFilter()));
List<FileSearchFilter> metadataFilters = new ArrayList<>();
metadataFilters.add(new SizeSearchFilter());
metadataFilters.add(new MimeTypeFilter());
metadataFilters.add(new DateSearchFilter());
this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), metadataFilters));
this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.knownStatus"), new KnownStatusSearchFilter()));
for (FilterArea fa : this.filterAreas) {
fa.setMaximumSize(new Dimension(Integer.MAX_VALUE, fa.getMinimumSize().height));
fa.setAlignmentX(Component.LEFT_ALIGNMENT);
filterPanel.add(fa);
}
for (FileSearchFilter filter : this.getFilters()) {
filter.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
searchButton.setEnabled(isValidSearch());
}
});
}
addListenerToAll(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
search();
}
});
searchButton.setEnabled(isValidSearch());
}
/**
* @return true if any of the filters in the panel are enabled (checked)
*/
private boolean isValidSearch() {
boolean enabled = false;
for (FileSearchFilter filter : this.getFilters()) {
if (filter.isEnabled()) {
enabled = true;
if (!filter.isValid()) {
return false;
}
}
}
return enabled;
}
/**
* Action when the "Search" button is pressed.
*
*/
private void search() {
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
if (this.isValidSearch()) {
String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount);
String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText");
// try to get the number of matches first
Case currentCase = Case.getCurrentCase(); // get the most updated case
long totalMatches = 0;
List<AbstractFile> contentList = null;
try {
SleuthkitCase tskDb = currentCase.getSleuthkitCase();
contentList = tskDb.findAllFilesWhere(this.getQuery());
} catch (TskCoreException ex) {
Logger logger = Logger.getLogger(this.getClass().getName());
logger.log(Level.WARNING, "Error while trying to get the number of matches.", ex); //NON-NLS
}
if (contentList == null) {
contentList = Collections.<AbstractFile>emptyList();
}
SearchNode sn = new SearchNode(contentList);
final TopComponent searchResultWin = DataResultTopComponent.createInstance(title, pathText,
new TableFilterNode(sn, true, sn.getName()), contentList.size());
searchResultWin.requestActive(); // make it the active top component
/**
* If total matches more than 1000, pop up a dialog box that say
* the performance maybe be slow and to increase the
* performance, tell the users to refine their search.
*/
if (totalMatches > 10000) {
// show info
String msg = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.msg", totalMatches);
String details = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.details");
MessageNotifyUtil.Notify.info(msg, details);
}
} else {
throw new FilterValidationException(
NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.exception.noFilterSelected.msg"));
}
} catch (FilterValidationException ex) {
NotifyDescriptor d = new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.validationErr.msg", ex.getMessage()));
DialogDisplayer.getDefault().notify(d);
} finally {
this.setCursor(null);
}
}
/**
* Gets the SQL query to get the data from the database based on the
* criteria that user chooses on the FileSearch.
*
* @return query the SQL query
*
* @throws
* org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException
* if
* an
* enabled
* filter
* is
* in
* an
* invalid
* state
*/
private String getQuery() throws FilterValidationException {
//String query = "SELECT " + tempQuery + " FROM tsk_files WHERE ";
String query = "";
int i = 0;
for (FileSearchFilter f : this.getEnabledFilters()) {
String result = f.getPredicate();
if (!result.isEmpty()) {
if (i > 0) {
query += " AND (" + result + ")"; //NON-NLS
} else {
query += " (" + result + ")"; //NON-NLS
}
++i;
}
}
if (query.isEmpty()) {
throw new FilterValidationException(EMPTY_WHERE_CLAUSE);
}
return query;
}
private Collection<FileSearchFilter> getFilters() {
Collection<FileSearchFilter> filters = new ArrayList<>();
for (FilterArea fa : this.filterAreas) {
filters.addAll(fa.getFilters());
}
return filters;
}
private Collection<FileSearchFilter> getEnabledFilters() {
Collection<FileSearchFilter> enabledFilters = new ArrayList<>();
for (FileSearchFilter f : this.getFilters()) {
if (f.isEnabled()) {
enabledFilters.add(f);
}
}
return enabledFilters;
}
void addListenerToAll(ActionListener l) {
searchButton.addActionListener(l);
for (FilterArea fa : this.filterAreas) {
for (FileSearchFilter fsf : fa.getFilters()) {
fsf.addActionListener(l);
}
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
filterPanel = new javax.swing.JPanel();
searchButton = new javax.swing.JButton();
setPreferredSize(new java.awt.Dimension(300, 300));
filterPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
filterPanel.setPreferredSize(new java.awt.Dimension(300, 400));
filterPanel.setLayout(new javax.swing.BoxLayout(filterPanel, javax.swing.BoxLayout.Y_AXIS));
searchButton.setText(org.openide.util.NbBundle.getMessage(FileSearchPanel.class, "FileSearchPanel.searchButton.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(filterPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(searchButton)
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(filterPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(0, 0, 0)
.addComponent(searchButton)
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel filterPanel;
private javax.swing.JButton searchButton;
// End of variables declaration//GEN-END:variables
}