Merge branch 'develop' of https://github.com/briangsweeney/autopsy into 3788-intercase-intracase-merge

# Conflicts:
#	Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java
#	Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java
#	Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java
#	Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
This commit is contained in:
Brian Sweeney 2018-07-18 11:00:04 -06:00
commit 1f08352ae5
45 changed files with 688 additions and 391 deletions

View File

@ -64,14 +64,14 @@ public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction<B
* *
* @param oldArtifactTag tag to be replaced * @param oldArtifactTag tag to be replaced
* @param newTagName name of the tag to replace with * @param newTagName name of the tag to replace with
* @param comment the comment for the tag use an empty string for no comment * @param newComment the newComment for the tag use an empty string for no newComment
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
"# {0} - old tag name", "# {0} - old tag name",
"# {1} - artifactID", "# {1} - artifactID",
"ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}."}) "ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}."})
@Override @Override
protected void replaceTag( BlackboardArtifactTag oldArtifactTag, TagName newTagName, String comment) { protected void replaceTag( BlackboardArtifactTag oldArtifactTag, TagName newTagName, String newComment) {
new SwingWorker<Void, Void>() { new SwingWorker<Void, Void>() {
@Override @Override
@ -91,7 +91,7 @@ public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction<B
logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldArtifactTag.getName().getDisplayName(), newTagName.getDisplayName(), oldArtifactTag.getContent().getName()}); //NON-NLS logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldArtifactTag.getName().getDisplayName(), newTagName.getDisplayName(), oldArtifactTag.getContent().getName()}); //NON-NLS
tagsManager.deleteBlackboardArtifactTag(oldArtifactTag); tagsManager.deleteBlackboardArtifactTag(oldArtifactTag);
tagsManager.addBlackboardArtifactTag(oldArtifactTag.getArtifact(), newTagName, comment); tagsManager.addBlackboardArtifactTag(oldArtifactTag.getArtifact(), newTagName, newComment);
} catch (TskCoreException tskCoreException) { } catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS

View File

@ -64,7 +64,7 @@ public final class ReplaceContentTagAction extends ReplaceTagAction<ContentTag>
"# {1} - content obj id", "# {1} - content obj id",
"ReplaceContentTagAction.replaceTag.alert=Unable to replace tag {0} for {1}."}) "ReplaceContentTagAction.replaceTag.alert=Unable to replace tag {0} for {1}."})
@Override @Override
protected void replaceTag(ContentTag oldTag, TagName newTagName, String comment) { protected void replaceTag(ContentTag oldTag, TagName newTagName, String newComment) {
new SwingWorker<Void, Void>() { new SwingWorker<Void, Void>() {
@Override @Override
@ -84,7 +84,7 @@ public final class ReplaceContentTagAction extends ReplaceTagAction<ContentTag>
logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS
tagsManager.deleteContentTag(oldTag); tagsManager.deleteContentTag(oldTag);
tagsManager.addContentTag(oldTag.getContent(), newTagName, comment); tagsManager.addContentTag(oldTag.getContent(), newTagName, newComment);
} catch (TskCoreException tskCoreException) { } catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS

View File

@ -141,7 +141,7 @@ abstract class ReplaceTagAction<T extends Tag> extends AbstractAction implements
// Add action to replace the tag // Add action to replace the tag
tagNameItem.addActionListener((ActionEvent event) -> { tagNameItem.addActionListener((ActionEvent event) -> {
selectedTags.forEach((oldtag) -> { selectedTags.forEach((oldtag) -> {
replaceTag(oldtag, entry.getValue(), ""); replaceTag(oldtag, entry.getValue(), oldtag.getComment());
}); });
}); });
@ -178,7 +178,7 @@ abstract class ReplaceTagAction<T extends Tag> extends AbstractAction implements
TagName newTagName = GetTagNameDialog.doDialog(); TagName newTagName = GetTagNameDialog.doDialog();
if (null != newTagName) { if (null != newTagName) {
selectedTags.forEach((oldtag) -> { selectedTags.forEach((oldtag) -> {
replaceTag(oldtag, newTagName, ""); replaceTag(oldtag, newTagName, oldtag.getComment());
}); });
} }
}); });

View File

@ -129,6 +129,7 @@ public class Case {
private static final String EXPORT_FOLDER = "Export"; //NON-NLS private static final String EXPORT_FOLDER = "Export"; //NON-NLS
private static final String LOG_FOLDER = "Log"; //NON-NLS private static final String LOG_FOLDER = "Log"; //NON-NLS
private static final String REPORTS_FOLDER = "Reports"; //NON-NLS private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
private static final String CONFIG_FOLDER = "Config"; // NON-NLS
private static final String TEMP_FOLDER = "Temp"; //NON-NLS private static final String TEMP_FOLDER = "Temp"; //NON-NLS
private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
private static final String CASE_ACTION_THREAD_NAME = "%s-case-action"; private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
@ -291,9 +292,10 @@ public class Case {
*/ */
ADDING_DATA_SOURCE_FAILED, ADDING_DATA_SOURCE_FAILED,
/** /**
* A new data source has been added to the current case. The old value * A new data source or series of data sources have been added to the
* of the PropertyChangeEvent is null, the new value is the newly-added * current case. The old value of the PropertyChangeEvent is null, the
* data source (type: Content). Cast the PropertyChangeEvent to * new value is the newly-added data source (type: Content). Cast the
* PropertyChangeEvent to
* org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent to * org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent to
* access additional event data. * access additional event data.
*/ */
@ -1098,6 +1100,10 @@ public class Case {
/* /*
* Open the top components (windows within the main application * Open the top components (windows within the main application
* window). * window).
*
* Note: If the core windows are not opened here, they will be
* opened via the DirectoryTreeTopComponent 'propertyChange()'
* method on a DATA_SOURCE_ADDED event.
*/ */
if (newCurrentCase.hasData()) { if (newCurrentCase.hasData()) {
CoreComponentControl.openCoreWindows(); CoreComponentControl.openCoreWindows();
@ -1351,6 +1357,16 @@ public class Case {
return getOrCreateSubdirectory(REPORTS_FOLDER); return getOrCreateSubdirectory(REPORTS_FOLDER);
} }
/**
* Gets the full path to the config directory for this case, creating it if
* it does not exist.
*
* @return The config directory path.
*/
public String getConfigDirectory() {
return getOrCreateSubdirectory(CONFIG_FOLDER);
}
/** /**
* Gets the full path to the module output directory for this case, creating * Gets the full path to the module output directory for this case, creating
* it if it does not exist. * it if it does not exist.

View File

@ -1,6 +1,5 @@
OptionsCategory_Name_TagNamesOptions=Tags OptionsCategory_Name_TagNamesOptions=Tags
OptionsCategory_TagNames=TagNames OptionsCategory_TagNames=TagNames
Blackboard.unableToIndexArtifact.error.msg=Unable to index blackboard artifact {0}
TagNameDialog.title.text=New Tag TagNameDialog.title.text=New Tag
TagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ; TagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ;
TagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name TagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name

View File

@ -105,7 +105,7 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction {
dbManager.updateAttributeInstanceComment(correlationAttribute); dbManager.updateAttributeInstanceComment(correlationAttribute);
} }
} catch (EamDbException ex) { } catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); logger.log(Level.SEVERE, "Error adding comment", ex);
} }
} }
return centralRepoCommentDialog.getComment(); return centralRepoCommentDialog.getComment();

View File

@ -58,6 +58,9 @@ abstract class AbstractSqlEamDb implements EamDb {
protected int bulkArtifactsThreshold; protected int bulkArtifactsThreshold;
private final Map<String, Collection<CorrelationAttribute>> bulkArtifacts; private final Map<String, Collection<CorrelationAttribute>> bulkArtifacts;
// Maximum length for the value column in the instance tables
static final int MAX_VALUE_LENGTH = 256;
// number of instances to keep in bulk queue before doing an insert. // number of instances to keep in bulk queue before doing an insert.
// Update Test code if this changes. It's hard coded there. // Update Test code if this changes. It's hard coded there.
static final int DEFAULT_BULK_THRESHHOLD = 1000; static final int DEFAULT_BULK_THRESHHOLD = 1000;
@ -472,7 +475,8 @@ abstract class AbstractSqlEamDb implements EamDb {
if (eamDataSource.getCaseID() == -1) { if (eamDataSource.getCaseID() == -1) {
throw new EamDbException("Case ID is -1"); throw new EamDbException("Case ID is -1");
} else if (eamDataSource.getID() != -1) { } else if (eamDataSource.getID() != -1) {
throw new EamDbException("Database ID is already set in object"); // This data source is already in the central repo
return;
} }
Connection conn = connect(); Connection conn = connect();
@ -632,6 +636,13 @@ abstract class AbstractSqlEamDb implements EamDb {
if (eamArtifact.getCorrelationValue() == null) { if (eamArtifact.getCorrelationValue() == null) {
throw new EamDbException("Correlation value is null"); throw new EamDbException("Correlation value is null");
} }
if (eamArtifact.getCorrelationValue().length() >= MAX_VALUE_LENGTH) {
throw new EamDbException("Artifact value too long for central repository."
+ "\nCorrelationArtifact ID: " + eamArtifact.getID()
+ "\nCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName()
+ "\nCorrelationArtifact Value: " + eamArtifact.getCorrelationValue());
}
Connection conn = connect(); Connection conn = connect();
@ -1056,27 +1067,50 @@ abstract class AbstractSqlEamDb implements EamDb {
if (!eamArtifact.getCorrelationValue().isEmpty()) { if (!eamArtifact.getCorrelationValue().isEmpty()) {
if (eamInstance.getCorrelationCase() == null) { if (eamInstance.getCorrelationCase() == null) {
throw new EamDbException("CorrelationAttributeInstance case is null"); throw new EamDbException("CorrelationAttributeInstance case is null for: "
+ "\n\tCorrelationArtifact ID: " + eamArtifact.getID()
+ "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName()
+ "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue());
} }
if (eamInstance.getCorrelationDataSource() == null) { if (eamInstance.getCorrelationDataSource() == null) {
throw new EamDbException("CorrelationAttributeInstance data source is null"); throw new EamDbException("CorrelationAttributeInstance data source is null for: "
+ "\n\tCorrelationArtifact ID: " + eamArtifact.getID()
+ "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName()
+ "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue());
} }
if (eamInstance.getKnownStatus() == null) { if (eamInstance.getKnownStatus() == null) {
throw new EamDbException("CorrelationAttributeInstance known status is null"); throw new EamDbException("CorrelationAttributeInstance known status is null for: "
+ "\n\tCorrelationArtifact ID: " + eamArtifact.getID()
+ "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName()
+ "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue()
+ "\n\tEam Instance: "
+ "\n\t\tCaseId: " + eamInstance.getCorrelationDataSource().getCaseID()
+ "\n\t\tDeviceID: " + eamInstance.getCorrelationDataSource().getDeviceID());
} }
bulkPs.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); if (eamArtifact.getCorrelationValue().length() < MAX_VALUE_LENGTH) {
bulkPs.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); bulkPs.setString(1, eamInstance.getCorrelationCase().getCaseUUID());
bulkPs.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); bulkPs.setString(2, eamInstance.getCorrelationDataSource().getDeviceID());
bulkPs.setString(4, eamArtifact.getCorrelationValue()); bulkPs.setInt(3, eamInstance.getCorrelationDataSource().getCaseID());
bulkPs.setString(5, eamInstance.getFilePath()); bulkPs.setString(4, eamArtifact.getCorrelationValue());
bulkPs.setByte(6, eamInstance.getKnownStatus().getFileKnownValue()); bulkPs.setString(5, eamInstance.getFilePath());
if ("".equals(eamInstance.getComment())) { bulkPs.setByte(6, eamInstance.getKnownStatus().getFileKnownValue());
bulkPs.setNull(7, Types.INTEGER); if ("".equals(eamInstance.getComment())) {
bulkPs.setNull(7, Types.INTEGER);
} else {
bulkPs.setString(7, eamInstance.getComment());
}
bulkPs.addBatch();
} else { } else {
bulkPs.setString(7, eamInstance.getComment()); logger.log(Level.WARNING, ("Artifact value too long for central repository."
+ "\n\tCorrelationArtifact ID: " + eamArtifact.getID()
+ "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName()
+ "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue())
+ "\n\tEam Instance: "
+ "\n\t\tCaseId: " + eamInstance.getCorrelationDataSource().getCaseID()
+ "\n\t\tDeviceID: " + eamInstance.getCorrelationDataSource().getDeviceID()
+ "\n\t\tFilePath: " + eamInstance.getFilePath());
} }
bulkPs.addBatch();
} }
} }
} }
@ -1821,11 +1855,13 @@ abstract class AbstractSqlEamDb implements EamDb {
return 0 < badInstances; return 0 < badInstances;
} }
/** /**
* Process the Artifact instance in the EamDb * Process the Artifact instance in the EamDb
* *
* @param type EamArtifact.Type to search for * @param type EamArtifact.Type to search for
* @param instanceTableCallback callback to process the instance * @param instanceTableCallback callback to process the instance
*
* @throws EamDbException * @throws EamDbException
*/ */
@Override @Override

View File

@ -67,6 +67,7 @@ public class CorrelationDataSource implements Serializable {
/** /**
* Create a CorrelationDataSource object from a TSK Content object. * Create a CorrelationDataSource object from a TSK Content object.
* This will add it to the central repository.
* *
* @param correlationCase the current CorrelationCase used for ensuring * @param correlationCase the current CorrelationCase used for ensuring
* uniqueness of DataSource * uniqueness of DataSource

View File

@ -450,10 +450,10 @@ final class CaseEventListener implements PropertyChangeListener {
correlationCase = dbManager.newCase(openCase); correlationCase = dbManager.newCase(openCase);
} }
if (null == dbManager.getDataSource(correlationCase, deviceId)) { if (null == dbManager.getDataSource(correlationCase, deviceId)) {
dbManager.newDataSource(CorrelationDataSource.fromTSKDataSource(correlationCase, newDataSource)); CorrelationDataSource.fromTSKDataSource(correlationCase, newDataSource);
} }
} catch (EamDbException ex) { } catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); //NON-NLS LOGGER.log(Level.SEVERE, "Error adding new data source to the central repository", ex); //NON-NLS
} catch (TskCoreException | TskDataException ex) { } catch (TskCoreException | TskDataException ex) {
LOGGER.log(Level.SEVERE, "Error getting data source from DATA_SOURCE_ADDED event content.", ex); //NON-NLS LOGGER.log(Level.SEVERE, "Error getting data source from DATA_SOURCE_ADDED event content.", ex); //NON-NLS
} }

View File

@ -276,12 +276,12 @@ public class IngestEventsListener {
} }
} }
if (FALSE == eamArtifacts.isEmpty()) { if (FALSE == eamArtifacts.isEmpty()) {
try { for (CorrelationAttribute eamArtifact : eamArtifacts) {
for (CorrelationAttribute eamArtifact : eamArtifacts) { try {
dbManager.addArtifact(eamArtifact); dbManager.addArtifact(eamArtifact);
} catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Error adding artifact to database.", ex); //NON-NLS
} }
} catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); //NON-NLS
} }
} // DATA_ADDED } // DATA_ADDED
} }

View File

@ -23,6 +23,8 @@ import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.table.TableColumn; import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
@ -36,10 +38,14 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
* rows and looking for wide cells. Rather, we just pick some reasonable values. * rows and looking for wide cells. Rather, we just pick some reasonable values.
*/ */
public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTable { public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTable {
private static final Map<String, Integer> COLUMN_WIDTHS; private static final Map<String, Integer> COLUMN_WIDTHS;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(CommonFilesSearchResultsViewerTable.class.getName());
private static final int DEFAULT_WIDTH = 100;
static { static {
Map<String, Integer> map = new HashMap<>(); Map<String, Integer> map = new HashMap<>();
map.put(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), 260); map.put(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), 260);
@ -50,10 +56,10 @@ public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTa
map.put(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), 100); map.put(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), 100);
map.put(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), 130); map.put(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), 130);
map.put(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1(), 300);; map.put(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1(), 300);;
COLUMN_WIDTHS = Collections.unmodifiableMap(map); COLUMN_WIDTHS = Collections.unmodifiableMap(map);
} }
@NbBundle.Messages({ @NbBundle.Messages({
"CommonFilesSearchResultsViewerTable.noDescText= ", "CommonFilesSearchResultsViewerTable.noDescText= ",
"CommonFilesSearchResultsViewerTable.filesColLbl=Files", "CommonFilesSearchResultsViewerTable.filesColLbl=Files",
@ -66,18 +72,24 @@ public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTa
"CommonFilesSearchResultsViewerTable.tagsColLbl1=Tags" "CommonFilesSearchResultsViewerTable.tagsColLbl1=Tags"
}) })
@Override @Override
protected void setColumnWidths(){ protected void setColumnWidths() {
TableColumnModel model = this.getColumnModel(); TableColumnModel model = this.getColumnModel();
Enumeration<TableColumn> columnsEnumerator = model.getColumns(); Enumeration<TableColumn> columnsEnumerator = model.getColumns();
while(columnsEnumerator.hasMoreElements()){ while (columnsEnumerator.hasMoreElements()) {
TableColumn column = columnsEnumerator.nextElement(); TableColumn column = columnsEnumerator.nextElement();
final String headerValue = column.getHeaderValue().toString(); final String headerValue = column.getHeaderValue().toString();
final Integer get = COLUMN_WIDTHS.get(headerValue);
final Integer defaultWidth = COLUMN_WIDTHS.get(headerValue);
column.setPreferredWidth(get);
if(defaultWidth == null){
column.setPreferredWidth(DEFAULT_WIDTH);
LOGGER.log(Level.SEVERE, String.format("Tried to set width on a column not supported by the CommonFilesSearchResultsViewerTable: %s", headerValue));
} else {
column.setPreferredWidth(defaultWidth);
}
} }
} }
} }

View File

@ -88,7 +88,21 @@ final public class CoreComponentControl {
TopComponent directoryTree = null; TopComponent directoryTree = null;
TopComponent favorites = null; TopComponent favorites = null;
final WindowManager windowManager = WindowManager.getDefault(); final WindowManager windowManager = WindowManager.getDefault();
// Set the UI selections to null before closing the top components.
// Otherwise it may experience errors trying to load data for the closed case.
for (Mode mode : windowManager.getModes()) { for (Mode mode : windowManager.getModes()) {
for (TopComponent tc : windowManager.getOpenedTopComponents(mode)) {
if(tc instanceof DataContent) {
((DataContent) tc).setNode(null);
} else if(tc instanceof DataResult) {
((DataResult) tc).setNode(null);
}
}
}
for (Mode mode : windowManager.getModes()) {
for (TopComponent tc : windowManager.getOpenedTopComponents(mode)) { for (TopComponent tc : windowManager.getOpenedTopComponents(mode)) {
String tcName = tc.getName(); String tcName = tc.getName();
@ -105,7 +119,7 @@ final public class CoreComponentControl {
} }
} }
} }
if (directoryTree != null) { if (directoryTree != null) {
directoryTree.close(); directoryTree.close();
} }

View File

@ -249,7 +249,7 @@ public final class FileTypes implements AutopsyVisitableItem {
if (typesRoot.showCounts) { if (typesRoot.showCounts) {
//only show "(counting...)" the first time, otherwise it is distracting. //only show "(counting...)" the first time, otherwise it is distracting.
setDisplayName(getDisplayNameBase() + ((childCount < 0) ? Bundle.FileTypes_bgCounting_placeholder() setDisplayName(getDisplayNameBase() + ((childCount < 0) ? Bundle.FileTypes_bgCounting_placeholder()
: ("(" + childCount + ")"))); //NON-NLS : (" (" + childCount + ")"))); //NON-NLS
new SwingWorker<Long, Void>() { new SwingWorker<Long, Void>() {
@Override @Override
protected Long doInBackground() throws Exception { protected Long doInBackground() throws Exception {

View File

@ -120,3 +120,8 @@ AddExternalViewerRulePanel.exePathTextField.text=
AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension
AddExternalViewerRulePanel.extRadioButton.text=Extension AddExternalViewerRulePanel.extRadioButton.text=Extension
DirectoryTreeTopComponent.groupByDatasourceCheckBox.text=Group by Data Source DirectoryTreeTopComponent.groupByDatasourceCheckBox.text=Group by Data Source
GroupDataSourcesDialog.dataSourceCountLabel.text=jLabel1
GroupDataSourcesDialog.queryLabel.text=Would you like to group by data source for faster loading?
GroupDataSourcesDialog.yesButton.text=Yes
GroupDataSourcesDialog.noButton.text=No
GroupDataSourcesDialog.title=Group by Data Source?

View File

@ -24,6 +24,11 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException; import java.beans.PropertyVetoException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
@ -35,6 +40,7 @@ import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent; import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener; import java.util.prefs.PreferenceChangeListener;
import java.util.Properties;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
@ -62,6 +68,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataExplorer;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.CreditCards; import org.sleuthkit.autopsy.datamodel.CreditCards;
@ -103,6 +110,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName());
private AutopsyTreeChildrenFactory autopsyTreeChildrenFactory; private AutopsyTreeChildrenFactory autopsyTreeChildrenFactory;
private Children autopsyTreeChildren; private Children autopsyTreeChildren;
private static final long DEFAULT_DATASOURCE_GROUPING_THRESHOLD = 5; // Threshold for prompting the user about grouping by data source
private static final String GROUPING_THRESHOLD_NAME = "GroupDataSourceThreshold";
private static final String SETTINGS_FILE = "CasePreferences.properties"; //NON-NLS
/** /**
* the constructor * the constructor
@ -125,7 +135,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
this.forwardList = new LinkedList<>(); this.forwardList = new LinkedList<>();
backButton.setEnabled(false); backButton.setEnabled(false);
forwardButton.setEnabled(false); forwardButton.setEnabled(false);
groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource()); groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource());
} }
@ -149,7 +159,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} }
} }
}); });
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.DATA_SOURCE_ADDED), this); Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.DATA_SOURCE_ADDED), this);
this.em.addPropertyChangeListener(this); this.em.addPropertyChangeListener(this);
IngestManager.getInstance().addIngestJobEventListener(this); IngestManager.getInstance().addIngestJobEventListener(this);
@ -370,6 +380,51 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
return TopComponent.PERSISTENCE_NEVER; return TopComponent.PERSISTENCE_NEVER;
} }
/**
* Ask the user if they want to group by data source when opening a large
* case.
*
* @param currentCase
* @param dataSourceCount
*/
private void promptForDataSourceGrouping(Case currentCase, int dataSourceCount) {
Path settingsFile = Paths.get(currentCase.getConfigDirectory(), SETTINGS_FILE); //NON-NLS
if (settingsFile.toFile().exists()) {
// Read the setting
try (InputStream inputStream = Files.newInputStream(settingsFile)) {
Properties props = new Properties();
props.load(inputStream);
if (props.getProperty("groupByDataSource", "false").equals("true")) {
UserPreferences.setGroupItemsInTreeByDatasource(true);
groupByDatasourceCheckBox.setSelected(true);
}
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Error reading settings file", ex);
}
} else {
GroupDataSourcesDialog dialog = new GroupDataSourcesDialog(dataSourceCount);
dialog.display();
if (dialog.groupByDataSourceSelected()) {
UserPreferences.setGroupItemsInTreeByDatasource(true);
groupByDatasourceCheckBox.setSelected(true);
}
// Save the response
Properties props = new Properties();
if (dialog.groupByDataSourceSelected()) {
props.setProperty("groupByDataSource", "true");
} else {
props.setProperty("groupByDataSource", "false");
}
try (OutputStream fos = Files.newOutputStream(settingsFile)) {
props.store(fos, ""); //NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Error writing settings file", ex);
}
}
}
/** /**
* Called only when top component was closed on all workspaces before and * Called only when top component was closed on all workspaces before and
* now is opened for the first time on some workspace. The intent is to * now is opened for the first time on some workspace. The intent is to
@ -377,6 +432,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* existing workspaces. Subclasses will usually perform initializing tasks * existing workspaces. Subclasses will usually perform initializing tasks
* here. * here.
*/ */
@NbBundle.Messages({"# {0} - dataSourceCount",
"DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contains {0} data sources. Would you like to group by data source for faster loading?",
"DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source?"})
@Override @Override
public void componentOpened() { public void componentOpened() {
// change the cursor to "waiting cursor" for this operation // change the cursor to "waiting cursor" for this operation
@ -392,9 +450,34 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
if (null == currentCase || currentCase.hasData() == false) { if (null == currentCase || currentCase.hasData() == false) {
getTree().setRootVisible(false); // hide the root getTree().setRootVisible(false); // hide the root
} else { } else {
// If the case contains a lot of data sources, and they aren't already grouping
// by data source, give the user the option to do so before loading the tree.
if (RuntimeProperties.runningWithGUI()) {
long threshold = DEFAULT_DATASOURCE_GROUPING_THRESHOLD;
if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME)) {
try {
threshold = Long.parseLong(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME));
} catch (NumberFormatException ex) {
LOGGER.log(Level.SEVERE, "Group data sources threshold is not a number", ex);
}
} else {
ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME, String.valueOf(threshold));
}
try {
int dataSourceCount = currentCase.getDataSources().size();
if (!UserPreferences.groupItemsInTreeByDatasource()
&& dataSourceCount > threshold) {
promptForDataSourceGrouping(currentCase, dataSourceCount);
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error loading data sources", ex);
}
}
// if there's at least one image, load the image and open the top componen // if there's at least one image, load the image and open the top componen
autopsyTreeChildrenFactory = new AutopsyTreeChildrenFactory(); autopsyTreeChildrenFactory = new AutopsyTreeChildrenFactory();
autopsyTreeChildren = Children.create(autopsyTreeChildrenFactory, true); autopsyTreeChildren = Children.create(autopsyTreeChildrenFactory, true);
Node root = new AbstractNode(autopsyTreeChildren) { Node root = new AbstractNode(autopsyTreeChildren) {
//JIRA-2807: What is the point of these overrides? //JIRA-2807: What is the point of these overrides?
/** /**
@ -453,9 +536,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
tree.collapseNode(views); tree.collapseNode(views);
} }
/* /*
* JIRA-2806: What is this supposed to do? Right now it selects * JIRA-2806: What is this supposed to do? Right now it
* the data sources node, but the comment seems to indicate * selects the data sources node, but the comment seems to
* it is supposed to select the first datasource. * indicate it is supposed to select the first datasource.
*/ */
// select the first image node, if there is one // select the first image node, if there is one
// (this has to happen after dataResult is opened, because the event // (this has to happen after dataResult is opened, because the event
@ -484,7 +567,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
// dataResult active) // dataResult active)
try { try {
Node[] selections = get(); Node[] selections = get();
if (selections != null && selections.length > 0){ if (selections != null && selections.length > 0) {
em.setSelectedNodes(selections); em.setSelectedNodes(selections);
} }
} catch (PropertyVetoException ex) { } catch (PropertyVetoException ex) {
@ -592,15 +675,15 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} }
/** /**
* The "listener" that listens to any changes made in the Case.java class. * The "listener" that monitors changes made in the Case class. This serves
* It will do something based on the changes in the Case.java class. * the purpose of keeping the UI in sync with the data as it changes.
* *
* @param evt the property change event * @param event The property change event.
*/ */
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent event) {
if (RuntimeProperties.runningWithGUI()) { if (RuntimeProperties.runningWithGUI()) {
String changed = evt.getPropertyName(); String changed = event.getPropertyName();
if (changed.equals(Case.Events.CURRENT_CASE.toString())) { // changed current case if (changed.equals(Case.Events.CURRENT_CASE.toString())) { // changed current case
// When a case is closed, the old value of this property is the // When a case is closed, the old value of this property is the
// closed Case object and the new value is null. When a case is // closed Case object and the new value is null. When a case is
@ -610,15 +693,15 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
// opened events instead of property change events would be a better // opened events instead of property change events would be a better
// solution. Either way, more probably needs to be done to clean up // solution. Either way, more probably needs to be done to clean up
// data model objects when a case is closed. // data model objects when a case is closed.
if (evt.getOldValue() != null && evt.getNewValue() == null) { if (event.getOldValue() != null && event.getNewValue() == null) {
// The current case has been closed. Reset the ExplorerManager. // The current case has been closed. Reset the ExplorerManager.
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); em.setRootContext(emptyNode);
}); });
} else if (evt.getNewValue() != null) { } else if (event.getNewValue() != null) {
// A new case has been opened. Reset the ExplorerManager. // A new case has been opened. Reset the ExplorerManager.
Case newCase = (Case) evt.getNewValue(); Case newCase = (Case) event.getNewValue();
final String newCaseName = newCase.getName(); final String newCaseName = newCase.getName();
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
em.getRootContext().setName(newCaseName); em.getRootContext().setName(newCaseName);
@ -642,20 +725,27 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* already closed. * already closed.
*/ */
try { try {
Case currentCase = Case.getCurrentCaseThrows(); Case.getCurrentCaseThrows();
// We only need to trigger openCoreWindows() when the /*
// first data source is added. * In case the Case 'updateGUIForCaseOpened()' method hasn't
if (currentCase.getDataSources().size() == 1) { * already done so, open the tree and all other core
* windows.
*
* TODO: (JIRA-4053) DirectoryTreeTopComponent should not be
* responsible for opening core windows. Consider moving
* this elsewhere.
*/
if (!this.isOpened()) {
SwingUtilities.invokeLater(CoreComponentControl::openCoreWindows); SwingUtilities.invokeLater(CoreComponentControl::openCoreWindows);
} }
} catch (NoCurrentCaseException | TskCoreException notUsed) { } catch (NoCurrentCaseException notUsed) {
/** /**
* Case is closed, do nothing. * Case is closed, do nothing.
*/ */
} }
} // change in node selection } // change in node selection
else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) { else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
respondSelection((Node[]) evt.getOldValue(), (Node[]) evt.getNewValue()); respondSelection((Node[]) event.getOldValue(), (Node[]) event.getNewValue());
} else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { } else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
// nothing to do here. // nothing to do here.
// all nodes should be listening for these events and update accordingly. // all nodes should be listening for these events and update accordingly.
@ -800,7 +890,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
/** /**
* Rebuilds the autopsy tree. * Rebuilds the autopsy tree.
* *
* Does nothing if there is no open case. * Does nothing if there is no open case.
*/ */
private void rebuildTree() { private void rebuildTree() {
@ -813,9 +903,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
return; return;
} }
if (null == currentCase || currentCase.hasData() == false) { if (null == currentCase || currentCase.hasData() == false) {
return; return;
} }
// refresh all children of the root. // refresh all children of the root.
autopsyTreeChildrenFactory.refreshChildren(); autopsyTreeChildrenFactory.refreshChildren();
@ -842,14 +932,14 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} }
}.execute(); }.execute();
} }
/** /**
* Selects the first node in the tree. * Selects the first node in the tree.
* *
*/ */
private void selectFirstChildNode () { private void selectFirstChildNode() {
Children rootChildren = em.getRootContext().getChildren(); Children rootChildren = em.getRootContext().getChildren();
if (rootChildren.getNodesCount() > 0) { if (rootChildren.getNodesCount() > 0) {
Node firstNode = rootChildren.getNodeAt(0); Node firstNode = rootChildren.getNodeAt(0);
if (firstNode != null) { if (firstNode != null) {
@ -858,6 +948,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} }
} }
} }
/** /**
* Set the selected node using a path to a previously selected node. * Set the selected node using a path to a previously selected node.
* *

View File

@ -18,8 +18,6 @@
*/ */
package org.sleuthkit.autopsy.filesearch; package org.sleuthkit.autopsy.filesearch;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -29,7 +27,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JList;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -47,38 +44,28 @@ public class DataSourcePanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(DataSourcePanel.class.getName()); private static final Logger logger = Logger.getLogger(DataSourcePanel.class.getName());
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Map<Long, String> dataSourceMap = new HashMap<>(); private final Map<Long, String> dataSourceMap = new HashMap<>();
private final List<String> toolTipList = new ArrayList<>();
/** /**
* Creates new form DataSourcePanel * Creates new form DataSourcePanel
*/ */
public DataSourcePanel() { public DataSourcePanel() {
initComponents(); initComponents();
this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { if (this.dataSourceList.getModel().getSize() > 1) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> {
}); firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
this.dataSourceList.addMouseMotionListener(new MouseMotionListener() { });
} else {
@Override /*
public void mouseDragged(MouseEvent evt) { * Disable data source filtering since there aren't multiple data
//Unused by now * sources to choose from.
} */
this.dataSourceCheckBox.setEnabled(false);
@Override this.dataSourceList.setEnabled(false);
public void mouseMoved(MouseEvent evt) { }
if (evt.getSource() instanceof JList<?>) {
JList<?> dsList = (JList<?>) evt.getSource();
int index = dsList.locationToIndex(evt.getPoint());
if (index > -1) {
dsList.setToolTipText(toolTipList.get(index));
}
}
}
});
} }
/** /**
* Get dataSourceMap with object id and data source display name. Add the data source full name to toolTipList * Get dataSourceMap with object id and data source display name.
* *
* @return The list of data source name * @return The list of data source name
*/ */
@ -93,8 +80,7 @@ public class DataSourcePanel extends javax.swing.JPanel {
String dsName = ds.getName(); String dsName = ds.getName();
File dataSourceFullName = new File(dsName); File dataSourceFullName = new File(dsName);
String displayName = dataSourceFullName.getName(); String displayName = dataSourceFullName.getName();
dataSourceMap.put(ds.getId(), displayName); dataSourceMap.put(ds.getId(), displayName);
toolTipList.add(dsName);
dsList.add(displayName); dsList.add(displayName);
} }
} catch (NoCurrentCaseException ex) { } catch (NoCurrentCaseException ex) {
@ -107,7 +93,8 @@ public class DataSourcePanel extends javax.swing.JPanel {
/** /**
* Get a set of data source object ids that are selected. * Get a set of data source object ids that are selected.
* @return A set of selected object ids. *
* @return A set of selected object ids.
*/ */
Set<Long> getDataSourcesSelected() { Set<Long> getDataSourcesSelected() {
Set<Long> dataSourceObjIdSet = new HashSet<>(); Set<Long> dataSourceObjIdSet = new HashSet<>();
@ -124,6 +111,7 @@ public class DataSourcePanel extends javax.swing.JPanel {
/** /**
* Is dataSourceCheckBox selected * Is dataSourceCheckBox selected
*
* @return true if the dataSoureCheckBox is selected * @return true if the dataSoureCheckBox is selected
*/ */
boolean isSelected() { boolean isSelected() {
@ -131,7 +119,8 @@ public class DataSourcePanel extends javax.swing.JPanel {
} }
/** /**
* Enable the dsList and dataSourceNoteLable if the dataSourceCheckBox is checked. * Enable the dsList and dataSourceNoteLable if the dataSourceCheckBox is
* checked.
*/ */
final void setComponentsEnabled() { final void setComponentsEnabled() {
boolean enabled = this.isSelected(); boolean enabled = this.isSelected();

View File

@ -556,17 +556,29 @@ class ReportHTML implements TableReportModule {
} }
/** /**
* Add a row to the current table. * Add a row to the current table, escaping the text to be contained in the
* row.
* *
* @param row values for each cell in the row * @param row values for each cell in the row
*/ */
@Override @Override
public void addRow(List<String> row) { public void addRow(List<String> row) {
addRow(row, true);
}
/**
* Add a row to the current table.
*
* @param row values for each cell in the row
* @param escapeText whether or not the text of the row should be escaped,
* true for escaped, false for not escaped
*/
private void addRow(List<String> row, boolean escapeText) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("\t<tr>\n"); //NON-NLS builder.append("\t<tr>\n"); //NON-NLS
for (String cell : row) { for (String cell : row) {
String escapeHTMLCell = EscapeUtil.escapeHtml(cell); String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
builder.append("\t\t<td>").append(escapeHTMLCell).append("</td>\n"); //NON-NLS builder.append("\t\t<td>").append(cellText).append("</td>\n"); //NON-NLS
} }
builder.append("\t</tr>\n"); //NON-NLS builder.append("\t</tr>\n"); //NON-NLS
rowCount++; rowCount++;
@ -593,7 +605,7 @@ class ReportHTML implements TableReportModule {
public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) { public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
Content content = contentTag.getContent(); Content content = contentTag.getContent();
if (content instanceof AbstractFile == false) { if (content instanceof AbstractFile == false) {
addRow(row); addRow(row, true);
return; return;
} }
AbstractFile file = (AbstractFile) content; AbstractFile file = (AbstractFile) content;
@ -647,7 +659,7 @@ class ReportHTML implements TableReportModule {
int pages = 1; int pages = 1;
for (Content content : images) { for (Content content : images) {
if (currentRow.size() == THUMBNAIL_COLUMNS) { if (currentRow.size() == THUMBNAIL_COLUMNS) {
addRow(currentRow); addRow(currentRow, false);
currentRow.clear(); currentRow.clear();
} }
@ -727,7 +739,7 @@ class ReportHTML implements TableReportModule {
// Finish out the row. // Finish out the row.
currentRow.add(""); currentRow.add("");
} }
addRow(currentRow); addRow(currentRow, false);
} }
// manually set rowCount to be the total number of images. // manually set rowCount to be the total number of images.
@ -1094,8 +1106,8 @@ class ReportHTML implements TableReportModule {
summary.append("<div class=\"title\">\n"); //NON-NLS summary.append("<div class=\"title\">\n"); //NON-NLS
summary.append(writeSummaryCaseDetails()); summary.append(writeSummaryCaseDetails());
summary.append(writeSummaryImageInfo()); summary.append(writeSummaryImageInfo());
summary.append(writeSummarySoftwareInfo(skCase,ingestJobs)); summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
summary.append(writeSummaryIngestHistoryInfo(skCase,ingestJobs)); summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
if (generatorLogoSet) { if (generatorLogoSet) {
summary.append("<div class=\"left\">\n"); //NON-NLS summary.append("<div class=\"left\">\n"); //NON-NLS
summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
@ -1126,14 +1138,13 @@ class ReportHTML implements TableReportModule {
} }
} }
} }
/** /**
* Write the case details section of the summary for this report. * Write the case details section of the summary for this report.
* *
* @return StringBuilder updated html report with case details * @return StringBuilder updated html report with case details
*/ */
private StringBuilder writeSummaryCaseDetails() {
private StringBuilder writeSummaryCaseDetails(){
StringBuilder summary = new StringBuilder(); StringBuilder summary = new StringBuilder();
String caseName = currentCase.getDisplayName(); String caseName = currentCase.getDisplayName();
String caseNumber = currentCase.getNumber(); String caseNumber = currentCase.getNumber();
@ -1146,40 +1157,39 @@ class ReportHTML implements TableReportModule {
imagecount = 0; imagecount = 0;
} }
summary.append("<div class=\"title\">\n"); //NON-NLS summary.append("<div class=\"title\">\n"); //NON-NLS
if (agencyLogoSet) { if (agencyLogoSet) {
summary.append("<div class=\"left\">\n"); //NON-NLS summary.append("<div class=\"left\">\n"); //NON-NLS
summary.append("<img src=\""); summary.append("<img src=\"");
summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString()); summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
summary.append("\" />\n"); //NON-NLS summary.append("\" />\n"); //NON-NLS
summary.append("</div>\n"); //NON-NLS
}
final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
summary.append("<table>\n"); //NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseName")) //NON-NLS
.append("</td><td>").append(caseName).append("</td></tr>\n"); //NON-NLS NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseNum")) //NON-NLS
.append("</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle //NON-NLS
.getMessage(this.getClass(), "ReportHTML.writeSum.noCaseNum")).append("</td></tr>\n"); //NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.examiner")).append("</td><td>") //NON-NLS
.append(!examiner.isEmpty() ? examiner : NbBundle
.getMessage(this.getClass(), "ReportHTML.writeSum.noExaminer"))
.append("</td></tr>\n"); //NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.numImages")) //NON-NLS
.append("</td><td>").append(imagecount).append("</td></tr>\n"); //NON-NLS
summary.append("</table>\n"); //NON-NLS
summary.append("</div>\n"); //NON-NLS summary.append("</div>\n"); //NON-NLS
summary.append("<div class=\"clear\"></div>\n"); //NON-NLS }
summary.append("</div>\n"); //NON-NLS final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
return summary; summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
summary.append("<table>\n"); //NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseName")) //NON-NLS
.append("</td><td>").append(caseName).append("</td></tr>\n"); //NON-NLS NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseNum")) //NON-NLS
.append("</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle //NON-NLS
.getMessage(this.getClass(), "ReportHTML.writeSum.noCaseNum")).append("</td></tr>\n"); //NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.examiner")).append("</td><td>") //NON-NLS
.append(!examiner.isEmpty() ? examiner : NbBundle
.getMessage(this.getClass(), "ReportHTML.writeSum.noExaminer"))
.append("</td></tr>\n"); //NON-NLS
summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.numImages")) //NON-NLS
.append("</td><td>").append(imagecount).append("</td></tr>\n"); //NON-NLS
summary.append("</table>\n"); //NON-NLS
summary.append("</div>\n"); //NON-NLS
summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
summary.append("</div>\n"); //NON-NLS
return summary;
} }
/** /**
* Write the Image Information section of the summary for this report. * Write the Image Information section of the summary for this report.
* *
* @return StringBuilder updated html report with Image Information * @return StringBuilder updated html report with Image Information
*/ */
private StringBuilder writeSummaryImageInfo() { private StringBuilder writeSummaryImageInfo() {
StringBuilder summary = new StringBuilder(); StringBuilder summary = new StringBuilder();
summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading")); summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading"));
@ -1208,13 +1218,12 @@ class ReportHTML implements TableReportModule {
summary.append("</div>\n"); //NON-NLS summary.append("</div>\n"); //NON-NLS
return summary; return summary;
} }
/** /**
* Write the software information section of the summary for this report. * Write the software information section of the summary for this report.
* *
* @return StringBuilder updated html report with software information * @return StringBuilder updated html report with software information
*/ */
private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) { private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
StringBuilder summary = new StringBuilder(); StringBuilder summary = new StringBuilder();
summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading")); summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading"));
@ -1244,13 +1253,12 @@ class ReportHTML implements TableReportModule {
summary.append("<div class=\"clear\"></div>\n"); //NON-NLS summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
return summary; return summary;
} }
/** /**
* Write the Ingest History section of the summary for this report. * Write the Ingest History section of the summary for this report.
* *
* @return StringBuilder updated html report with ingest history * @return StringBuilder updated html report with ingest history
*/ */
private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) { private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
StringBuilder summary = new StringBuilder(); StringBuilder summary = new StringBuilder();
try { try {
@ -1304,4 +1312,4 @@ class ReportHTML implements TableReportModule {
+ thumbFile.getName(); + thumbFile.getName();
} }
} }

View File

@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import static org.sleuthkit.autopsy.casemodule.services.TagsManager.getNotableTagLabel;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -53,6 +54,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.Type;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
@ -538,6 +540,65 @@ class TableReportGenerator {
logger.log(Level.SEVERE, "Exception while getting open case: ", ex); //NON-NLS logger.log(Level.SEVERE, "Exception while getting open case: ", ex); //NON-NLS
return; return;
} }
// Get a list of all selected tag IDs
String tagIDList = "";
if( ! tagNamesFilter.isEmpty()) {
try {
Map<String, TagName> tagNamesMap = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap();
for(String tagDisplayName : tagNamesFilter) {
if(tagNamesMap.containsKey(tagDisplayName)) {
if (! tagIDList.isEmpty()) {
tagIDList += ",";
}
tagIDList += tagNamesMap.get(tagDisplayName).getId();
} else {
// If the tag name ends with "(Notable)", try stripping that off
if(tagDisplayName.endsWith(getNotableTagLabel())) {
String editedDisplayName = tagDisplayName.substring(0, tagDisplayName.length() - getNotableTagLabel().length());
if(tagNamesMap.containsKey(editedDisplayName)) {
if (! tagIDList.isEmpty()) {
tagIDList += ",";
}
tagIDList += tagNamesMap.get(editedDisplayName).getId();
}
}
}
}
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.SEVERE, "Exception while getting tag info - proceeding without tag filter: ", ex); //NON-NLS
tagIDList = "";
}
}
// Check if there are any ad-hoc results
String adHocCountQuery = "SELECT COUNT(*) FROM " + //NON-NLS
"(SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 ";//NON-NLS
if (!tagIDList.isEmpty()) {
adHocCountQuery += ", blackboard_artifact_tags as tag "; //NON-NLS
}
adHocCountQuery += "WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") "; // NON-NLS
if (!tagIDList.isEmpty()) {
adHocCountQuery += " AND (art.artifact_id = tag.artifact_id) AND (tag.tag_name_id IN (" + tagIDList + ")) "; //NON-NLS
}
adHocCountQuery += "EXCEPT " + // NON-NLS
"SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ")) "; //NON-NLS
int adHocCount = 0;
try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(adHocCountQuery)) {
ResultSet adHocCountResultSet = dbQuery.getResultSet();
if (adHocCountResultSet.next()) {
adHocCount = adHocCountResultSet.getInt(1); //NON-NLS
} else {
throw new TskCoreException("Error counting ad hoc keywords");
}
} catch (TskCoreException | SQLException ex) {
errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryKWLists"));
logger.log(Level.SEVERE, "Failed to count ad hoc searches with query " + adHocCountQuery, ex); //NON-NLS
return;
}
// Create the query to get the keyword list names
if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) {
orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS
} else { } else {
@ -546,16 +607,25 @@ class TableReportGenerator {
String keywordListQuery String keywordListQuery
= "SELECT att.value_text AS list " = "SELECT att.value_text AS list "
+ //NON-NLS + //NON-NLS
"FROM blackboard_attributes AS att, blackboard_artifacts AS art " "FROM blackboard_attributes AS att, blackboard_artifacts AS art "; // NON-NLS
+ //NON-NLS if(! tagIDList.isEmpty()) {
"WHERE att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " keywordListQuery += ", blackboard_artifact_tags as tag "; //NON-NLS
}
keywordListQuery += "WHERE att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " "
+ //NON-NLS + //NON-NLS
"AND art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + " " "AND art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + " "
+ //NON-NLS + //NON-NLS
"AND att.artifact_id = art.artifact_id " "AND att.artifact_id = art.artifact_id ";
+ //NON-NLS if (! tagIDList.isEmpty()) {
"GROUP BY list " + orderByClause; //NON-NLS keywordListQuery += "AND (art.artifact_id = tag.artifact_id) " + //NON-NLS
"AND (tag.tag_name_id IN (" + tagIDList + ")) "; //NON-NLS
}
if (adHocCount > 0) {
keywordListQuery += " UNION SELECT \"\" AS list ";
}
keywordListQuery += "GROUP BY list " + orderByClause; //NON-NLS
// Make the table of contents links for each list type
try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordListQuery)) { try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordListQuery)) {
ResultSet listsRs = dbQuery.getResultSet(); ResultSet listsRs = dbQuery.getResultSet();
List<String> lists = new ArrayList<>(); List<String> lists = new ArrayList<>();
@ -579,6 +649,7 @@ class TableReportGenerator {
return; return;
} }
// Query for keywords, grouped by list
if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) {
orderByClause = "ORDER BY convert_to(att3.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS orderByClause = "ORDER BY convert_to(att3.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS
+ "convert_to(att1.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + "convert_to(att1.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS
@ -588,9 +659,10 @@ class TableReportGenerator {
} else { } else {
orderByClause = "ORDER BY list ASC, keyword ASC, parent_path ASC, name ASC, preview ASC"; //NON-NLS orderByClause = "ORDER BY list ASC, keyword ASC, parent_path ASC, name ASC, preview ASC"; //NON-NLS
} }
// Query for keywords, grouped by list
String keywordsQuery // Query for keywords that are part of a list
= "SELECT art.artifact_id, art.obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name, f.parent_path AS parent_path " String keywordListsQuery
= "SELECT art.artifact_id AS artifact_id, art.obj_id AS obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name, f.parent_path AS parent_path "
+ //NON-NLS + //NON-NLS
"FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, blackboard_attributes AS att3, tsk_files AS f " "FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, blackboard_attributes AS att3, tsk_files AS f "
+ //NON-NLS + //NON-NLS
@ -608,9 +680,24 @@ class TableReportGenerator {
+ //NON-NLS + //NON-NLS
"AND (att3.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " "AND (att3.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") "
+ //NON-NLS + //NON-NLS
"AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " "AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") ";
+ //NON-NLS
orderByClause; //NON-NLS // Query for keywords that are not part of a list
String keywordAdHocQuery =
"SELECT art.artifact_id AS artifact_id, art.obj_id AS obj_id, att1.value_text AS keyword, att2.value_text AS preview, \"\" AS list, f.name AS name, f.parent_path AS parent_path " + // NON-NLS
"FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, tsk_files AS f " + // NON-NLS
"WHERE " + // NON-NLS
" (art.artifact_id IN (SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " + // NON-NLS
"EXCEPT " + // NON-NLS
"SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + "))) " + //NON-NLS
"AND (att1.artifact_id = art.artifact_id) " + //NON-NLS
"AND (att2.artifact_id = art.artifact_id) " + //NON-NLS
"AND (f.obj_id = art.obj_id) " + //NON-NLS
"AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID() + ") " + // NON-NLS
"AND (att2.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID() + ") " + // NON-NLS
"AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") "; // NON-NLS
String keywordsQuery = keywordListsQuery + " UNION " + keywordAdHocQuery + orderByClause;
try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordsQuery)) { try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordsQuery)) {
ResultSet resultSet = dbQuery.getResultSet(); ResultSet resultSet = dbQuery.getResultSet();
@ -1623,14 +1710,15 @@ class TableReportGenerator {
private HashSet<String> getUniqueTagNames(long artifactId) throws TskCoreException { private HashSet<String> getUniqueTagNames(long artifactId) throws TskCoreException {
HashSet<String> uniqueTagNames = new HashSet<>(); HashSet<String> uniqueTagNames = new HashSet<>();
String query = "SELECT display_name, artifact_id FROM tag_names AS tn, blackboard_artifact_tags AS bat " String query = "SELECT display_name, artifact_id, knownStatus FROM tag_names AS tn, blackboard_artifact_tags AS bat "
+ //NON-NLS + //NON-NLS
"WHERE tn.tag_name_id = bat.tag_name_id AND bat.artifact_id = " + artifactId; //NON-NLS "WHERE tn.tag_name_id = bat.tag_name_id AND bat.artifact_id = " + artifactId; //NON-NLS
try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) {
ResultSet tagNameRows = dbQuery.getResultSet(); ResultSet tagNameRows = dbQuery.getResultSet();
while (tagNameRows.next()) { while (tagNameRows.next()) {
uniqueTagNames.add(tagNameRows.getString("display_name")); //NON-NLS String notableString = tagNameRows.getInt("knownStatus") == TskData.FileKnown.BAD.ordinal() ? getNotableTagLabel() : "";
uniqueTagNames.add(tagNameRows.getString("display_name") + notableString); //NON-NLS
} }
} catch (TskCoreException | SQLException | NoCurrentCaseException ex) { } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
throw new TskCoreException("Error getting tag names for artifact: ", ex); throw new TskCoreException("Error getting tag names for artifact: ", ex);

View File

@ -104,16 +104,36 @@ class IntraCaseUtils {
} }
void setUp() { void setUp() {
CaseUtils.createAsCurrentCase(this.caseName); this.createAsCurrentCase();
final ImageDSProcessor imageDSProcessor = new ImageDSProcessor(); final ImageDSProcessor imageDSProcessor = new ImageDSProcessor();
IngestUtils.addDataSource(imageDSProcessor, imagePath1); this.addImageOne(imageDSProcessor);
IngestUtils.addDataSource(imageDSProcessor, imagePath2); this.addImageTwo(imageDSProcessor);
IngestUtils.addDataSource(imageDSProcessor, imagePath3); this.addImageThree(imageDSProcessor);
this.addImageFour(imageDSProcessor);
}
void addImageFour(final ImageDSProcessor imageDSProcessor) {
IngestUtils.addDataSource(imageDSProcessor, imagePath4); IngestUtils.addDataSource(imageDSProcessor, imagePath4);
} }
void addImageThree(final ImageDSProcessor imageDSProcessor) {
IngestUtils.addDataSource(imageDSProcessor, imagePath3);
}
void addImageTwo(final ImageDSProcessor imageDSProcessor) {
IngestUtils.addDataSource(imageDSProcessor, imagePath2);
}
void addImageOne(final ImageDSProcessor imageDSProcessor) {
IngestUtils.addDataSource(imageDSProcessor, imagePath1);
}
void createAsCurrentCase() {
CaseUtils.createAsCurrentCase(this.caseName);
}
Map<Long, String> getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException { Map<Long, String> getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException {
return this.dataSourceLoader.getDataSourceMap(); return this.dataSourceLoader.getDataSourceMap();
} }

View File

@ -33,7 +33,6 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.openide.util.Lookup; import org.openide.util.Lookup;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.LocalDiskDSProcessor;
import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor; import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS;

View File

@ -338,7 +338,7 @@ final class AutoIngestAdminActions {
dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job); AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job);
dashboard.getCompletedJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshChildrenEvent(dashboard.getMonitor().getJobsSnapshot())); dashboard.getCompletedJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshChildrenEvent(dashboard.getMonitor()));
dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
if (AutoIngestManager.CaseDeletionResult.FAILED == result) { if (AutoIngestManager.CaseDeletionResult.FAILED == result) {
JOptionPane.showMessageDialog(dashboard, JOptionPane.showMessageDialog(dashboard,

View File

@ -38,7 +38,6 @@ import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.core.ServicesMonitor;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.RefreshChildrenEvent; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.RefreshChildrenEvent;
/** /**
@ -257,7 +256,7 @@ final class AutoIngestDashboard extends JPanel implements Observer {
@Override @Override
public void update(Observable observable, Object arg) { public void update(Observable observable, Object arg) {
if (arg instanceof JobsSnapshot) { if (arg == null ) {
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
refreshTables(); refreshTables();
}); });
@ -271,9 +270,9 @@ final class AutoIngestDashboard extends JPanel implements Observer {
* @param nodeStateSnapshot The jobs snapshot. * @param nodeStateSnapshot The jobs snapshot.
*/ */
void refreshTables() { void refreshTables() {
pendingJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor.getJobsSnapshot())); pendingJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor));
runningJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor.getJobsSnapshot())); runningJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor));
completedJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor.getJobsSnapshot())); completedJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor));
} }
/** /**

View File

@ -33,7 +33,6 @@ import org.openide.nodes.Node;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot;
import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer;
import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer;
@ -61,13 +60,13 @@ final class AutoIngestJobsNode extends AbstractNode {
/** /**
* Construct a new AutoIngestJobsNode. * Construct a new AutoIngestJobsNode.
* *
* @param snapshot the snapshot which contains the AutoIngestJobs * @param monitor the monitor which gives access to the AutoIngestJobs
* @param status the status of the jobs being displayed * @param status the status of the jobs being displayed
* @param eventBus the event bus which will be used to send and receive * @param eventBus the event bus which will be used to send and receive
* refresh events * refresh events
*/ */
AutoIngestJobsNode(JobsSnapshot jobsSnapshot, AutoIngestJobStatus status, EventBus eventBus) { AutoIngestJobsNode(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) {
super(Children.create(new AutoIngestNodeChildren(jobsSnapshot, status, eventBus), false)); super(Children.create(new AutoIngestNodeChildren(monitor, status, eventBus), false));
refreshChildrenEventBus = eventBus; refreshChildrenEventBus = eventBus;
} }
@ -84,7 +83,7 @@ final class AutoIngestJobsNode extends AbstractNode {
static final class AutoIngestNodeChildren extends ChildFactory<AutoIngestJob> { static final class AutoIngestNodeChildren extends ChildFactory<AutoIngestJob> {
private final AutoIngestJobStatus autoIngestJobStatus; private final AutoIngestJobStatus autoIngestJobStatus;
private JobsSnapshot jobsSnapshot; private AutoIngestMonitor monitor;
private final RefreshChildrenSubscriber refreshChildrenSubscriber = new RefreshChildrenSubscriber(); private final RefreshChildrenSubscriber refreshChildrenSubscriber = new RefreshChildrenSubscriber();
private final EventBus refreshEventBus; private final EventBus refreshEventBus;
@ -92,13 +91,13 @@ final class AutoIngestJobsNode extends AbstractNode {
* Create children nodes for the AutoIngestJobsNode which will each * Create children nodes for the AutoIngestJobsNode which will each
* represent a single AutoIngestJob * represent a single AutoIngestJob
* *
* @param snapshot the snapshot which contains the AutoIngestJobs * @param monitor the monitor which gives access to the AutoIngestJobs
* @param status the status of the jobs being displayed * @param status the status of the jobs being displayed
* @param eventBus the event bus which the class registers to for * @param eventBus the event bus which the class registers to for
* refresh events * refresh events
*/ */
AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status, EventBus eventBus) { AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) {
jobsSnapshot = snapshot; this.monitor = monitor;
autoIngestJobStatus = status; autoIngestJobStatus = status;
refreshEventBus = eventBus; refreshEventBus = eventBus;
refreshChildrenSubscriber.register(refreshEventBus); refreshChildrenSubscriber.register(refreshEventBus);
@ -109,14 +108,14 @@ final class AutoIngestJobsNode extends AbstractNode {
List<AutoIngestJob> jobs; List<AutoIngestJob> jobs;
switch (autoIngestJobStatus) { switch (autoIngestJobStatus) {
case PENDING_JOB: case PENDING_JOB:
jobs = jobsSnapshot.getPendingJobs(); jobs = monitor.getPendingJobs();
Collections.sort(jobs); Collections.sort(jobs);
break; break;
case RUNNING_JOB: case RUNNING_JOB:
jobs = jobsSnapshot.getRunningJobs(); jobs = monitor.getRunningJobs();
break; break;
case COMPLETED_JOB: case COMPLETED_JOB:
jobs = jobsSnapshot.getCompletedJobs(); jobs = monitor.getCompletedJobs();
break; break;
default: default:
jobs = new ArrayList<>(); jobs = new ArrayList<>();
@ -167,7 +166,7 @@ final class AutoIngestJobsNode extends AbstractNode {
//Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus //Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus
//RefreshChildrenEvents can change which children are present however //RefreshChildrenEvents can change which children are present however
//RefreshJobEvents and RefreshCaseEvents can still change the order we want to display them in //RefreshJobEvents and RefreshCaseEvents can still change the order we want to display them in
jobsSnapshot = refreshEvent.getJobsSnapshot(); monitor = refreshEvent.getMonitor();
refresh(true); refresh(true);
} }

View File

@ -171,7 +171,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa
((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(refreshEvent); ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(refreshEvent);
} else { } else {
//Make a new AutoIngestJobsNode with it's own EventBus and set it as the root context //Make a new AutoIngestJobsNode with it's own EventBus and set it as the root context
explorerManager.setRootContext(new AutoIngestJobsNode(refreshEvent.getJobsSnapshot(), status, new EventBus("AutoIngestJobsNodeEventBus"))); explorerManager.setRootContext(new AutoIngestJobsNode(refreshEvent.getMonitor(), status, new EventBus("AutoIngestJobsNodeEventBus")));
} }
outline.setRowSelectionAllowed(true); outline.setRowSelectionAllowed(true);
outline.setFocusable(true); outline.setFocusable(true);

View File

@ -490,6 +490,14 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
break; break;
case RESUME: case RESUME:
resume(); resume();
/**
* Kick off an immediate scan so that the next pending job
* will be picked up sooner than having to wait for the
* InputDirScannerTask to run again.
*/
scanInputDirsNow();
break; break;
case SHUTDOWN: case SHUTDOWN:
shutDown(); shutDown();
@ -1840,13 +1848,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
*/ */
setChanged(); setChanged();
notifyObservers(Event.RESUMED); notifyObservers(Event.RESUMED);
/**
* Publish an event to let remote listeners know that the
* node has been resumed.
*/
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RESUMED, AutoIngestManager.LOCAL_HOST_NAME));
} }
/**
* Publish an event to let remote listeners know that the node
* has been resumed.
*/
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RESUMED, AutoIngestManager.LOCAL_HOST_NAME));
pauseLock.notifyAll(); pauseLock.notifyAll();
} }
} }
@ -2790,6 +2798,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
sysLogger.log(Level.INFO, "Finished ingest modules analysis for {0} ", manifestPath); sysLogger.log(Level.INFO, "Finished ingest modules analysis for {0} ", manifestPath);
IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot(); IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot();
for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) {
AutoIngestJobLogger nestedJobLogger = new AutoIngestJobLogger(manifestPath, snapshot.getDataSource(), caseDirectoryPath);
if (!snapshot.isCancelled()) { if (!snapshot.isCancelled()) {
List<String> cancelledModules = snapshot.getCancelledDataSourceIngestModules(); List<String> cancelledModules = snapshot.getCancelledDataSourceIngestModules();
if (!cancelledModules.isEmpty()) { if (!cancelledModules.isEmpty()) {
@ -2798,15 +2807,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
setCaseNodeDataErrorsOccurred(caseDirectoryPath); setCaseNodeDataErrorsOccurred(caseDirectoryPath);
for (String module : snapshot.getCancelledDataSourceIngestModules()) { for (String module : snapshot.getCancelledDataSourceIngestModules()) {
sysLogger.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, manifestPath)); sysLogger.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, manifestPath));
jobLogger.logIngestModuleCancelled(module); nestedJobLogger.logIngestModuleCancelled(module);
} }
} }
jobLogger.logAnalysisCompleted(); nestedJobLogger.logAnalysisCompleted();
} else { } else {
currentJob.setProcessingStage(AutoIngestJob.Stage.CANCELLING, Date.from(Instant.now())); currentJob.setProcessingStage(AutoIngestJob.Stage.CANCELLING, Date.from(Instant.now()));
currentJob.setErrorsOccurred(true); currentJob.setErrorsOccurred(true);
setCaseNodeDataErrorsOccurred(caseDirectoryPath); setCaseNodeDataErrorsOccurred(caseDirectoryPath);
jobLogger.logAnalysisCancelled(); nestedJobLogger.logAnalysisCancelled();
CancellationReason cancellationReason = snapshot.getCancellationReason(); CancellationReason cancellationReason = snapshot.getCancellationReason();
if (CancellationReason.NOT_CANCELLED != cancellationReason && CancellationReason.USER_CANCELLED != cancellationReason) { if (CancellationReason.NOT_CANCELLED != cancellationReason && CancellationReason.USER_CANCELLED != cancellationReason) {
throw new AnalysisStartupException(String.format("Analysis cancelled due to %s for %s", cancellationReason.getDisplayName(), manifestPath)); throw new AnalysisStartupException(String.format("Analysis cancelled due to %s for %s", cancellationReason.getDisplayName(), manifestPath));

View File

@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -116,11 +117,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
} catch (AutopsyEventException ex) { } catch (AutopsyEventException ex) {
throw new AutoIngestMonitorException("Failed to open auto ingest event channel", ex); //NON-NLS throw new AutoIngestMonitorException("Failed to open auto ingest event channel", ex); //NON-NLS
} }
coordSvcQueryExecutor.scheduleWithFixedDelay(new CoordinationServiceQueryTask(), 0, CORRD_SVC_QUERY_INERVAL_MINS, TimeUnit.MINUTES); coordSvcQueryExecutor.scheduleWithFixedDelay(new StateRefreshTask(), 0, CORRD_SVC_QUERY_INERVAL_MINS, TimeUnit.MINUTES);
eventPublisher.addSubscriber(EVENT_LIST, this); eventPublisher.addSubscriber(EVENT_LIST, this);
// Publish an event that asks running nodes to send their state. refreshNodeState();
eventPublisher.publishRemotely(new AutoIngestRequestNodeStateEvent(AutoIngestManager.Event.REPORT_STATE));
} }
/** /**
@ -172,7 +172,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
jobsSnapshot.removePendingJob(event.getJob()); jobsSnapshot.removePendingJob(event.getJob());
jobsSnapshot.addOrReplaceRunningJob(event.getJob()); jobsSnapshot.addOrReplaceRunningJob(event.getJob());
setChanged(); setChanged();
notifyObservers(jobsSnapshot); notifyObservers();
} }
} }
@ -190,7 +190,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
jobsSnapshot.removePendingJob(job); jobsSnapshot.removePendingJob(job);
// Update the state of the existing job in the running jobs table // Update the state of the existing job in the running jobs table
for (AutoIngestJob runningJob : jobsSnapshot.getRunningJobs()) { for (AutoIngestJob runningJob : getRunningJobs()) {
if (runningJob.equals(job)) { if (runningJob.equals(job)) {
runningJob.setIngestJobsSnapshot(job.getIngestJobSnapshots()); runningJob.setIngestJobsSnapshot(job.getIngestJobSnapshots());
runningJob.setIngestThreadSnapshot(job.getIngestThreadActivitySnapshots()); runningJob.setIngestThreadSnapshot(job.getIngestThreadActivitySnapshots());
@ -200,7 +200,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
} }
} }
setChanged(); setChanged();
notifyObservers(jobsSnapshot); notifyObservers();
} }
} }
@ -216,7 +216,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
jobsSnapshot.removeRunningJob(job); jobsSnapshot.removeRunningJob(job);
jobsSnapshot.addOrReplaceCompletedJob(job); jobsSnapshot.addOrReplaceCompletedJob(job);
setChanged(); setChanged();
notifyObservers(jobsSnapshot); notifyObservers();
} }
} }
@ -226,7 +226,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @param event A job/case prioritization event. * @param event A job/case prioritization event.
*/ */
private void handleCasePrioritizationEvent(AutoIngestCasePrioritizedEvent event) { private void handleCasePrioritizationEvent(AutoIngestCasePrioritizedEvent event) {
coordSvcQueryExecutor.submit(new CoordinationServiceQueryTask()); coordSvcQueryExecutor.submit(new StateRefreshTask());
} }
/** /**
@ -235,7 +235,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @param event A job/case deletion event. * @param event A job/case deletion event.
*/ */
private void handleCaseDeletedEvent(AutoIngestCaseDeletedEvent event) { private void handleCaseDeletedEvent(AutoIngestCaseDeletedEvent event) {
coordSvcQueryExecutor.submit(new CoordinationServiceQueryTask()); coordSvcQueryExecutor.submit(new StateRefreshTask());
} }
/** /**
@ -259,15 +259,35 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
} }
/** /**
* Gets the auto ingest monitor's current snapshot of the pending jobs * Gets the snapshot of the pending jobs queue for an auto ingest cluster.
* queue, running jobs list, and completed jobs list for an auto ingest
* cluster.
* *
* @return The snapshot. * @return The pending jobs queue.
*/ */
JobsSnapshot getJobsSnapshot() { List<AutoIngestJob> getPendingJobs() {
synchronized (jobsLock) { synchronized (jobsLock) {
return jobsSnapshot; return new ArrayList<>(jobsSnapshot.pendingJobs);
}
}
/**
* Gets the snapshot of the running jobs list for an auto ingest cluster.
*
* @return The running jobs list.
*/
List<AutoIngestJob> getRunningJobs() {
synchronized (jobsLock) {
return new ArrayList<>(jobsSnapshot.runningJobs);
}
}
/**
* Gets the snapshot of the completed jobs list for an auto ingest cluster.
*
* @return The completed jobs list.
*/
List<AutoIngestJob> getCompletedJobs() {
synchronized (jobsLock) {
return new ArrayList<>(jobsSnapshot.completedJobs);
} }
} }
@ -277,7 +297,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @return * @return
*/ */
List<AutoIngestNodeState> getNodeStates() { List<AutoIngestNodeState> getNodeStates() {
return nodeStates.values().stream().collect(Collectors.toList()); // We only report the state for nodes for which we have received
// a 'state' event in the last 15 minutes.
return nodeStates.values()
.stream()
.filter(s -> s.getLastSeenTime().isAfter(Instant.now().minus(Duration.ofMinutes(15))))
.collect(Collectors.toList());
} }
/** /**
@ -287,13 +312,20 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* *
* @return The refreshed snapshot. * @return The refreshed snapshot.
*/ */
JobsSnapshot refreshJobsSnapshot() { void refreshJobsSnapshot() {
synchronized (jobsLock) { synchronized (jobsLock) {
jobsSnapshot = queryCoordinationService(); jobsSnapshot = queryCoordinationService();
return jobsSnapshot;
} }
} }
/**
* Ask running auto ingest nodes to report their state.
*/
private void refreshNodeState() {
// Publish an event that asks running nodes to send their state.
eventPublisher.publishRemotely(new AutoIngestRequestNodeStateEvent(AutoIngestManager.Event.REPORT_STATE));
}
/** /**
* Gets a new snapshot of the pending jobs queue, running jobs list, and * Gets a new snapshot of the pending jobs queue, running jobs list, and
* completed jobs list for an auto ingest cluster. * completed jobs list for an auto ingest cluster.
@ -358,13 +390,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @throws AutoIngestMonitorException If there is an error removing the * @throws AutoIngestMonitorException If there is an error removing the
* priority of the jobs for the case. * priority of the jobs for the case.
* *
* @return The latest jobs snapshot.
*/ */
JobsSnapshot deprioritizeCase(final String caseName) throws AutoIngestMonitorException { void deprioritizeCase(final String caseName) throws AutoIngestMonitorException {
List<AutoIngestJob> jobsToDeprioritize = new ArrayList<>(); List<AutoIngestJob> jobsToDeprioritize = new ArrayList<>();
synchronized (jobsLock) { synchronized (jobsLock) {
for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { for (AutoIngestJob pendingJob : getPendingJobs()) {
if (pendingJob.getManifest().getCaseName().equals(caseName)) { if (pendingJob.getManifest().getCaseName().equals(caseName)) {
jobsToDeprioritize.add(pendingJob); jobsToDeprioritize.add(pendingJob);
} }
@ -395,7 +426,6 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, ""));
}).start(); }).start();
} }
return jobsSnapshot;
} }
} }
@ -407,13 +437,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @throws AutoIngestMonitorException If there is an error bumping the * @throws AutoIngestMonitorException If there is an error bumping the
* priority of the jobs for the case. * priority of the jobs for the case.
* *
* @return The latest jobs snapshot.
*/ */
JobsSnapshot prioritizeCase(final String caseName) throws AutoIngestMonitorException { void prioritizeCase(final String caseName) throws AutoIngestMonitorException {
List<AutoIngestJob> jobsToPrioritize = new ArrayList<>(); List<AutoIngestJob> jobsToPrioritize = new ArrayList<>();
int highestPriority = 0; int highestPriority = 0;
synchronized (jobsLock) { synchronized (jobsLock) {
for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { for (AutoIngestJob pendingJob : getPendingJobs()) {
if (pendingJob.getPriority() > highestPriority) { if (pendingJob.getPriority() > highestPriority) {
highestPriority = pendingJob.getPriority(); highestPriority = pendingJob.getPriority();
} }
@ -448,7 +477,6 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, ""));
}).start(); }).start();
} }
return jobsSnapshot;
} }
} }
@ -460,15 +488,14 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @throws AutoIngestMonitorException If there is an error removing the * @throws AutoIngestMonitorException If there is an error removing the
* priority of the job. * priority of the job.
* *
* @return The latest jobs snapshot.
*/ */
JobsSnapshot deprioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException { void deprioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException {
synchronized (jobsLock) { synchronized (jobsLock) {
AutoIngestJob jobToDeprioritize = null; AutoIngestJob jobToDeprioritize = null;
/* /*
* Make sure the job is still in the pending jobs queue. * Make sure the job is still in the pending jobs queue.
*/ */
for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { for (AutoIngestJob pendingJob : getPendingJobs()) {
if (pendingJob.equals(job)) { if (pendingJob.equals(job)) {
jobToDeprioritize = job; jobToDeprioritize = job;
break; break;
@ -506,7 +533,6 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
}).start(); }).start();
} }
return jobsSnapshot;
} }
} }
@ -518,9 +544,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @throws AutoIngestMonitorException If there is an error bumping the * @throws AutoIngestMonitorException If there is an error bumping the
* priority of the job. * priority of the job.
* *
* @return The latest jobs snapshot.
*/ */
JobsSnapshot prioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException { void prioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException {
synchronized (jobsLock) { synchronized (jobsLock) {
int highestPriority = 0; int highestPriority = 0;
AutoIngestJob jobToPrioritize = null; AutoIngestJob jobToPrioritize = null;
@ -528,7 +553,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* Get the highest known priority and make sure the job is still in * Get the highest known priority and make sure the job is still in
* the pending jobs queue. * the pending jobs queue.
*/ */
for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { for (AutoIngestJob pendingJob : getPendingJobs()) {
if (pendingJob.getPriority() > highestPriority) { if (pendingJob.getPriority() > highestPriority) {
highestPriority = pendingJob.getPriority(); highestPriority = pendingJob.getPriority();
} }
@ -569,7 +594,6 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
}).start(); }).start();
} }
return jobsSnapshot;
} }
} }
@ -591,7 +615,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
*/ */
void reprocessJob(AutoIngestJob job) throws AutoIngestMonitorException { void reprocessJob(AutoIngestJob job) throws AutoIngestMonitorException {
synchronized (jobsLock) { synchronized (jobsLock) {
if (!jobsSnapshot.getCompletedJobs().contains(job)) { if (!getCompletedJobs().contains(job)) {
return; return;
} }
@ -660,7 +684,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
// Update the state of completed jobs associated with this case to indicate // Update the state of completed jobs associated with this case to indicate
// that the case has been deleted // that the case has been deleted
for (AutoIngestJob completedJob : jobsSnapshot.getCompletedJobs()) { for (AutoIngestJob completedJob : getCompletedJobs()) {
if (caseName.equals(completedJob.getManifest().getCaseName())) { if (caseName.equals(completedJob.getManifest().getCaseName())) {
try { try {
completedJob.setProcessingStatus(DELETED); completedJob.setProcessingStatus(DELETED);
@ -724,25 +748,26 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
} }
/** /**
* A task that queries the coordination service for auto ingest manifest * A task that updates the state maintained by the monitor.
* node data and converts it to auto ingest jobs for publication top its * At present this includes auto ingest job and auto ingest node data.
* observers. * The job data is refreshed by querying the coordination service for
* auto ingest manifest nodes.
* The auto ingest node data is refreshed by publishing a message asking
* all nodes to report their state.
*/ */
private final class CoordinationServiceQueryTask implements Runnable { private final class StateRefreshTask implements Runnable {
/**
* Queries the coordination service for auto ingest manifest node data
* and converts it to auto ingest jobs for publication top its
* observers.
*/
@Override @Override
public void run() { public void run() {
if (!Thread.currentThread().isInterrupted()) { if (!Thread.currentThread().isInterrupted()) {
synchronized (jobsLock) { // Query coordination service for jobs data.
jobsSnapshot = queryCoordinationService(); refreshJobsSnapshot();
setChanged();
notifyObservers(jobsSnapshot); // Ask running auto ingest nodes to report their status.
} refreshNodeState();
setChanged();
notifyObservers();
} }
} }
@ -752,42 +777,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* A snapshot of the pending jobs queue, running jobs list and completed * A snapshot of the pending jobs queue, running jobs list and completed
* jobs list for an auto ingest cluster. * jobs list for an auto ingest cluster.
*/ */
static final class JobsSnapshot { private static final class JobsSnapshot {
private final Set<AutoIngestJob> pendingJobs = new HashSet<>(); private final Set<AutoIngestJob> pendingJobs = new HashSet<>();
private final Set<AutoIngestJob> runningJobs = new HashSet<>(); private final Set<AutoIngestJob> runningJobs = new HashSet<>();
private final Set<AutoIngestJob> completedJobs = new HashSet<>(); private final Set<AutoIngestJob> completedJobs = new HashSet<>();
/**
* Gets the snapshot of the pending jobs queue for an auto ingest
* cluster.
*
* @return The pending jobs queue.
*/
List<AutoIngestJob> getPendingJobs() {
return new ArrayList<>(this.pendingJobs);
}
/**
* Gets the snapshot of the running jobs list for an auto ingest
* cluster.
*
* @return The running jobs list.
*/
List<AutoIngestJob> getRunningJobs() {
return new ArrayList<>(this.runningJobs);
}
/**
* Gets the snapshot of the completed jobs list for an auto ingest
* cluster.
*
* @return The completed jobs list.
*/
List<AutoIngestJob> getCompletedJobs() {
return new ArrayList<>(this.completedJobs);
}
/** /**
* Adds an auto job to the snapshot of the pending jobs queue for an * Adds an auto job to the snapshot of the pending jobs queue for an
* auto ingest cluster. If an equivalent job already exists, it is * auto ingest cluster. If an equivalent job already exists, it is
@ -886,6 +881,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
private final String nodeName; private final String nodeName;
private final State nodeState; private final State nodeState;
private final Instant lastSeenTime;
AutoIngestNodeState(String name, Event event) { AutoIngestNodeState(String name, Event event) {
nodeName = name; nodeName = name;
@ -915,6 +911,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
nodeState = State.UNKNOWN; nodeState = State.UNKNOWN;
break; break;
} }
lastSeenTime = Instant.now();
} }
String getName() { String getName() {
@ -924,6 +921,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
State getState() { State getState() {
return nodeState; return nodeState;
} }
Instant getLastSeenTime() {
return lastSeenTime;
}
} }
/** /**

View File

@ -18,8 +18,6 @@
*/ */
package org.sleuthkit.autopsy.experimental.autoingest; package org.sleuthkit.autopsy.experimental.autoingest;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot;
/** /**
* Class which contains events to identify what should be refreshed in the * Class which contains events to identify what should be refreshed in the
* AutoIngestJobsNode * AutoIngestJobsNode
@ -31,19 +29,20 @@ class AutoIngestNodeRefreshEvents {
*/ */
static class AutoIngestRefreshEvent { static class AutoIngestRefreshEvent {
private final JobsSnapshot jobsSnapshot; private final AutoIngestMonitor monitor;
AutoIngestRefreshEvent(JobsSnapshot jobs) { AutoIngestRefreshEvent(AutoIngestMonitor monitor) {
this.jobsSnapshot = jobs; this.monitor = monitor;
} }
/** /**
* Get the state of the jobs lists when the event was fired. * Get the monitor which will provide access to the state of
* the jobs.
* *
* @return * @return
*/ */
JobsSnapshot getJobsSnapshot() { AutoIngestMonitor getMonitor() {
return this.jobsSnapshot; return this.monitor;
} }
} }
@ -56,8 +55,8 @@ class AutoIngestNodeRefreshEvents {
/** /**
* Constructs a RefreshChildrenEvent. * Constructs a RefreshChildrenEvent.
*/ */
RefreshChildrenEvent(JobsSnapshot jobs) { RefreshChildrenEvent(AutoIngestMonitor monitor) {
super(jobs); super(monitor);
} }
} }
@ -72,11 +71,11 @@ class AutoIngestNodeRefreshEvents {
/** /**
* Contructs a RefreshCaseEvent * Contructs a RefreshCaseEvent
* *
* @param jobs The current state of the jobs lists. * @param monitor The monitor that will provide access to the current state of the jobs lists.
* @param name The name of the case whose nodes should be refreshed. * @param name The name of the case whose nodes should be refreshed.
*/ */
RefreshCaseEvent(JobsSnapshot jobs, String name) { RefreshCaseEvent(AutoIngestMonitor monitor, String name) {
super(jobs); super(monitor);
caseName = name; caseName = name;
} }
@ -103,11 +102,11 @@ class AutoIngestNodeRefreshEvents {
/** /**
* Constructs a RefreshJobEvent. * Constructs a RefreshJobEvent.
* *
* @param jobs The curent state of the jobs lists. * @param monitor The monitor which will provide access to the current state of the jobs lists.
* @param job The job which should be refreshed. * @param job The job which should be refreshed.
*/ */
RefreshJobEvent(JobsSnapshot jobs, AutoIngestJob job) { RefreshJobEvent(AutoIngestMonitor monitor, AutoIngestJob job) {
super(jobs); super(monitor);
autoIngestJob = job; autoIngestJob = job;
} }

View File

@ -145,7 +145,7 @@ abstract class PrioritizationAction extends AbstractAction {
@Override @Override
AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) {
return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor.getJobsSnapshot(), getJob()); return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor, getJob());
} }
} }
@ -184,7 +184,7 @@ abstract class PrioritizationAction extends AbstractAction {
@Override @Override
AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) {
return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor.getJobsSnapshot(), getJob()); return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor, getJob());
} }
} }
@ -225,7 +225,7 @@ abstract class PrioritizationAction extends AbstractAction {
@Override @Override
AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) {
return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor.getJobsSnapshot(), getJob().getManifest().getCaseName()); return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor, getJob().getManifest().getCaseName());
} }
} }
@ -266,7 +266,7 @@ abstract class PrioritizationAction extends AbstractAction {
@Override @Override
AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) {
return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor.getJobsSnapshot(), getJob().getManifest().getCaseName()); return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor, getJob().getManifest().getCaseName());
} }
} }
} }

View File

@ -106,10 +106,22 @@ public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter
try { try {
imageInMemory = IOUtils.toByteArray(inputStream); imageInMemory = IOUtils.toByteArray(inputStream);
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.WARNING, "Unable to perform object detection on " + file.getName(), ex); logger.log(Level.WARNING, "Unable to read image to byte array for performing object detection on " + file.getParentPath() + file.getName() + " with object id of " + file.getId(), ex);
return IngestModule.ProcessResult.ERROR; return IngestModule.ProcessResult.ERROR;
} }
Mat originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_GRAYSCALE); Mat originalImage;
try {
originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_GRAYSCALE);
} catch (CvException ex) {
//The image was something which could not be decoded by OpenCv, our isImageThumbnailSupported(file) check above failed us
logger.log(Level.WARNING, "Unable to decode image from byte array to perform object detection on " + file.getParentPath() + file.getName() + " with object id of " + file.getId(), ex); //NON-NLS
return IngestModule.ProcessResult.ERROR;
} catch (Exception unexpectedException) {
//hopefully an unnecessary generic exception catch but currently present to catch any exceptions OpenCv throws which may not be documented
logger.log(Level.SEVERE, "Unexpected Exception encountered attempting to use OpenCV to decode picture: " + file.getParentPath() + file.getName() + " with object id of " + file.getId(), unexpectedException);
return IngestModule.ProcessResult.ERROR;
}
MatOfRect detectionRectangles = new MatOfRect(); //the rectangles which reprent the coordinates on the image for where objects were detected MatOfRect detectionRectangles = new MatOfRect(); //the rectangles which reprent the coordinates on the image for where objects were detected
for (String classifierKey : classifiers.keySet()) { for (String classifierKey : classifiers.keySet()) {
//apply each classifier to the file //apply each classifier to the file
@ -117,7 +129,10 @@ public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter
classifiers.get(classifierKey).detectMultiScale(originalImage, detectionRectangles); classifiers.get(classifierKey).detectMultiScale(originalImage, detectionRectangles);
} catch (CvException ignored) { } catch (CvException ignored) {
//The image was likely an image which we are unable to generate a thumbnail for, and the classifier was likely one where that is not acceptable //The image was likely an image which we are unable to generate a thumbnail for, and the classifier was likely one where that is not acceptable
logger.log(Level.INFO, String.format("Classifier '%s' could not be applied to file '%s'.", classifierKey, file.getParentPath() + file.getName())); //NON-NLS continue;
} catch (Exception unexpectedException) {
//hopefully an unnecessary generic exception catch but currently present to catch any exceptions OpenCv throws which may not be documented
logger.log(Level.SEVERE, "Unexpected Exception encountered for image " + file.getParentPath() + file.getName() + " with object id of " + file.getId() +" while trying to apply classifier " + classifierKey, unexpectedException);
continue; continue;
} }

View File

@ -203,17 +203,20 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
} }
void store() { void store() {
Case openCase;
try {
openCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
Logger.getLogger(ImageGalleryOptionsPanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return;
}
ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected()); ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected());
ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected()); ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected());
new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected()));
ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected()); ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected());
// If a case is open, save the per case setting
try {
Case openCase = Case.getCurrentCaseThrows();
new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected()));
} catch (NoCurrentCaseException ex) {
// It's not an error if there's no case open
}
} }
/** /**

View File

@ -360,7 +360,7 @@ public class GroupManager {
groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids";
} }
else { else {
groupConcatClause = "select group_concat(obj_id) as object_ids"; groupConcatClause = " group_concat(obj_id) as object_ids";
} }
String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type ";
try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS

View File

@ -105,7 +105,7 @@ class BrowserLocationAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactTypeName(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactTypeName()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName())

View File

@ -138,7 +138,7 @@ class CacheLocationAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName())

View File

@ -157,7 +157,7 @@ class CallLogAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index call log artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index call log artifact for keyword search.", artifact.getDisplayName())

View File

@ -164,7 +164,7 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index contact artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index contact artifact for keyword search.", artifact.getDisplayName())

View File

@ -115,7 +115,7 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index GPS route artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index GPS route artifact for keyword search.", artifact.getDisplayName())

View File

@ -116,7 +116,7 @@ class TangoMessageAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index Tango message artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index Tango message artifact for keyword search.", artifact.getDisplayName())

View File

@ -130,7 +130,7 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index text message artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index text message artifact for keyword search.", artifact.getDisplayName())

View File

@ -125,7 +125,7 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer):
blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard = Case.getCurrentCase().getServices().getBlackboard()
blackboard.indexArtifact(artifact) blackboard.indexArtifact(artifact)
except Blackboard.BlackboardException as ex: except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index WWF message artifact for keyword search.", artifact.getDisplayName()) MessageNotifyUtil.Notify.error("Failed to index WWF message artifact for keyword search.", artifact.getDisplayName())

View File

@ -43,7 +43,6 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel {
private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader");
protected int filesIndexed; protected int filesIndexed;
private final Map<Long, String> dataSourceMap = new HashMap<>(); private final Map<Long, String> dataSourceMap = new HashMap<>();
private final List<String> toolTipList = new ArrayList<>();
private List<DataSource> dataSources = new ArrayList<>(); private List<DataSource> dataSources = new ArrayList<>();
private final DefaultListModel<String> dataSourceListModel = new DefaultListModel<>(); private final DefaultListModel<String> dataSourceListModel = new DefaultListModel<>();
@ -153,14 +152,12 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel {
*/ */
synchronized List<String> getDataSourceArray() { synchronized List<String> getDataSourceArray() {
List<String> dsList = new ArrayList<>(); List<String> dsList = new ArrayList<>();
toolTipList.clear();
Collections.sort(this.dataSources, (DataSource ds1, DataSource ds2) -> ds1.getName().compareTo(ds2.getName())); Collections.sort(this.dataSources, (DataSource ds1, DataSource ds2) -> ds1.getName().compareTo(ds2.getName()));
for (DataSource ds : dataSources) { for (DataSource ds : dataSources) {
String dsName = ds.getName(); String dsName = ds.getName();
File dataSourceFullName = new File(dsName); File dataSourceFullName = new File(dsName);
String displayName = dataSourceFullName.getName(); String displayName = dataSourceFullName.getName();
dataSourceMap.put(ds.getId(), displayName); dataSourceMap.put(ds.getId(), displayName);
toolTipList.add(dsName);
dsList.add(displayName); dsList.add(displayName);
} }
return dsList; return dsList;
@ -175,22 +172,13 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel {
} }
/** /**
* Get dataSourceMap with object id and data source display name. Add the * Get dataSourceMap with object id and data source display name.
* data source full name to toolTipList
* *
* @return The list of data source name * @return The list of data source name
*/ */
Map<Long, String> getDataSourceMap() { Map<Long, String> getDataSourceMap() {
return dataSourceMap; return dataSourceMap;
} }
/**
* Get a list of tooltip text for data source
* @return A list of tool tips
*/
List<String> getDataSourceToolTipList() {
return toolTipList;
}
/** /**
* Get a list of DataSourceListModel * Get a list of DataSourceListModel

View File

@ -18,11 +18,11 @@
*/ */
package org.sleuthkit.autopsy.keywordsearch; package org.sleuthkit.autopsy.keywordsearch;
import java.awt.*; import java.awt.Component;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList; import java.util.ArrayList;
@ -32,7 +32,6 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JCheckBox; import javax.swing.JCheckBox;
import javax.swing.JList;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
@ -73,22 +72,6 @@ class DropdownListSearchPanel extends AdHocSearchPanel {
dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> {
firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null);
}); });
dataSourceList.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent evt) {
//Unused by now
}
@Override
public void mouseMoved(MouseEvent evt) {
JList<String> dsList = (JList<String>) evt.getSource();
int index = dsList.locationToIndex(evt.getPoint());
if (index > -1) {
dsList.setToolTipText(getDataSourceToolTipList().get(index));
}
}
});
} }
static synchronized DropdownListSearchPanel getDefault() { static synchronized DropdownListSearchPanel getDefault() {
@ -683,11 +666,21 @@ class DropdownListSearchPanel extends AdHocSearchPanel {
* Set the dataSourceList enabled if the dataSourceCheckBox is selected * Set the dataSourceList enabled if the dataSourceCheckBox is selected
*/ */
private void setComponentsEnabled() { private void setComponentsEnabled() {
boolean enabled = this.dataSourceCheckBox.isSelected();
this.dataSourceList.setEnabled(enabled); if (getDataSourceListModel().size() > 1) {
if (enabled) { this.dataSourceCheckBox.setEnabled(true);
this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1);
boolean enabled = this.dataSourceCheckBox.isSelected();
this.dataSourceList.setEnabled(enabled);
if (enabled) {
this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1);
} else {
this.dataSourceList.setSelectedIndices(new int[0]);
}
} else { } else {
this.dataSourceCheckBox.setEnabled(false);
this.dataSourceCheckBox.setSelected(false);
this.dataSourceList.setEnabled(false);
this.dataSourceList.setSelectedIndices(new int[0]); this.dataSourceList.setSelectedIndices(new int[0]);
} }
} }

View File

@ -23,8 +23,6 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList; import java.util.ArrayList;
@ -32,7 +30,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JList;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
@ -88,22 +85,6 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel {
this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> {
firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null);
}); });
this.dataSourceList.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent evt) {
//Unused by now
}
@Override
public void mouseMoved(MouseEvent evt) {
JList<String> DsList = (JList<String>) evt.getSource();
int index = DsList.locationToIndex(evt.getPoint());
if (index > -1) {
DsList.setToolTipText(getDataSourceToolTipList().get(index));
}
}
});
} }
/** /**
@ -390,18 +371,26 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel {
} }
setComponentsEnabled(); setComponentsEnabled();
firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null);
} }
/** /**
* Set the dataSourceList enabled if the dataSourceCheckBox is selected * Set the dataSourceList enabled if the dataSourceCheckBox is selected
*/ */
private void setComponentsEnabled() { private void setComponentsEnabled() {
boolean enabled = this.dataSourceCheckBox.isSelected(); if (getDataSourceListModel().size() > 1) {
this.dataSourceList.setEnabled(enabled); this.dataSourceCheckBox.setEnabled(true);
if (enabled) {
this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1); boolean enabled = this.dataSourceCheckBox.isSelected();
this.dataSourceList.setEnabled(enabled);
if (enabled) {
this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1);
} else {
this.dataSourceList.setSelectedIndices(new int[0]);
}
} else { } else {
this.dataSourceCheckBox.setEnabled(false);
this.dataSourceCheckBox.setSelected(false);
this.dataSourceList.setEnabled(false);
this.dataSourceList.setSelectedIndices(new int[0]); this.dataSourceList.setSelectedIndices(new int[0]);
} }
} }

View File

@ -28,7 +28,6 @@ import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.FileIngestModule;
@ -92,6 +91,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
private boolean startedSearching = false; private boolean startedSearching = false;
private List<ContentTextExtractor> textExtractors; private List<ContentTextExtractor> textExtractors;
private StringsTextExtractor stringExtractor; private StringsTextExtractor stringExtractor;
private TextFileExtractor txtFileExtractor;
private final KeywordSearchJobSettings settings; private final KeywordSearchJobSettings settings;
private boolean initialized = false; private boolean initialized = false;
private long jobId; private long jobId;
@ -244,6 +244,8 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
stringExtractor.setScripts(KeywordSearchSettings.getStringExtractScripts()); stringExtractor.setScripts(KeywordSearchSettings.getStringExtractScripts());
stringExtractor.setOptions(KeywordSearchSettings.getStringExtractOptions()); stringExtractor.setOptions(KeywordSearchSettings.getStringExtractOptions());
txtFileExtractor = new TextFileExtractor();
textExtractors = new ArrayList<>(); textExtractors = new ArrayList<>();
//order matters, more specific extractors first //order matters, more specific extractors first
textExtractors.add(new HtmlTextExtractor()); textExtractors.add(new HtmlTextExtractor());
@ -343,7 +345,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
textExtractors.clear(); textExtractors.clear();
textExtractors = null; textExtractors = null;
stringExtractor = null; stringExtractor = null;
txtFileExtractor = null;
initialized = false; initialized = false;
} }
@ -568,6 +570,17 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT); putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT);
} }
if ((wasTextAdded == false) && (aFile.getNameExtension().equalsIgnoreCase("txt"))) {
try {
if (Ingester.getDefault().indexText(txtFileExtractor, aFile, context)) {
putIngestStatus(jobId, aFile.getId(), IngestStatus.TEXT_INGESTED);
wasTextAdded = true;
}
} catch (IngesterException ex) {
logger.log(Level.WARNING, "Unable to index as unicode", ex);
}
}
// if it wasn't supported or had an error, default to strings // if it wasn't supported or had an error, default to strings
if (wasTextAdded == false) { if (wasTextAdded == false) {
extractStringsAndIndex(aFile); extractStringsAndIndex(aFile);

View File

@ -1,15 +1,15 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2012-2014 Basis Technology Corp. * Copyright 2012-2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -237,8 +237,19 @@ class SearchEngineURLQueryAnalyzer extends Extract {
try { //try to decode the url try { //try to decode the url
String decoded = URLDecoder.decode(x, "UTF-8"); //NON-NLS String decoded = URLDecoder.decode(x, "UTF-8"); //NON-NLS
return decoded; return decoded;
} catch (UnsupportedEncodingException uee) { //if it fails, return the encoded string } catch (UnsupportedEncodingException exception) { //if it fails, return the encoded string
logger.log(Level.FINE, "Error during URL decoding ", uee); //NON-NLS logger.log(Level.FINE, "Error during URL decoding, returning undecoded value:"
+ "\n\tURL: " + url
+ "\n\tUndecoded value: " + x
+ "\n\tEngine name: " + eng.getEngineName()
+ "\n\tEngine domain: " + eng.getDomainSubstring(), exception); //NON-NLS
return x;
} catch (IllegalArgumentException exception) { //if it fails, return the encoded string
logger.log(Level.SEVERE, "Illegal argument passed to URL decoding, returning undecoded value:"
+ "\n\tURL: " + url
+ "\n\tUndecoded value: " + x
+ "\n\tEngine name: " + eng.getEngineName()
+ "\n\tEngine domain: " + eng.getDomainSubstring(), exception); //NON-NLS)
return x; return x;
} }
} }

View File

@ -86,11 +86,12 @@ class Util {
public static String getBaseDomain(String url) { public static String getBaseDomain(String url) {
String host = null; String host = null;
//strip protocol //strip protocol
String cleanUrl = url.replaceFirst("/.*:\\/\\//", ""); String cleanUrl = url.replaceFirst(".*:\\/\\/", "");
//strip after slashes //strip after slashes
String dirToks[] = cleanUrl.split("/\\//"); String dirToks[] = cleanUrl.split("\\/");
if (dirToks.length > 0) { if (dirToks.length > 0) {
host = dirToks[0]; host = dirToks[0];
} else { } else {
@ -141,7 +142,6 @@ class Util {
if (result == null || result.trim().isEmpty()) { if (result == null || result.trim().isEmpty()) {
return getBaseDomain(value); return getBaseDomain(value);
} }
return result; return result;
} }