diff --git a/Core/ivy.xml b/Core/ivy.xml
index 36245ce14d..92be3f86e6 100644
--- a/Core/ivy.xml
+++ b/Core/ivy.xml
@@ -46,6 +46,9 @@
+
+
+
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 8d9c857fba..91f9aa13e0 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -11,6 +11,8 @@ file.reference.bcpkix-jdk15on-1.54.jar=release\\modules\\ext\\bcpkix-jdk15on-1.5
file.reference.bcprov-ext-jdk15on-1.54.jar=release\\modules\\ext\\bcprov-ext-jdk15on-1.54.jar
file.reference.bcprov-jdk15on-1.52.jar=release\\modules\\ext\\bcprov-jdk15on-1.52.jar
file.reference.bcprov-jdk15on-1.54.jar=release\\modules\\ext\\bcprov-jdk15on-1.54.jar
+file.reference.byte-buddy-1.10.13.jar=release\\modules\\ext\\byte-buddy-1.10.13.jar
+file.reference.byte-buddy-agent-1.10.13.jar=release\\modules\\ext\\byte-buddy-agent-1.10.13.jar
file.reference.c3p0-0.9.5.jar=release\\modules\\ext\\c3p0-0.9.5.jar
file.reference.checker-compat-qual-2.5.3.jar=release\\modules\\ext\\checker-compat-qual-2.5.3.jar
file.reference.commons-beanutils-1.9.2.jar=release\\modules\\ext\\commons-beanutils-1.9.2.jar
@@ -70,9 +72,11 @@ file.reference.jai_core-1.1.3.jar=release\\modules\\ext\\jai_core-1.1.3.jar
file.reference.jai_imageio-1.1.jar=release\\modules\\ext\\jai_imageio-1.1.jar
file.reference.javax.annotation-api-1.3.2.jar=release\\modules\\ext\\javax.annotation-api-1.3.2.jar
file.reference.javax.ws.rs-api-2.0.jar=release\\modules\\ext\\javax.ws.rs-api-2.0.jar
+file.reference.jcommon-1.0.23.jar=release/modules/ext/jcommon-1.0.23.jar
file.reference.jdom-2.0.5-contrib.jar=release\\modules\\ext\\jdom-2.0.5-contrib.jar
file.reference.jdom-2.0.5.jar=release\\modules\\ext\\jdom-2.0.5.jar
file.reference.jericho-html-3.3.jar=release\\modules\\ext\\jericho-html-3.3.jar
+file.reference.jfreechart-1.0.19.jar=release/modules/ext/jfreechart-1.0.19.jar
file.reference.jgraphx-4.1.0.jar=release\\modules\\ext\\jgraphx-4.1.0.jar
file.reference.jline-0.9.94.jar=release\\modules\\ext\\jline-0.9.94.jar
file.reference.jsoup-1.10.3.jar=release\\modules\\ext\\jsoup-1.10.3.jar
@@ -86,7 +90,9 @@ file.reference.listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar=re
file.reference.log4j-1.2.16.jar=release\\modules\\ext\\log4j-1.2.16.jar
file.reference.mchange-commons-java-0.2.9.jar=release\\modules\\ext\\mchange-commons-java-0.2.9.jar
file.reference.metadata-extractor-2.11.0.jar=release\\modules\\ext\\metadata-extractor-2.11.0.jar
+file.reference.mockito-core-3.5.7.jar=release\\modules\\ext\\mockito-core-3.5.7.jar
file.reference.netty-3.7.0.Final.jar=release\\modules\\ext\\netty-3.7.0.Final.jar
+file.reference.objenesis-3.1.jar=release\\modules\\ext\\objenesis-3.1.jar
file.reference.okhttp-2.7.5.jar=release\\modules\\ext\\okhttp-2.7.5.jar
file.reference.okio-1.6.0.jar=release\\modules\\ext\\okio-1.6.0.jar
file.reference.opencensus-api-0.19.2.jar=release\\modules\\ext\\opencensus-api-0.19.2.jar
@@ -102,8 +108,8 @@ file.reference.protobuf-java-util-3.7.0.jar=release\\modules\\ext\\protobuf-java
file.reference.Rejistry-1.1-SNAPSHOT.jar=release\\modules\\ext\\Rejistry-1.1-SNAPSHOT.jar
file.reference.sevenzipjbinding-AllPlatforms.jar=release\\modules\\ext\\sevenzipjbinding-AllPlatforms.jar
file.reference.sevenzipjbinding.jar=release\\modules\\ext\\sevenzipjbinding.jar
-file.reference.sleuthkit-4.10.0.jar=release\\modules\\ext\\sleuthkit-4.10.0.jar
-file.reference.sleuthkit-caseuco-4.10.0.jar=release\\modules\\ext\\sleuthkit-caseuco-4.10.0.jar
+file.reference.sleuthkit-4.10.0.jar=release/modules/ext/sleuthkit-4.10.0.jar
+file.reference.sleuthkit-caseuco-4.10.0.jar=release/modules/ext/sleuthkit-caseuco-4.10.0.jar
file.reference.slf4j-api-1.7.6.jar=release\\modules\\ext\\slf4j-api-1.7.6.jar
file.reference.slf4j-log4j12-1.7.6.jar=release\\modules\\ext\\slf4j-log4j12-1.7.6.jar
file.reference.SparseBitSet-1.1.jar=release\\modules\\ext\\SparseBitSet-1.1.jar
@@ -121,4 +127,3 @@ nbm.module.author=Brian Carrier
nbm.needs.restart=true
source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar
spec.version.base=10.21
-
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 19c863e6b7..ae69a4e5c6 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -399,6 +399,10 @@
ext/proto-google-cloud-translate-v3beta1-0.53.0.jar
release\modules\ext\proto-google-cloud-translate-v3beta1-0.53.0.jar
+
+ ext/byte-buddy-1.10.13.jar
+ release\modules\ext\byte-buddy-1.10.13.jar
+
ext/error_prone_annotations-2.3.2.jar
release\modules\ext\error_prone_annotations-2.3.2.jar
@@ -439,14 +443,6 @@
ext/jxmapviewer2-2.4.jar
release\modules\ext\jxmapviewer2-2.4.jar
-
- ext/jfreechart-1.0.19.jar
- release/modules/ext/jfreechart-1.0.19.jar
-
-
- ext/jcommon-1.0.23.jar
- release/modules/ext/jcommon-1.0.23.jar
-
ext/jdom-2.0.5-contrib.jar
release\modules\ext\jdom-2.0.5-contrib.jar
@@ -553,20 +549,12 @@
ext/sleuthkit-4.10.0.jar
- release\modules\ext\sleuthkit-4.10.0.jar
+ release/modules/ext/sleuthkit-4.10.0.jar
ext/animal-sniffer-annotations-1.17.jar
release\modules\ext\animal-sniffer-annotations-1.17.jar
-
- ext/sleuthkit-caseuco-4.10.0.jar
- release\modules\ext\sleuthkit-caseuco-4.10.0.jar
-
-
- ext/sleuthkit-4.10.0.jar
- release/modules/ext/sleuthkit-4.10.0.jar
-
ext/sleuthkit-caseuco-4.10.0.jar
release/modules/ext/sleuthkit-caseuco-4.10.0.jar
@@ -611,6 +599,10 @@
ext/decodetect-core-0.3.jar
release\modules\ext\decodetect-core-0.3.jar
+
+ ext/mockito-core-3.5.7.jar
+ release\modules\ext\mockito-core-3.5.7.jar
+
ext/httpclient-4.5.5.jar
release\modules\ext\httpclient-4.5.5.jar
@@ -623,6 +615,10 @@
ext/jackson-annotations-2.9.0.jar
release\modules\ext\jackson-annotations-2.9.0.jar
+
+ ext/objenesis-3.1.jar
+ release\modules\ext\objenesis-3.1.jar
+
ext/jackson-core-2.9.7.jar
release\modules\ext\jackson-core-2.9.7.jar
@@ -715,6 +711,10 @@
ext/netty-3.7.0.Final.jar
release\modules\ext\netty-3.7.0.Final.jar
+
+ ext/jfreechart-1.0.19.jar
+ release/modules/ext/jfreechart-1.0.19.jar
+
ext/opencensus-contrib-grpc-metrics-0.19.2.jar
release\modules\ext\opencensus-contrib-grpc-metrics-0.19.2.jar
@@ -743,6 +743,10 @@
ext/javax.ws.rs-api-2.0.jar
release\modules\ext\javax.ws.rs-api-2.0.jar
+
+ ext/jcommon-1.0.23.jar
+ release/modules/ext/jcommon-1.0.23.jar
+
ext/icepdf-core-6.2.2.jar
release\modules\ext\icepdf-core-6.2.2.jar
@@ -795,6 +799,10 @@
ext/jutf7-1.0.0.jar
release\modules\ext\jutf7-1.0.0.jar
+
+ ext/byte-buddy-agent-1.10.13.jar
+ release\modules\ext\byte-buddy-agent-1.10.13.jar
+
ext/batik-awt-util-1.6.jar
release\modules\ext\batik-awt-util-1.6.jar
diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED
index a3a13c0cff..507e079cad 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED
@@ -14,6 +14,7 @@ AddContentTagAction.taggingErr=Tagging Error
AddContentTagAction.unableToTag.msg=Unable to tag {0}, not a regular file.
# {0} - fileName
AddContentTagAction.unableToTag.msg2=Unable to tag {0}.
+CTL_DumpThreadAction=Thread Dump
CTL_ShowIngestProgressSnapshotAction=Ingest Status Details
DeleteBlackboardArtifactTagAction.deleteTag=Remove Selected Tag(s)
DeleteBlackboardArtifactTagAction.tagDelErr=Tag Deletion Error
diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
index fb178c797d..92b04140c3 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
@@ -44,7 +44,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
* This action should only be invoked in the event dispatch thread (EDT).
*/
@ActionRegistration(displayName = "#CTL_OpenLogFolder", iconInMenu = true)
-@ActionReference(path = "Menu/Help", position = 1750)
+@ActionReference(path = "Menu/Help", position = 2000)
@ActionID(id = "org.sleuthkit.autopsy.actions.OpenLogFolderAction", category = "Help")
public final class OpenLogFolderAction implements ActionListener {
diff --git a/Core/src/org/sleuthkit/autopsy/actions/ThreadDumpAction.java b/Core/src/org/sleuthkit/autopsy/actions/ThreadDumpAction.java
new file mode 100755
index 0000000000..d74511996c
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/actions/ThreadDumpAction.java
@@ -0,0 +1,156 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.actions;
+
+import java.awt.Desktop;
+import java.awt.event.ActionListener;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import javax.swing.SwingWorker;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionReference;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.actions.CallableSystemAction;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
+
+/**
+ * Action class for the Thread Dump help menu item. If there is no case open the
+ * dump file will be created in PlatformUtil.getLogDirectory() otherwise the
+ * file will be created in Case.getCurrentCase().getLogDirectoryPath()
+ */
+@ActionID(category = "Help", id = "org.sleuthkit.autopsy.actions.ThreadDumpAction")
+@ActionRegistration(displayName = "#CTL_DumpThreadAction", lazy = false)
+@ActionReference(path = "Menu/Help", position = 1750)
+@Messages({
+ "CTL_DumpThreadAction=Thread Dump"
+})
+public final class ThreadDumpAction extends CallableSystemAction implements ActionListener {
+
+ private static final long serialVersionUID = 1L;
+ private static final Logger logger = Logger.getLogger(ThreadDumpAction.class.getName());
+
+ private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS");
+
+ @Override
+ public void performAction() {
+ (new ThreadDumper()).run();
+ }
+
+ @Override
+ public String getName() {
+ return Bundle.CTL_DumpThreadAction();
+ }
+
+ @Override
+ public HelpCtx getHelpCtx() {
+ return HelpCtx.DEFAULT_HELP;
+ }
+
+ /**
+ * SwingWorker to that will create the thread dump file. Once the file is
+ * created it will be opened in an external viewer.
+ */
+ private final class ThreadDumper extends SwingWorker {
+
+ @Override
+ protected File doInBackground() throws Exception {
+ return createThreadDump();
+ }
+
+ @Override
+ protected void done() {
+ File dumpFile = null;
+ try {
+ dumpFile = get();
+ Desktop.getDesktop().open(dumpFile);
+ } catch (ExecutionException | InterruptedException ex) {
+ logger.log(Level.SEVERE, "Failure occurred while creating thread dump file", ex);
+ } catch (IOException ex) {
+ if (dumpFile != null) {
+ logger.log(Level.WARNING, "Failed to open thread dump file in external viewer: " + dumpFile.getAbsolutePath(), ex);
+ } else {
+ logger.log(Level.SEVERE, "Failed to create thread dump file.", ex);
+ }
+ }
+ }
+
+ /**
+ * Create the thread dump file.
+ *
+ * @throws IOException
+ */
+ private File createThreadDump() throws IOException {
+ File dumpFile = createFilePath().toFile();
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(dumpFile, true))) {
+ ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
+ ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100);
+ for (ThreadInfo threadInfo : threadInfos) {
+ writer.write(threadInfo.toString());
+ writer.write("\n");
+ }
+
+ long[] deadlockThreadIds = threadMXBean.findDeadlockedThreads();
+ if (deadlockThreadIds != null) {
+ writer.write("-------------------List of Deadlocked Thread IDs ---------------------");
+ String idsList = (Arrays
+ .stream(deadlockThreadIds)
+ .boxed()
+ .collect(Collectors.toList()))
+ .stream().map(n -> String.valueOf(n))
+ .collect(Collectors.joining("-", "{", "}"));
+ writer.write(idsList);
+ }
+ }
+
+ return dumpFile;
+ }
+
+ /**
+ * Create the dump file path.
+ *
+ * @return Path for dump file.
+ */
+ private Path createFilePath() {
+ String fileName = "ThreadDump_" + DATE_FORMAT.format(new Date()) + ".txt";
+ if (Case.isCaseOpen()) {
+ return Paths.get(Case.getCurrentCase().getLogDirectoryPath(), fileName);
+ }
+ return Paths.get(PlatformUtil.getLogDirectory(), fileName);
+ }
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 1e05403dd8..3ad14f6137 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -107,7 +107,7 @@ import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.events.AutopsyEventException;
import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
-import org.sleuthkit.autopsy.discovery.OpenDiscoveryAction;
+import org.sleuthkit.autopsy.discovery.ui.OpenDiscoveryAction;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestServices;
diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java
index 350e99c598..ad84bdcd0f 100755
--- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java
@@ -49,8 +49,7 @@ public class CommandLineOptionProcessor extends OptionProcessor {
private final Option dataSourceObjectIdOption = Option.requiredArgument('i', "dataSourceObjectId");
private final Option addDataSourceCommandOption = Option.withoutArgument('a', "addDataSource");
private final Option caseDirOption = Option.requiredArgument('d', "caseDir");
- private final Option runIngestCommandOption = Option.withoutArgument('r', "runIngest");
- private final Option ingestProfileOption = Option.requiredArgument('p', "ingestProfile");
+ private final Option runIngestCommandOption = Option.optionalArgument('r', "runIngest");
private final Option listAllDataSourcesCommandOption = Option.withoutArgument('l', "listAllDataSources");
private final Option generateReportsOption = Option.optionalArgument('g', "generateReports");
private final Option defaultArgument = Option.defaultArguments();
@@ -76,7 +75,6 @@ public class CommandLineOptionProcessor extends OptionProcessor {
set.add(dataSourceObjectIdOption);
set.add(caseDirOption);
set.add(runIngestCommandOption);
- set.add(ingestProfileOption);
set.add(listAllDataSourcesCommandOption);
set.add(generateReportsOption);
set.add(defaultArgument);
@@ -205,21 +203,6 @@ public class CommandLineOptionProcessor extends OptionProcessor {
}
}
- String ingestProfile = "";
- if (values.containsKey(ingestProfileOption)) {
-
- argDirs = values.get(ingestProfileOption);
- if (argDirs.length < 1) {
- handleError("Argument missing from 'ingestProfile' option");
- }
- ingestProfile = argDirs[0];
-
- // verify inputs
- if (ingestProfile == null || ingestProfile.isEmpty()) {
- handleError("Missing argument 'ingestProfile'");
- }
- }
-
// Create commands in order in which they should be executed:
// First create the "CREATE_CASE" command, if present
if (values.containsKey(createCaseCommandOption)) {
@@ -263,9 +246,15 @@ public class CommandLineOptionProcessor extends OptionProcessor {
runFromCommandLine = true;
}
+ String ingestProfile = "";
// Add RUN_INGEST command, if present
if (values.containsKey(runIngestCommandOption)) {
+ argDirs = values.get(runIngestCommandOption);
+ if(argDirs != null && argDirs.length > 0) {
+ ingestProfile = argDirs[0];
+ }
+
// 'caseDir' must only be specified if the case is not being created during the current run
if (!values.containsKey(createCaseCommandOption) && caseDir.isEmpty()) {
// new case is not being created during this run, so 'caseDir' should have been specified
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java
index db0710ee2d..00f0e7217a 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java
@@ -33,7 +33,7 @@ import org.openide.util.NbBundle;
*/
@ActionID(id = "org.sleuthkit.autopsy.corecomponents.AboutWindowAction", category = "Help")
@ActionRegistration(displayName = "#CTL_CustomAboutAction", iconInMenu = true, lazy = false)
-@ActionReference(path = "Menu/Help", position = 3000)
+@ActionReference(path = "Menu/Help", position = 3000, separatorBefore = 2999)
public class AboutWindowAction extends AboutAction {
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java
index 3e4b1ff1c0..88dc335b5d 100755
--- a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java
@@ -117,6 +117,8 @@ public final class IconsUtil {
imageFile = "devices.png"; //NON-NLS
} else if (typeID == ARTIFACT_TYPE.TSK_VERIFICATION_FAILED.getTypeID()) {
imageFile = "validationFailed.png"; //NON-NLS
+ } else if (typeID == ARTIFACT_TYPE.TSK_WEB_ACCOUNT_TYPE.getTypeID()) {
+ imageFile = "web-account-type.png"; //NON-NLS
} else {
imageFile = "artifact-icon.png"; //NON-NLS
}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/AnalysisSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/AnalysisSummary.java
index f3f436ae62..9d35e3d28d 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/AnalysisSummary.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/AnalysisSummary.java
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -146,6 +147,11 @@ public class AnalysisSummary implements DefaultArtifactUpdateGovernor {
*/
private List> getCountsData(DataSource dataSource, BlackboardAttribute.Type keyType, ARTIFACT_TYPE... artifactTypes)
throws SleuthkitCaseProviderException, TskCoreException {
+
+ if (dataSource == null) {
+ return Collections.emptyList();
+ }
+
List artifacts = new ArrayList<>();
SleuthkitCase skCase = provider.get();
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/Bundle.properties-MERGED
index 2e019a0248..8d62e88381 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/Bundle.properties-MERGED
@@ -1,2 +1,3 @@
DataSourceUserActivitySummary_getRecentAccounts_calllogMessage=Call Log
DataSourceUserActivitySummary_getRecentAccounts_emailMessage=Email Message
+IngestModuleCheckUtil_recentActivityModuleName=Recent Activity
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java
new file mode 100644
index 0000000000..86c731e78b
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java
@@ -0,0 +1,146 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.datamodel;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
+import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo;
+import org.sleuthkit.datamodel.IngestModuleInfo;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Utilities for checking if an ingest module has been run on a datasource.
+ */
+@Messages({
+ "IngestModuleCheckUtil_recentActivityModuleName=Recent Activity",
+
+})
+public class IngestModuleCheckUtil {
+ public static final String RECENT_ACTIVITY_FACTORY = "org.sleuthkit.autopsy.recentactivity.RecentActivityExtracterModuleFactory";
+ public static final String RECENT_ACTIVITY_MODULE_NAME = Bundle.IngestModuleCheckUtil_recentActivityModuleName();
+
+ // IngestModuleInfo separator for unique_name
+ private static final String UNIQUE_NAME_SEPARATOR = "-";
+
+ private final SleuthkitCaseProvider caseProvider;
+
+ /**
+ * Main constructor.
+ */
+ public IngestModuleCheckUtil() {
+ this(SleuthkitCaseProvider.DEFAULT);
+
+ }
+
+ /**
+ * Main constructor with external dependencies specified. This constructor
+ * is designed with unit testing in mind since mocked dependencies can be
+ * utilized.
+ *
+ * @param provider The object providing the current SleuthkitCase.
+ * @param logger The logger to use.
+ */
+ public IngestModuleCheckUtil(SleuthkitCaseProvider provider) {
+
+ this.caseProvider = provider;
+ }
+
+
+ /**
+ * Gets the fully qualified factory from the IngestModuleInfo.
+ * @param info The IngestJobInfo.
+ * @return The fully qualified factory.
+ */
+ private static String getFullyQualifiedFactory(IngestModuleInfo info) {
+ if (info == null) {
+ return null;
+ }
+
+ String qualifiedName = info.getUniqueName();
+ if (StringUtils.isBlank(qualifiedName)) {
+ return null;
+ }
+
+ return qualifiedName.split(UNIQUE_NAME_SEPARATOR)[0];
+ }
+
+
+ /**
+ * Whether or not the ingest job info contains the ingest modulename.
+ * @param info The IngestJobInfo.
+ * @param fullyQualifiedFactory The fully qualified classname of the relevant factory.
+ * @return True if the ingest module name is contained in the data.
+ */
+ private static boolean hasIngestModule(IngestJobInfo info, String fullyQualifiedFactory) {
+ if (info == null || info.getIngestModuleInfo() == null || StringUtils.isBlank(fullyQualifiedFactory)) {
+ return false;
+ }
+
+ return info.getIngestModuleInfo().stream()
+ .anyMatch((moduleInfo) -> {
+ String thisQualifiedFactory = getFullyQualifiedFactory(moduleInfo);
+ return fullyQualifiedFactory.equalsIgnoreCase(thisQualifiedFactory);
+ });
+ }
+
+ /**
+ * Whether or not a data source has been ingested with a particular ingest module.
+ * @param dataSource The datasource.
+ * @param fullyQualifiedFactory The fully qualified classname of the relevant factory.
+ * @return Whether or not a data source has been ingested with a particular ingest module.
+ * @throws TskCoreException
+ * @throws SleuthkitCaseProviderException
+ */
+ public boolean isModuleIngested(DataSource dataSource, String fullyQualifiedFactory)
+ throws TskCoreException, SleuthkitCaseProviderException {
+ if (dataSource == null) {
+ return false;
+ }
+
+ long dataSourceId = dataSource.getId();
+
+ return caseProvider.get().getIngestJobs().stream()
+ .anyMatch((ingestJob) -> {
+ return ingestJob != null
+ && ingestJob.getObjectId() == dataSourceId
+ && hasIngestModule(ingestJob, fullyQualifiedFactory);
+ });
+
+ }
+
+ /**
+ * Get a mapping of fully qualified factory name to display name.
+ * @param skCase The SleuthkitCase.
+ * @return The mapping of fully qualified factory name to display name.
+ * @throws TskCoreException
+ */
+ public static Map getFactoryDisplayNames(SleuthkitCase skCase) throws TskCoreException {
+ return skCase.getIngestJobs().stream()
+ .flatMap(ingestJob -> ingestJob.getIngestModuleInfo().stream())
+ .collect(Collectors.toMap(
+ (moduleInfo) -> getFullyQualifiedFactory(moduleInfo),
+ (moduleInfo) -> moduleInfo.getDisplayName(),
+ (a,b) -> a));
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/PastCasesSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/PastCasesSummary.java
index 2bb1e9c37a..a304d367ff 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/PastCasesSummary.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/PastCasesSummary.java
@@ -28,7 +28,6 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
@@ -38,7 +37,6 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.DataSource;
-import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@@ -66,47 +64,6 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
public class PastCasesSummary implements DefaultArtifactUpdateGovernor {
- /**
- * Exception that is thrown in the event that a data source has not been
- * ingested with a particular ingest module.
- */
- public static class NotIngestedWithModuleException extends Exception {
-
- private static final long serialVersionUID = 1L;
-
- private final String moduleDisplayName;
-
- /**
- * Constructor.
- *
- * @param moduleName The module name.
- * @param message The message for the exception.
- */
- public NotIngestedWithModuleException(String moduleName, String message) {
- super(message);
- this.moduleDisplayName = moduleName;
- }
-
- /**
- * Constructor.
- *
- * @param moduleName The module name.
- * @param message The message for the exception.
- * @param thrwbl Inner exception if applicable.
- */
- public NotIngestedWithModuleException(String moduleName, String message, Throwable thrwbl) {
- super(message, thrwbl);
- this.moduleDisplayName = moduleName;
- }
-
- /**
- * @return The module display name.
- */
- public String getModuleDisplayName() {
- return moduleDisplayName;
- }
- }
-
/**
* Return type for results items in the past cases tab.
*/
@@ -335,16 +292,17 @@ public class PastCasesSummary implements DefaultArtifactUpdateGovernor {
*
* @param dataSource The data source.
*
- * @return The retrieved data.
+ * @return The retrieved data or null if null dataSource.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
- * @throws NotIngestedWithModuleException
*/
public PastCasesResult getPastCasesData(DataSource dataSource)
- throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException {
+ throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException {
- throwOnNotCentralRepoIngested(dataSource);
+ if (dataSource == null) {
+ return null;
+ }
SleuthkitCase skCase = caseProvider.get();
@@ -372,75 +330,4 @@ public class PastCasesSummary implements DefaultArtifactUpdateGovernor {
getCaseCounts(Stream.concat(filesCases, nonDeviceArtifactCases.stream()))
);
}
-
- /**
- * Returns true if the ingest job info contains an ingest module that
- * matches the Central Repo Module ingest display name.
- *
- * @param info The info.
- *
- * @return True if there is a central repo ingest match.
- */
- private boolean hasCentralRepoIngest(IngestJobInfo info) {
- if (info == null || info.getIngestModuleInfo() == null) {
- return false;
- }
-
- return info.getIngestModuleInfo().stream()
- .anyMatch((moduleInfo) -> {
- return StringUtils.isNotBlank(moduleInfo.getDisplayName())
- && moduleInfo.getDisplayName().trim().equalsIgnoreCase(CENTRAL_REPO_INGEST_NAME);
- });
- }
-
- /**
- * Returns true if the central repository ingest module has been run on the
- * datasource.
- *
- * @param dataSource The data source.
- *
- * @return True if there is an ingest job pertaining to the data source
- * where an ingest module matches the central repo ingest module
- * display name.
- *
- * @throws SleuthkitCaseProviderException
- * @throws TskCoreException
- */
- public boolean isCentralRepoIngested(DataSource dataSource)
- throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException {
- if (dataSource == null) {
- return false;
- }
-
- long dataSourceId = dataSource.getId();
-
- return this.caseProvider.get().getIngestJobs().stream()
- .anyMatch((ingestJob) -> {
- return ingestJob != null
- && ingestJob.getObjectId() == dataSourceId
- && hasCentralRepoIngest(ingestJob);
- });
-
- }
-
- /**
- * Throws an exception if the current data source has not been ingested with
- * the Central Repository Ingest Module.
- *
- * @param dataSource The data source to check if it has been ingested with
- * the Central Repository Ingest Module.
- *
- * @throws SleuthkitCaseProviderException
- * @throws TskCoreException
- * @throws NotIngestedWithModuleException
- */
- private void throwOnNotCentralRepoIngested(DataSource dataSource)
- throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException {
-
- if (!isCentralRepoIngested(dataSource)) {
- String objectId = (dataSource == null) ? "" : String.valueOf(dataSource.getId());
- String message = String.format("Data source: %s has not been ingested with the Central Repository Ingest Module.", objectId);
- throw new NotIngestedWithModuleException(CENTRAL_REPO_INGEST_NAME, message);
- }
- }
}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java
index 4dadb8e028..0a47e0ea6c 100755
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java
@@ -24,6 +24,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -108,7 +109,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
*/
public List getRecentlyOpenedDocuments(DataSource dataSource, int maxCount) throws SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
- throw new IllegalArgumentException("Failed to get recently opened documents given data source was null");
+ return Collections.emptyList();
}
List artifactList
@@ -159,6 +160,10 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
* @throws SleuthkitCaseProviderException
*/
public List getRecentDownloads(DataSource dataSource, int maxCount) throws TskCoreException, SleuthkitCaseProviderException {
+ if (dataSource == null) {
+ return Collections.emptyList();
+ }
+
List artifactList
= DataSourceInfoUtilities.getArtifacts(provider.get(),
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD),
@@ -206,6 +211,10 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
* @throws TskCoreException
*/
public List getRecentAttachments(DataSource dataSource, int maxCount) throws SleuthkitCaseProviderException, TskCoreException {
+ if (dataSource == null) {
+ return Collections.emptyList();
+ }
+
return createListFromMap(buildAttachmentMap(dataSource), maxCount);
}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java
index 0f048a6ed0..2652a450ec 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java
@@ -149,6 +149,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List getRecentDomains(DataSource dataSource, int count) throws TskCoreException, SleuthkitCaseProviderException {
assertValidCount(count);
+ if (dataSource == null) {
+ return Collections.emptyList();
+ }
+
Pair>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
// if no recent domains, return accordingly
if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) {
@@ -307,6 +311,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List getMostRecentWebSearches(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
+ if (dataSource == null) {
+ return Collections.emptyList();
+ }
+
// get the artifacts
List webSearchArtifacts = caseProvider.get().getBlackboard()
.getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId());
@@ -391,6 +399,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List getRecentDevices(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
+ if (dataSource == null) {
+ return Collections.emptyList();
+ }
+
return DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED,
dataSource, TYPE_DATETIME, DataSourceInfoUtilities.SortOrder.DESCENDING, 0)
.stream()
@@ -476,6 +488,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List getRecentAccounts(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
+ if (dataSource == null) {
+ return Collections.emptyList();
+ }
+
Stream messageResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), dataSource.getId())
.stream()
.map((art) -> getMessageAccountResult(art));
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.form
index 4016b539a7..402b709b38 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.form
@@ -57,6 +57,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java
index 3cb5a9b10d..34dfa97a13 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java
@@ -26,8 +26,11 @@ import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.AnalysisSummary;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
+import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory;
+import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory;
import org.sleuthkit.datamodel.DataSource;
/**
@@ -36,12 +39,22 @@ import org.sleuthkit.datamodel.DataSource;
*/
@Messages({
"AnalysisPanel_keyColumn_title=Name",
- "AnalysisPanel_countColumn_title=Count"
+ "AnalysisPanel_countColumn_title=Count",
+ "AnalysisPanel_keywordSearchModuleName=Keyword Search"
})
public class AnalysisPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
+ private static final String KEYWORD_SEARCH_MODULE_NAME = Bundle.AnalysisPanel_keywordSearchModuleName();
+ private static final String KEYWORD_SEARCH_FACTORY = "org.sleuthkit.autopsy.keywordsearch.KeywordSearchModuleFactory";
+
+ private static final String INTERESTING_ITEM_MODULE_NAME = new InterestingItemsIngestModuleFactory().getModuleDisplayName();
+ private static final String INTERESTING_ITEM_FACTORY = InterestingItemsIngestModuleFactory.class.getCanonicalName();
+
+ private static final String HASHSET_MODULE_NAME = HashLookupModuleFactory.getModuleName();
+ private static final String HASHSET_FACTORY = HashLookupModuleFactory.class.getCanonicalName();
+
/**
* Default Column definitions for each table
*/
@@ -77,6 +90,9 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
keywordHitsTable,
interestingItemsTable
);
+
+ private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
+
/**
* All of the components necessary for data fetch swing workers to load data
@@ -99,20 +115,28 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
// hashset hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getHashsetCounts(dataSource),
- (result) -> hashsetHitsTable.showDataFetchResult(result)),
+ (result) -> showResultWithModuleCheck(hashsetHitsTable, result, HASHSET_FACTORY, HASHSET_MODULE_NAME)),
// keyword hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getKeywordCounts(dataSource),
- (result) -> keywordHitsTable.showDataFetchResult(result)),
+ (result) -> showResultWithModuleCheck(keywordHitsTable, result, KEYWORD_SEARCH_FACTORY, KEYWORD_SEARCH_MODULE_NAME)),
// interesting item hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getInterestingItemCounts(dataSource),
- (result) -> interestingItemsTable.showDataFetchResult(result))
+ (result) -> showResultWithModuleCheck(interestingItemsTable, result, INTERESTING_ITEM_FACTORY, INTERESTING_ITEM_MODULE_NAME))
);
initComponents();
}
+
+ @Override
+ public void close() {
+ ingestRunningLabel.unregister();
+ super.close();
+ }
+
+
@Override
protected void fetchInformation(DataSource dataSource) {
fetchInformation(dataFetchComponents, dataSource);
@@ -134,6 +158,7 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel mainContentPanel = new javax.swing.JPanel();
+ javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JLabel hashsetHitsLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
javax.swing.JPanel hashSetHitsPanel = hashsetHitsTable;
@@ -152,6 +177,12 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
mainContentPanel.setMinimumSize(new java.awt.Dimension(200, 452));
mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS));
+ ingestRunningPanel.setAlignmentX(0.0F);
+ ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
+ ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
+ ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
+ mainContentPanel.add(ingestRunningPanel);
+
org.openide.awt.Mnemonics.setLocalizedText(hashsetHitsLabel, org.openide.util.NbBundle.getMessage(AnalysisPanel.class, "AnalysisPanel.hashsetHitsLabel.text")); // NOI18N
mainContentPanel.add(hashsetHitsLabel);
mainContentPanel.add(filler1);
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java
index 6b548e2b6e..77b4a4c688 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java
@@ -23,13 +23,18 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
+import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler;
@@ -55,6 +60,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
private static final Logger logger = Logger.getLogger(BaseDataSourceSummaryPanel.class.getName());
private final SwingWorkerSequentialExecutor executor = new SwingWorkerSequentialExecutor();
+ private final IngestModuleCheckUtil ingestModuleCheck = new IngestModuleCheckUtil();
private final EventUpdateHandler updateHandler;
private final List governors;
@@ -284,15 +290,20 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* @param dataSource The data source argument.
*/
protected void fetchInformation(List> dataFetchComponents, DataSource dataSource) {
- // create swing workers to run for each loadable item
- List> workers = dataFetchComponents
- .stream()
- .map((components) -> new DataFetchWorker<>(components, dataSource))
- .collect(Collectors.toList());
+ if (dataSource == null || !Case.isCaseOpen()) {
+ dataFetchComponents.forEach((item) -> item.getResultHandler()
+ .accept(DataFetchResult.getSuccessResult(null)));
+ } else {
+ // create swing workers to run for each loadable item
+ List> workers = dataFetchComponents
+ .stream()
+ .map((components) -> new DataFetchWorker<>(components, dataSource))
+ .collect(Collectors.toList());
- // submit swing workers to run
- if (!workers.isEmpty()) {
- submit(workers);
+ // submit swing workers to run
+ if (!workers.isEmpty()) {
+ submit(workers);
+ }
}
}
@@ -329,4 +340,78 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
fetchInformation(dataSource);
}
}
+
+ /**
+ * Get default message when there is a NotIngestedWithModuleException.
+ *
+ * @param exception The moduleName.
+ *
+ * @return Message specifying that the ingest module was not run.
+ */
+ @Messages({
+ "# {0} - module name",
+ "BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source."
+ })
+ protected String getDefaultNoIngestMessage(String moduleName) {
+ return Bundle.BaseDataSourceSummaryPanel_defaultNotIngestMessage(moduleName);
+ }
+
+ /**
+ * Utility method to return the IngestModuleCheckUtil.
+ *
+ * @return The IngestModuleCheckUtil.
+ */
+ protected IngestModuleCheckUtil getIngestModuleCheckUtil() {
+ return this.ingestModuleCheck;
+ }
+
+ /**
+ * Utility method that in the event of a) there are no results and b) a
+ * relevant ingest module has not been run on this datasource, then a
+ * message indicating the unrun ingest module will be shown. Otherwise, the
+ * default LoadableComponent.showDataFetchResult behavior will be used.
+ *
+ * @param component The component.
+ * @param result The data result.
+ * @param factoryClass The fully qualified class name of the relevant
+ * factory.
+ * @param moduleName The name of the ingest module (i.e. 'Keyword
+ * Search').
+ */
+ protected void showResultWithModuleCheck(LoadableComponent> component, DataFetchResult> result, String factoryClass, String moduleName) {
+ Predicate> hasResults = (lst) -> lst != null && !lst.isEmpty();
+ showResultWithModuleCheck(component, result, hasResults, factoryClass, moduleName);
+ }
+
+ /**
+ * Utility method that in the event of a) there are no results and b) a
+ * relevant ingest module has not been run on this datasource, then a
+ * message indicating the unrun ingest module will be shown. Otherwise, the
+ * default LoadableComponent.showDataFetchResult behavior will be used.
+ *
+ * @param component The component.
+ * @param result The data result.
+ * @param hasResults Given the data type, will provide whether or not the
+ * data contains any actual results.
+ * @param factoryClass The fully qualified class name of the relevant
+ * factory.
+ * @param moduleName The name of the ingest module (i.e. 'Keyword
+ * Search').
+ */
+ protected void showResultWithModuleCheck(LoadableComponent component, DataFetchResult result,
+ Predicate hasResults, String factoryClass, String moduleName) {
+
+ if (result != null && result.getResultType() == ResultType.SUCCESS && !hasResults.test(result.getData())) {
+ try {
+ if (!ingestModuleCheck.isModuleIngested(getDataSource(), factoryClass)) {
+ component.showMessage(getDefaultNoIngestMessage(moduleName));
+ return;
+ }
+ } catch (TskCoreException | SleuthkitCaseProviderException ex) {
+ logger.log(Level.WARNING, "There was an error while checking for ingest modules for datasource.", ex);
+ }
+ }
+
+ component.showDataFetchResult(result);
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
index c994603559..9a48089e5d 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
@@ -1,7 +1,8 @@
AnalysisPanel_countColumn_title=Count
AnalysisPanel_keyColumn_title=Name
-ContainerPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.
-ContainerPanel.getDataSources.error.title=Load Failure
+AnalysisPanel_keywordSearchModuleName=Keyword Search
+# {0} - module name
+BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source.
CTL_DataSourceSummaryAction=Data Source Summary
DataSourceSummaryDialog.closeButton.text=Close
ContainerPanel.displayNameLabel.text=Display Name:
@@ -54,6 +55,7 @@ PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central
RecentFilePanel_col_header_domain=Domain
RecentFilePanel_col_header_path=Path
RecentFilePanel_col_header_sender=Sender
+RecentFilePanel_emailParserModuleName=Email Parser
RecentFilePanel_no_open_documents=No recently open documents found.
RecentFilesPanel_col_head_date=Date
SizeRepresentationUtil_units_bytes=\ bytes
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java
index d350ee0fa7..8f11e0dcd3 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java
@@ -26,7 +26,6 @@ import java.util.Set;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.table.DefaultTableModel;
-import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
@@ -49,23 +48,16 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
private final DataSource dataSource;
private final Long unallocatedFilesSize;
- private final String operatingSystem;
- private final String dataSourceType;
/**
* Main constructor.
*
* @param dataSource The original datasource.
* @param unallocatedFilesSize The unallocated file size.
- * @param operatingSystem The string representing the operating
- * system.
- * @param dataSourceType The datasource type as a string.
*/
- ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize, String operatingSystem, String dataSourceType) {
+ ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize) {
this.dataSource = dataSource;
this.unallocatedFilesSize = unallocatedFilesSize;
- this.operatingSystem = operatingSystem;
- this.dataSourceType = dataSourceType;
}
/**
@@ -81,21 +73,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
Long getUnallocatedFilesSize() {
return unallocatedFilesSize;
}
-
- /**
- * @return The string representing the operating system.
- */
- String getOperatingSystem() {
- return operatingSystem;
- }
-
- /**
- * @return The datasource type as a string.
- */
- String getDataSourceType() {
- return dataSourceType;
- }
-
}
// set of case events for which to call update (if the name changes, that will impact data shown)
@@ -134,8 +111,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
/**
* Creates new form ContainerPanel.
*/
- @Messages({"ContainerPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.",
- "ContainerPanel.getDataSources.error.title=Load Failure"})
ContainerPanel(ContainerSummary containerSummary) {
super(containerSummary, CONTAINER_UPDATES);
@@ -144,20 +119,24 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
(dataSource) -> {
return new ContainerPanelData(
dataSource,
- containerSummary.getSizeOfUnallocatedFiles(dataSource),
- containerSummary.getOperatingSystems(dataSource),
- containerSummary.getDataSourceType(dataSource)
+ containerSummary.getSizeOfUnallocatedFiles(dataSource)
);
},
(result) -> {
- if (result.getResultType() == ResultType.SUCCESS) {
+ if (result != null && result.getResultType() == ResultType.SUCCESS) {
ContainerPanelData data = result.getData();
- updateDetailsPanelData(
- data.getDataSource(),
- data.getUnallocatedFilesSize());
+ DataSource dataSource = (data == null) ? null : data.getDataSource();
+ Long unallocatedFileSize = (data == null) ? null : data.getUnallocatedFilesSize();
+
+ updateDetailsPanelData(dataSource, unallocatedFileSize);
} else {
- logger.log(Level.WARNING, "An exception occurred while attempting to fetch data for the ContainerPanel.",
- result.getException());
+ if (result == null) {
+ logger.log(Level.WARNING, "No data fetch result was provided to the ContainerPanel.");
+ } else {
+ logger.log(Level.WARNING, "An exception occurred while attempting to fetch data for the ContainerPanel.",
+ result.getException());
+ }
+
updateDetailsPanelData(null, null);
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.form
index 13ddb081bd..7c84d44660 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.form
@@ -51,6 +51,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -70,7 +91,7 @@
-
+
@@ -78,6 +99,7 @@
+
@@ -109,7 +131,7 @@
-
+
@@ -117,6 +139,7 @@
+
@@ -138,7 +161,7 @@
-
+
@@ -146,6 +169,7 @@
+
@@ -179,6 +203,7 @@
+
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java
index a530757276..76d36f4785 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java
@@ -21,18 +21,17 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
-import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
-import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary;
-import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.NotIngestedWithModuleException;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
@@ -45,11 +44,12 @@ import org.sleuthkit.datamodel.DataSource;
"PastCasesPanel_caseColumn_title=Case",
"PastCasesPanel_countColumn_title=Count",
"PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run."
-
})
public class PastCasesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
+ private static final String CR_FACTORY = CentralRepoIngestModuleFactory.class.getName();
+ private static final String CR_NAME = CentralRepoIngestModuleFactory.getModuleName();
private static final ColumnModel> CASE_COL = new ColumnModel<>(
Bundle.PastCasesPanel_caseColumn_title(),
@@ -76,6 +76,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
private final List> dataFetchComponents;
+ private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
+
public PastCasesPanel() {
this(new PastCasesSummary());
}
@@ -95,21 +97,14 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
}
/**
- * handles displaying the result for the table. If a
- * NotCentralRepoIngestedException is thrown, then an appropriate message is
- * shown. Otherwise, this method uses the tables default showDataFetchResult
- * method.
+ * Handles displaying the result for each table by breaking apart subdata
+ * items into seperate results for each table.
*
* @param result The result.
*/
private void handleResult(DataFetchResult result) {
- if (result.getResultType() == ResultType.ERROR && result.getException() instanceof NotIngestedWithModuleException) {
- notableFileTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message());
- sameIdTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message());
- } else {
- notableFileTable.showDataFetchResult(getSubResult(result, (res) -> (res == null) ? null : res.getTaggedNotable()));
- sameIdTable.showDataFetchResult(getSubResult(result, (res) -> (res == null) ? null : res.getSameIdsResults()));
- }
+ showResultWithModuleCheck(notableFileTable, getSubResult(result, (res) -> res.getTaggedNotable()), CR_FACTORY, CR_NAME);
+ showResultWithModuleCheck(sameIdTable, getSubResult(result, (res) -> res.getSameIdsResults()), CR_FACTORY, CR_NAME);
}
/**
@@ -124,9 +119,12 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
* @return The new result with the error of the original or the processed
* data.
*/
- private DataFetchResult getSubResult(DataFetchResult inputResult, Function getSubResult) {
- if (inputResult.getResultType() == ResultType.SUCCESS) {
- return DataFetchResult.getSuccessResult(getSubResult.apply(inputResult.getData()));
+ private DataFetchResult getSubResult(DataFetchResult inputResult, Function getSubResult) {
+ if (inputResult == null) {
+ return null;
+ } else if (inputResult.getResultType() == ResultType.SUCCESS) {
+ O innerData = (inputResult.getData() == null) ? null : getSubResult.apply(inputResult.getData());
+ return DataFetchResult.getSuccessResult(innerData);
} else {
return DataFetchResult.getErrorResult(inputResult.getException());
}
@@ -142,6 +140,12 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tables, dataSource);
}
+ @Override
+ public void close() {
+ ingestRunningLabel.unregister();
+ super.close();
+ }
+
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@@ -153,22 +157,30 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel mainContentPanel = new javax.swing.JPanel();
+ javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JLabel notableFileLabel = new javax.swing.JLabel();
- javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
+ javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel notableFilePanel = notableFileTable;
- javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(32767, 20));
+ javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel sameIdLabel = new javax.swing.JLabel();
- javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
+ javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel sameIdPanel = sameIdTable;
javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS));
+ ingestRunningPanel.setAlignmentX(0.0F);
+ ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
+ ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
+ ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
+ mainContentPanel.add(ingestRunningPanel);
+
org.openide.awt.Mnemonics.setLocalizedText(notableFileLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N
mainContentPanel.add(notableFileLabel);
notableFileLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N
+ filler1.setAlignmentX(0.0F);
mainContentPanel.add(filler1);
notableFilePanel.setAlignmentX(0.0F);
@@ -176,10 +188,14 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
notableFilePanel.setMinimumSize(new java.awt.Dimension(100, 106));
notableFilePanel.setPreferredSize(new java.awt.Dimension(100, 106));
mainContentPanel.add(notableFilePanel);
+
+ filler2.setAlignmentX(0.0F);
mainContentPanel.add(filler2);
org.openide.awt.Mnemonics.setLocalizedText(sameIdLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.sameIdLabel.text")); // NOI18N
mainContentPanel.add(sameIdLabel);
+
+ filler3.setAlignmentX(0.0F);
mainContentPanel.add(filler3);
sameIdPanel.setAlignmentX(0.0F);
@@ -187,6 +203,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
sameIdPanel.setMinimumSize(new java.awt.Dimension(100, 106));
sameIdPanel.setPreferredSize(new java.awt.Dimension(100, 106));
mainContentPanel.add(sameIdPanel);
+
+ filler5.setAlignmentX(0.0F);
mainContentPanel.add(filler5);
mainScrollPane.setViewportView(mainContentPanel);
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.form
index 4d1cdccecd..150abf2ed0 100755
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.form
@@ -31,6 +31,11 @@
+
+
+
+
+
@@ -45,13 +50,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -63,7 +94,7 @@
-
+
@@ -75,7 +106,7 @@
-
+
@@ -93,7 +124,7 @@
-
+
@@ -109,7 +140,7 @@
-
+
@@ -125,7 +156,7 @@
-
+
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java
index 11e083c9ae..1b675032d0 100755
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java
@@ -22,12 +22,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ListTableModel;
@@ -39,17 +41,22 @@ import org.sleuthkit.datamodel.DataSource;
public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
+ private static final String EMAIL_PARSER_FACTORY = "org.sleuthkit.autopsy.thunderbirdparser.EmailParserModuleFactory";
+ private static final String EMAIL_PARSER_MODULE_NAME = Bundle.RecentFilePanel_emailParserModuleName();
private final List> tablePanelList = new ArrayList<>();
private final List> dataFetchComponents = new ArrayList<>();
+ private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
+
private final RecentFilesSummary dataHandler;
@Messages({
"RecentFilesPanel_col_head_date=Date",
"RecentFilePanel_col_header_domain=Domain",
"RecentFilePanel_col_header_path=Path",
- "RecentFilePanel_col_header_sender=Sender"
+ "RecentFilePanel_col_header_sender=Sender",
+ "RecentFilePanel_emailParserModuleName=Email Parser"
})
/**
@@ -80,6 +87,12 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tablePanelList, dataSource);
}
+ @Override
+ public void close() {
+ ingestRunningLabel.unregister();
+ super.close();
+ }
+
/**
* Setup the data model and columns for the panel tables.
*/
@@ -118,8 +131,11 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
DataFetchWorker.DataFetchComponents> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentlyOpenedDocuments(dataSource, 10),
- (result) -> pane.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.RecentFilePanel_no_open_documents()));
+ (result) -> {
+ showResultWithModuleCheck(pane, result,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ });
dataFetchComponents.add(worker);
}
@@ -154,8 +170,11 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
DataFetchWorker.DataFetchComponents> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentDownloads(dataSource, 10),
- (result) -> pane.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.RecentFilePanel_no_open_documents()));
+ (result) -> {
+ showResultWithModuleCheck(pane, result,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ });
dataFetchComponents.add(worker);
}
@@ -190,8 +209,8 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
DataFetchWorker.DataFetchComponents> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentAttachments(dataSource, 10),
- (result) -> pane.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.RecentFilePanel_no_open_documents()));
+ (result) -> showResultWithModuleCheck(pane, result, EMAIL_PARSER_FACTORY, EMAIL_PARSER_MODULE_NAME)
+ );
dataFetchComponents.add(worker);
}
@@ -208,6 +227,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel tablePanel = new javax.swing.JPanel();
+ javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
openedDocPane = new JTablePanel();
downloadsPane = new JTablePanel();
attachmentsPane = new JTablePanel();
@@ -217,61 +237,72 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
setLayout(new java.awt.BorderLayout());
+ tablePanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
tablePanel.setMinimumSize(new java.awt.Dimension(400, 400));
tablePanel.setPreferredSize(new java.awt.Dimension(600, 400));
tablePanel.setLayout(new java.awt.GridBagLayout());
+
+ ingestRunningPanel.setAlignmentX(0.0F);
+ ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
+ ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
+ ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ tablePanel.add(ingestRunningPanel, gridBagConstraints);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 5);
+ gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
tablePanel.add(openedDocPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 3;
+ gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 5);
+ gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
tablePanel.add(downloadsPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 5;
+ gridBagConstraints.gridy = 6;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(5, 5, 10, 5);
+ gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
tablePanel.add(attachmentsPane, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(openDocsLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.openDocsLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
- gridBagConstraints.insets = new java.awt.Insets(10, 5, 0, 5);
tablePanel.add(openDocsLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(downloadLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.downloadLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
- gridBagConstraints.insets = new java.awt.Insets(15, 5, 0, 5);
+ gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(downloadLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(attachmentLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.attachmentLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 4;
+ gridBagConstraints.gridy = 5;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
- gridBagConstraints.insets = new java.awt.Insets(15, 5, 0, 5);
+ gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(attachmentLabel, gridBagConstraints);
scrollPane.setViewportView(tablePanel);
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.form
index 2efe151eb5..efb1ca2aae 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.form
@@ -52,6 +52,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
index e082543dee..4d9271d712 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
@@ -26,22 +26,29 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JLabel;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TypesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.MimeTypeSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.AbstractLoadableComponent;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel.PieChartItem;
+import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
@@ -93,27 +100,64 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
this.showResults(null);
}
- private void setValue(String value, boolean italicize) {
+ private void setValue(String value) {
String formattedKey = StringUtils.isBlank(key) ? "" : key;
String formattedValue = StringUtils.isBlank(value) ? "" : value;
- String htmlFormattedValue = (italicize) ? String.format("%s", formattedValue) : formattedValue;
- label.setText(String.format("%s: %s
", formattedKey, htmlFormattedValue));
+ label.setText(String.format("%s: %s", formattedKey, formattedValue));
}
@Override
protected void setMessage(boolean visible, String message) {
- setValue(message, true);
+ setValue(message);
}
@Override
protected void setResults(String data) {
- setValue(data, false);
+ setValue(data);
+ }
+ }
+
+ /**
+ * Data for types pie chart.
+ */
+ private static class TypesPieChartData {
+
+ private final List pieSlices;
+ private final boolean usefulContent;
+
+ /**
+ * Main constructor.
+ *
+ * @param pieSlices The pie slices.
+ * @param usefulContent True if this is useful content; false if there
+ * is 0 mime type information.
+ */
+ public TypesPieChartData(List pieSlices, boolean usefulContent) {
+ this.pieSlices = pieSlices;
+ this.usefulContent = usefulContent;
+ }
+
+ /**
+ * @return The pie chart data.
+ */
+ public List getPieSlices() {
+ return pieSlices;
+ }
+
+ /**
+ * @return Whether or not the data is usefulContent.
+ */
+ public boolean isUsefulContent() {
+ return usefulContent;
}
}
private static final long serialVersionUID = 1L;
private static final DecimalFormat INTEGER_SIZE_FORMAT = new DecimalFormat("#");
private static final DecimalFormat COMMA_FORMATTER = new DecimalFormat("#,###");
+ private static final String FILE_TYPE_FACTORY = FileTypeIdModuleFactory.class.getCanonicalName();
+ private static final String FILE_TYPE_MODULE_NAME = FileTypeIdModuleFactory.getModuleName();
+ private static final Logger logger = Logger.getLogger(TypesPanel.class.getName());
// All file type categories.
private static final List>> FILE_MIME_TYPE_CATEGORIES = Arrays.asList(
@@ -148,6 +192,8 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
directoriesLabel
);
+ private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
+
// all of the means for obtaining data for the gui components.
private final List> dataFetchComponents;
@@ -158,6 +204,12 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
this(new MimeTypeSummary(), new TypesSummary(), new ContainerSummary());
}
+ @Override
+ public void close() {
+ ingestRunningLabel.unregister();
+ super.close();
+ }
+
/**
* Creates a new TypesPanel.
*
@@ -176,11 +228,25 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
// usage label worker
new DataFetchWorker.DataFetchComponents<>(
containerData::getDataSourceType,
- usageLabel::showDataFetchResult),
+ (result) -> {
+ showResultWithModuleCheck(
+ usageLabel,
+ result,
+ StringUtils::isNotBlank,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ }),
// os label worker
new DataFetchWorker.DataFetchComponents<>(
containerData::getOperatingSystems,
- osLabel::showDataFetchResult),
+ (result) -> {
+ showResultWithModuleCheck(
+ osLabel,
+ result,
+ StringUtils::isNotBlank,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ }),
// size label worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> {
@@ -191,7 +257,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
// file types worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getMimeTypeCategoriesModel(mimeTypeData, dataSource),
- fileMimeTypesChart::showDataFetchResult),
+ this::showMimeTypeCategories),
// allocated files worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getStringOrZero(typeData.getCountOfAllocatedFiles(dataSource)),
@@ -230,7 +296,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
*
* @return The pie chart items.
*/
- private List getMimeTypeCategoriesModel(MimeTypeSummary mimeTypeData, DataSource dataSource)
+ private TypesPieChartData getMimeTypeCategoriesModel(MimeTypeSummary mimeTypeData, DataSource dataSource)
throws SQLException, SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
@@ -259,15 +325,76 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
fileCategoryItems.add(Pair.of(Bundle.TypesPanel_fileMimeTypesChart_other_title(),
allRegularFiles - (categoryTotalCount + noMimeTypeCount)));
+ // check at this point to see if these are all 0; if so, we don't have useful content.
+ boolean usefulContent = fileCategoryItems.stream().anyMatch((pair) -> pair.getValue() != null && pair.getValue() > 0);
+
// create entry for not analyzed mime types category
fileCategoryItems.add(Pair.of(Bundle.TypesPanel_fileMimeTypesChart_notAnalyzed_title(),
noMimeTypeCount));
// create pie chart items to provide to pie chart
- return fileCategoryItems.stream()
+ List items = fileCategoryItems.stream()
.filter(keyCount -> keyCount.getRight() != null && keyCount.getRight() > 0)
.map(keyCount -> new PieChartItem(keyCount.getLeft(), keyCount.getRight()))
.collect(Collectors.toList());
+
+ return new TypesPieChartData(items, usefulContent);
+ }
+
+ /**
+ * Handles properly showing data for the mime type categories pie chart
+ * accounting for whether there are any files with mime types specified and
+ * whether or not the current data source has been ingested with the file
+ * type ingest module.
+ *
+ * @param result The result to be shown.
+ */
+ private void showMimeTypeCategories(DataFetchResult result) {
+ // if result is null check for ingest module and show empty results.
+ if (result == null) {
+ showPieResultWithModuleCheck(null);
+ return;
+ }
+
+ // if error, show error
+ if (result.getResultType() == ResultType.ERROR) {
+ this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getErrorResult(result.getException()));
+ return;
+ }
+
+ TypesPieChartData data = result.getData();
+ if (data == null) {
+ // if no data, do an ingest module check with empty results
+ showPieResultWithModuleCheck(null);
+ } else if (!data.isUsefulContent()) {
+ // if no useful data, do an ingest module check and show data
+ showPieResultWithModuleCheck(data.getPieSlices());
+ } else {
+ // otherwise, show the data
+ this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(data.getPieSlices()));
+ }
+ }
+
+ /**
+ * Shows a message in the fileMimeTypesChart about the data source not being
+ * ingested with the file type ingest module if the data source has not been
+ * ingested with that module. Also shows data if present.
+ *
+ * @param items The list of items to show.
+ */
+ private void showPieResultWithModuleCheck(List items) {
+ boolean hasBeenIngested = false;
+ try {
+ hasBeenIngested = this.getIngestModuleCheckUtil().isModuleIngested(getDataSource(), FILE_TYPE_FACTORY);
+ } catch (TskCoreException | SleuthkitCaseProviderException ex) {
+ logger.log(Level.WARNING, "There was an error fetching whether or not the current data source has been ingested with the file type ingest module.", ex);
+ }
+
+ if (hasBeenIngested) {
+ this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(items));
+ } else {
+ this.fileMimeTypesChart.showDataWithMessage(items, getDefaultNoIngestMessage(FILE_TYPE_MODULE_NAME));
+ }
}
/**
@@ -304,6 +431,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane scrollParent = new javax.swing.JScrollPane();
javax.swing.JPanel contentParent = new javax.swing.JPanel();
+ javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JPanel usagePanel = usageLabel;
javax.swing.JPanel osPanel = osLabel;
javax.swing.JPanel sizePanel = sizeLabel;
@@ -322,6 +450,12 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
contentParent.setMinimumSize(new java.awt.Dimension(400, 490));
contentParent.setLayout(new javax.swing.BoxLayout(contentParent, javax.swing.BoxLayout.PAGE_AXIS));
+ ingestRunningPanel.setAlignmentX(0.0F);
+ ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
+ ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
+ ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
+ contentParent.add(ingestRunningPanel);
+
usagePanel.setAlignmentX(0.0F);
usagePanel.setMaximumSize(new java.awt.Dimension(32767, 20));
usagePanel.setMinimumSize(new java.awt.Dimension(10, 20));
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.form
index a7a3d6e0b2..28560a3ec7 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.form
@@ -60,6 +60,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java
index 6437812296..476d8c5e6f 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Locale;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult;
@@ -36,6 +37,7 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary.TopP
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
@@ -70,6 +72,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
private static final int TOP_SEARCHES_COUNT = 10;
private static final int TOP_ACCOUNTS_COUNT = 5;
private static final int TOP_DEVICES_COUNT = 10;
+ private static final String ANDROID_FACTORY = "org.python.proxies.module$AndroidModuleFactory";
+ private static final String ANDROID_MODULE_NAME = "Android Analyzer";
/**
* Gets a string formatted date or returns empty string if the date is null.
@@ -220,6 +224,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topAccountsTable
);
+ private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
+
private final List> dataFetchComponents;
private final TopProgramsSummary topProgramsData;
@@ -250,28 +256,43 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// top programs query
new DataFetchComponents>(
(dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
- (result) -> topProgramsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.UserActivityPanel_noDataExists())),
+ (result) -> {
+ showResultWithModuleCheck(topProgramsTable, result,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ }),
// top domains query
new DataFetchComponents>(
(dataSource) -> userActivityData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT),
- (result) -> recentDomainsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.UserActivityPanel_noDataExists())),
+ (result) -> {
+ showResultWithModuleCheck(recentDomainsTable, result,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ }),
// top web searches query
new DataFetchComponents>(
(dataSource) -> userActivityData.getMostRecentWebSearches(dataSource, TOP_SEARCHES_COUNT),
- (result) -> topWebSearchesTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.UserActivityPanel_noDataExists())),
+ (result) -> {
+ showResultWithModuleCheck(topWebSearchesTable, result,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ }),
// top devices query
new DataFetchComponents>(
(dataSource) -> userActivityData.getRecentDevices(dataSource, TOP_DEVICES_COUNT),
- (result) -> topDevicesAttachedTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.UserActivityPanel_noDataExists())),
+ (result) -> {
+ showResultWithModuleCheck(topDevicesAttachedTable, result,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
+ IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
+ }),
// top accounts query
new DataFetchComponents>(
(dataSource) -> userActivityData.getRecentAccounts(dataSource, TOP_ACCOUNTS_COUNT),
- (result) -> topAccountsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
- Bundle.UserActivityPanel_noDataExists()))
+ (result) -> {
+ showResultWithModuleCheck(topAccountsTable, result,
+ ANDROID_FACTORY,
+ ANDROID_MODULE_NAME);
+ })
);
initComponents();
@@ -299,6 +320,12 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tables, dataSource);
}
+ @Override
+ public void close() {
+ ingestRunningLabel.unregister();
+ super.close();
+ }
+
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@@ -310,6 +337,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane contentScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel contentPanel = new javax.swing.JPanel();
+ javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel topProgramsTablePanel = topProgramsTable;
@@ -340,6 +368,12 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
contentPanel.setMinimumSize(new java.awt.Dimension(10, 450));
contentPanel.setLayout(new javax.swing.BoxLayout(contentPanel, javax.swing.BoxLayout.PAGE_AXIS));
+ ingestRunningPanel.setAlignmentX(0.0F);
+ ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
+ ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
+ ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
+ contentPanel.add(ingestRunningPanel);
+
programsRunLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.programsRunLabel.text")); // NOI18N
programsRunLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/Bundle.properties-MERGED
index 491c3bfa56..c06bc6850a 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/Bundle.properties-MERGED
@@ -1,3 +1,5 @@
AbstractLoadableComponent_errorMessage_defaultText=There was an error loading results.
AbstractLoadableComponent_loadingMessage_defaultText=Loading results...
AbstractLoadableComponent_noDataExists_defaultText=No data exists.
+IngestRunningLabel_defaultMessage=Ingest is currently running.
+PieChartPanel_noDataLabel=No Data
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchWorker.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchWorker.java
index a89b273fcc..10ed19b87a 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchWorker.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchWorker.java
@@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
-import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.sleuthkit.autopsy.coreutils.Logger;
@@ -140,9 +139,6 @@ public class DataFetchWorker extends SwingWorker {
}
}
- // otherwise, there is an error to log
- logger.log(Level.WARNING, "There was an error while fetching results.", ex);
-
// and pass the result to the client
resultHandler.accept(DataFetchResult.getErrorResult(inner));
return;
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultArtifactUpdateGovernor.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultArtifactUpdateGovernor.java
index d664161696..390317a955 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultArtifactUpdateGovernor.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultArtifactUpdateGovernor.java
@@ -36,6 +36,10 @@ public interface DefaultArtifactUpdateGovernor extends DefaultUpdateGovernor {
@Override
default boolean isRefreshRequired(ModuleDataEvent evt) {
+ if (evt == null || evt.getBlackboardArtifactType() == null) {
+ return false;
+ }
+
return getArtifactTypeIdsForRefresh().contains(evt.getBlackboardArtifactType().getTypeID());
}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/IngestRunningLabel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/IngestRunningLabel.java
new file mode 100644
index 0000000000..0be1e85396
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/IngestRunningLabel.java
@@ -0,0 +1,167 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeListener;
+import java.net.URL;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+
+/**
+ * JLabel that shows ingest is running.
+ */
+@Messages({
+ "IngestRunningLabel_defaultMessage=Ingest is currently running."
+})
+public class IngestRunningLabel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+ public static final String DEFAULT_MESSAGE = Bundle.IngestRunningLabel_defaultMessage();
+ private static final URL DEFAULT_ICON = IngestRunningLabel.class.getResource("/org/sleuthkit/autopsy/modules/filetypeid/warning16.png");
+
+ private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(
+ IngestManager.IngestJobEvent.STARTED,
+ IngestManager.IngestJobEvent.CANCELLED,
+ IngestManager.IngestJobEvent.COMPLETED
+ );
+
+ private static Set activeLabels = new HashSet<>();
+ private static PropertyChangeListener classListener = null;
+ private static Object lockObject = new Object();
+
+ /**
+ * Setup ingest event listener for the current label.
+ *
+ * @param label The label.
+ */
+ private static void setupListener(IngestRunningLabel label) {
+ synchronized (lockObject) {
+
+ // if listener is not initialized, initialize it.
+ if (classListener == null) {
+ classListener = (evt) -> {
+ if (evt == null) {
+ return;
+ }
+
+ if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.STARTED.toString())) {
+ // ingest started
+ notifyListeners(true);
+
+ } else if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.CANCELLED.toString())
+ || evt.getPropertyName().equals(IngestManager.IngestJobEvent.COMPLETED.toString())) {
+ // ingest cancelled or finished
+ notifyListeners(false);
+
+ }
+ };
+ IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, classListener);
+ }
+
+ // add the item to the set
+ activeLabels.add(label);
+ }
+ }
+
+ /**
+ * Notifies all listening instances of an update in ingest state.
+ *
+ * @param ingestIsRunning Whether or not ingest is running currently.
+ */
+ private static void notifyListeners(boolean ingestIsRunning) {
+ synchronized (lockObject) {
+ for (IngestRunningLabel label : activeLabels) {
+ label.refreshState(ingestIsRunning);
+ }
+ }
+ }
+
+ /**
+ * Removes a label from listening events.
+ *
+ * @param label The label to remove from listening events.
+ */
+ private static void removeListener(IngestRunningLabel label) {
+ synchronized (lockObject) {
+ activeLabels.remove(label);
+ if (activeLabels.isEmpty() && classListener != null) {
+ IngestManager.getInstance().removeIngestJobEventListener(classListener);
+ classListener = null;
+ }
+ }
+ }
+
+ /**
+ * Main constructor with default message and showing icon.
+ */
+ public IngestRunningLabel() {
+ this(DEFAULT_MESSAGE, true);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message The message to be shown.
+ * @param showWarningIcon Whether or not to show warning icon.
+ */
+ public IngestRunningLabel(String message, boolean showWarningIcon) {
+ JLabel jlabel = new JLabel();
+ jlabel.setText(message);
+
+ if (showWarningIcon) {
+ jlabel.setIcon(new ImageIcon(DEFAULT_ICON));
+ }
+
+ setLayout(new BorderLayout());
+ add(jlabel, BorderLayout.NORTH);
+
+ setupListener(this);
+ refreshState();
+ }
+
+ /**
+ * Refresh state of this label based on ingest status.
+ */
+ protected final void refreshState() {
+ refreshState(IngestManager.getInstance().isIngestRunning());
+ }
+
+ /**
+ * Refresh state of this label based on ingest status.
+ *
+ * @param ingestIsRunning True if ingest is running.
+ */
+ protected final void refreshState(boolean ingestIsRunning) {
+ setVisible(ingestIsRunning);
+ }
+
+ /**
+ * Unregister this instance from listening for ingest status changes.
+ */
+ public void unregister() {
+ removeListener(this);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
index 8d6eab4a9b..466c578b60 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
@@ -33,10 +33,14 @@ import org.jfree.chart.panel.AbstractOverlay;
import org.jfree.chart.panel.Overlay;
import org.jfree.chart.plot.PiePlot;
import org.jfree.data.general.DefaultPieDataset;
+import org.openide.util.NbBundle.Messages;
/**
* A pie chart panel.
*/
+@Messages({
+ "PieChartPanel_noDataLabel=No Data"
+})
public class PieChartPanel extends AbstractLoadableComponent> {
/**
@@ -111,6 +115,13 @@ public class PieChartPanel extends AbstractLoadableComponent data) {
this.dataset.clear();
- if (data != null) {
+ if (data != null && !data.isEmpty()) {
for (PieChartPanel.PieChartItem slice : data) {
this.dataset.setValue(slice.getLabel(), slice.getValue());
}
+ } else {
+ // show a no data label if no data.
+ // this in fact shows a very small number for the value
+ // that should be way below rounding error for formatters
+ this.dataset.setValue(Bundle.PieChartPanel_noDataLabel(), NEAR_ZERO);
}
}
+
+ /**
+ * Shows a message on top of data.
+ *
+ * @param data The data.
+ * @param message The message.
+ */
+ public synchronized void showDataWithMessage(List data, String message) {
+ setResults(data);
+ setMessage(true, message);
+ repaint();
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/Bundle.properties b/Core/src/org/sleuthkit/autopsy/discovery/Bundle.properties
deleted file mode 100644
index 795018f1ac..0000000000
--- a/Core/src/org/sleuthkit/autopsy/discovery/Bundle.properties
+++ /dev/null
@@ -1,106 +0,0 @@
-FileSearchDialog.jLabel1.text=File Type
-FileSearchDialog.dsCheckBox.text=Data source
-FileSearchDialog.cancelButton.text=Cancel
-FileSearchDialog.freqCheckBox.text=CR Frequency
-FileSearchDialog.sizeCheckBox.text=Size
-FileSearchDialog.kwCheckBox.text=Keyword
-FileSearchDialog.addParentButton.text=Add
-FileSearchDialog.deleteParentButton.text=Delete
-FileSearchDialog.parentFullRadioButton.text=Full
-FileSearchDialog.parentSubstringRadioButton.text=Substring
-FileSearchDialog.jLabel2.text=(All will be used)
-FileSearchDialog.jLabel3.text=Group by attribute:
-FileSearchDialog.jLabel4.text=Order groups by:
-FileSearchDialog.orderAttrRadioButton.text=Attribute
-FileSearchDialog.orderSizeRadioButton.text=Group Size
-FileSearchDialog.jLabel5.text=Order files by:
-FileSearchDialog.parentCheckBox.text=Parent
-FileSearchPanel.sortingPanel.border.title=Grouping
-FileSearchPanel.addButton.text=Add
-FileSearchPanel.substringRadioButton.text=Substring
-FileSearchPanel.fullRadioButton.text=Full
-FileSearchPanel.parentCheckbox.text=Parent Folder:
-FileSearchPanel.keywordCheckbox.text=Keyword:
-FileSearchPanel.crFrequencyCheckbox.text=Past Occurrences:
-FileSearchPanel.dataSourceCheckbox.text=Data Source:
-FileSearchPanel.sizeCheckbox.text=File Size:
-FileSearchPanel.orderGroupsByLabel.text=Order Groups By:
-FileSearchPanel.filtersScrollPane.border.title=Filters
-FileSearchPanel.parentLabel.text=(All will be used)
-FileSearchPanel.deleteButton.text=Delete
-FileSearchPanel.orderByLabel.text=Order Within Groups By:
-FileSearchPanel.groupByLabel.text=Group By:
-FileSearchDialog.searchButton.text=Search
-FileSearchDialog.hashCheckBox.text=Hash Set
-FileSearchDialog.intCheckBox.text=Interesting Items
-FileSearchDialog.tagsCheckBox.text=Tags
-FileSearchDialog.objCheckBox.text=Objects
-FileSearchDialog.exifCheckBox.text=Must contain EXIF data
-FileSearchDialog.notableCheckBox.text=Must have been tagged as notable
-FileSearchDialog.scoreCheckBox.text=Has score
-FileSearchPanel.hashSetCheckbox.text=Hash Set:
-FileSearchPanel.tagsCheckbox.text=Tag:
-FileSearchPanel.interestingItemsCheckbox.text=Interesting Item:
-FileSearchPanel.scoreCheckbox.text=Has Score:
-FileSearchPanel.notableCheckbox.text=Must have been tagged as notable
-FileSearchPanel.objectsCheckbox.text=Object Detected:
-ResultsPanel.currentPageLabel.text=Page: -
-ResultsPanel.pageControlsLabel.text=Pages:
-ResultsPanel.gotoPageLabel.text=Go to Page:
-ResultsPanel.pageSizeLabel.text=Page Size:
-DiscoveryExtractAction.title.extractFiles.text=Extract File
-FileSearchPanel.includeRadioButton.text=Include
-FileSearchPanel.excludeRadioButton.text=Exclude
-FileSearchPanel.knownFilesCheckbox.toolTipText=
-FileSearchPanel.knownFilesCheckbox.text=Hide known files
-GroupListPanel.groupKeyList.border.title=Groups
-FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings
-DocumentPanel.fileSizeLabel.toolTipText=
-DocumentPanel.isDeletedLabel.toolTipText=
-ImageThumbnailPanel.isDeletedLabel.toolTipText=
-FileSearchPanel.userCreatedCheckbox.text=Possibly User Created
-DiscoveryDialog.documentsButton.text=Documents
-DiscoveryDialog.videosButton.text=Videos
-DiscoveryDialog.imagesButton.text=Images
-DiscoveryDialog.searchButton.text=Search
-DetailsPanel.instancesList.border.title=Instances
-SizeFilterPanel.sizeCheckbox.text=File Size:
-DataSourceFilterPanel.dataSourceCheckbox.text=Data Source:
-UserCreatedFilterPanel.userCreatedCheckbox.text=Possibly User Created
-# To change this license header, choose License Headers in Project Properties.
-# To change this template file, choose Tools | Templates
-# and open the template in the editor.
-HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
-InterestingItemFilterPanel.interestingItemsCheckbox.text=Interesting Item:
-ParentFolderFilterPanel.parentCheckbox.text=Parent Folder:
-ParentFolderFilterPanel.deleteButton.text=Delete
-ParentFolderFilterPanel.excludeRadioButton.text=Exclude
-ParentFolderFilterPanel.includeRadioButton.text=Include
-ParentFolderFilterPanel.substringRadioButton.text=Substring
-ParentFolderFilterPanel.fullRadioButton.text=Full
-ParentFolderFilterPanel.parentLabel.text=(All will be used)
-ParentFolderFilterPanel.addButton.text=Add
-ParentFolderFilterPanel.parentCheckbox.text_1=Parent Folder:
-ParentFolderFilterPanel.addButton.text_1=Add
-ParentFolderFilterPanel.deleteButton.text_1=Delete
-ParentFolderFilterPanel.excludeRadioButton.text_1=Exclude
-ParentFolderFilterPanel.substringRadioButton.text_1=Substring
-ParentFolderFilterPanel.includeRadioButton.text_1=Include
-ParentFolderFilterPanel.fullRadioButton.text_1=Full
-ParentFolderFilterPanel.parentLabel.text_1=(All will be used)
-InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item:
-UserCreatedFilterPanel.userCreatedCheckbox.text_1=Possibly User Created
-PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
-ObjectDetectedFilterPanel.text=Object Detected:
-DiscoveryDialog.sortingPanel.border.title=Step 3: Choose display settings
-DiscoveryDialog.groupByLabel.text=Group By:
-DiscoveryDialog.orderByLabel.text=Order Within Groups By:
-DiscoveryDialog.orderGroupsByLabel.text=Order Groups By:
-ImageFilterPanel.imageFiltersSplitPane.toolTipText=
-DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
-ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
-VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
-DiscoveryDialog.step1Label.text=Step 1: Choose result type
-ResultsSplitPaneDivider.hideButton.text=
-ResultsSplitPaneDivider.showButton.text=
-ResultsSplitPaneDivider.detailsLabel.text=Details Area
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/Bundle.properties-MERGED
deleted file mode 100644
index c0d9ef6975..0000000000
--- a/Core/src/org/sleuthkit/autopsy/discovery/Bundle.properties-MERGED
+++ /dev/null
@@ -1,280 +0,0 @@
-CTL_OpenDiscoveryAction=Discovery
-# {0} - dataSourceName
-DataSourceModuleWrapper.exifModule.text=Picture Analyzer module was not run on data source: {0}\n
-# {0} - dataSourceName
-DataSourceModuleWrapper.fileTypeModule.text=File Type Identification module was not run on data source: {0}\n
-# {0} - dataSourceName
-DataSourceModuleWrapper.hashModule.text=Hash Lookup module was not run on data source: {0}\n
-DiscoveryDialog.name.text=Discovery
-DiscoveryTopComponent.cancelButton.text=Cancel Search
-DiscoveryTopComponent.name=\ Discovery
-DiscoveryTopComponent.newSearch.text=New Search
-DiscoveryTopComponent.searchCancelled.text=Search has been cancelled.
-# {0} - search
-DiscoveryTopComponent.searchComplete.text=Results with {0}
-# {0} - searchType
-DiscoveryTopComponent.searchInProgress.text=Performing search for results of type {0}. Please wait.
-DiscoveryUiUtility.bytes.text=bytes
-DiscoveryUiUtility.gigaBytes.text=GB
-DiscoveryUiUtility.kiloBytes.text=KB
-DiscoveryUiUtility.megaBytes.text=MB
-# {0} - fileSize
-# {1} - units
-DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}
-DiscoveryUiUtility.terraBytes.text=TB
-# {0} - otherInstanceCount
-DocumentPanel.nameLabel.more.text=\ and {0} more
-DocumentPanel.noImageExtraction.text=0 of ? images
-DocumentPanel.numberOfImages.noImages=No images
-# {0} - numberOfImages
-DocumentPanel.numberOfImages.text=1 of {0} images
-DocumentWrapper.previewInitialValue=Preview not generated yet.
-FileGroup.groupSortingAlgorithm.groupName.text=Group Name
-FileGroup.groupSortingAlgorithm.groupSize.text=Group Size
-# {0} - Data source name
-# {1} - Data source ID
-FileSearch.DataSourceGroupKey.datasourceAndID={0}(ID: {1})
-# {0} - Data source ID
-FileSearch.DataSourceGroupKey.idOnly=Data source (ID: {0})
-FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview.
-FileSearch.documentSummary.noPreview=No preview available.
-FileSearch.FileTagGroupKey.noSets=None
-# {0} - file name
-FileSearch.genVideoThumb.progress.text=extracting temporary file {0}
-FileSearch.GroupingAttributeType.datasource.displayName=Data Source
-FileSearch.GroupingAttributeType.fileType.displayName=File Type
-FileSearch.GroupingAttributeType.frequency.displayName=Past Occurrences
-FileSearch.GroupingAttributeType.hash.displayName=Hash Set
-FileSearch.GroupingAttributeType.interestingItem.displayName=Interesting Item
-FileSearch.GroupingAttributeType.keywordList.displayName=Keyword
-FileSearch.GroupingAttributeType.none.displayName=None
-FileSearch.GroupingAttributeType.object.displayName=Object Detected
-FileSearch.GroupingAttributeType.parent.displayName=Parent Folder
-FileSearch.GroupingAttributeType.size.displayName=File Size
-FileSearch.GroupingAttributeType.tag.displayName=Tag
-FileSearch.HashHitsGroupKey.noHashHits=None
-FileSearch.InterestingItemGroupKey.noSets=None
-FileSearch.KeywordListGroupKey.noKeywords=None
-FileSearch.NoGroupingGroupKey.allFiles=All Files
-FileSearch.ObjectDetectedGroupKey.noSets=None
-FileSearchData.FileSize.100kbto1mb=: 100KB-1MB
-FileSearchData.FileSize.100mbto1gb=: 100MB-1GB
-FileSearchData.FileSize.10PlusGb=: 10GB+
-FileSearchData.FileSize.16kbto100kb=: 16-100KB
-FileSearchData.FileSize.1gbto5gb=: 1-5GB
-FileSearchData.FileSize.1mbto50mb=: 1-50MB
-FileSearchData.FileSize.200PlusMb=: 200MB+
-FileSearchData.FileSize.500kbto100mb=: 500KB-100MB
-FileSearchData.FileSize.50mbto200mb=: 50-200MB
-FileSearchData.FileSize.5gbto10gb=: 5-10GB
-FileSearchData.FileSize.LARGE.displayName=Large
-FileSearchData.FileSize.MEDIUM.displayName=Medium
-FileSearchData.FileSize.SMALL.displayName=Small
-FileSearchData.FileSize.upTo16kb=: 0-16KB
-FileSearchData.FileSize.upTo500kb=: 0-500KB
-FileSearchData.FileSize.XLARGE.displayName=XLarge
-FileSearchData.FileSize.XSMALL.displayName=XSmall
-FileSearchData.FileSize.XXLARGE.displayName=XXLarge
-FileSearchData.FileType.Audio.displayName=Audio
-FileSearchData.FileType.Documents.displayName=Documents
-FileSearchData.FileType.Executables.displayName=Executables
-FileSearchData.FileType.Image.displayName=Image
-FileSearchData.FileType.Other.displayName=Other/Unknown
-FileSearchData.FileType.Video.displayName=Video
-FileSearchData.Frequency.common.displayName=Common (11 - 100)
-FileSearchData.Frequency.known.displayName=Known (NSRL)
-FileSearchData.Frequency.rare.displayName=Rare (2-10)
-FileSearchData.Frequency.unique.displayName=Unique (1)
-FileSearchData.Frequency.unknown.displayName=Unknown
-FileSearchData.Frequency.verycommon.displayName=Very Common (100+)
-FileSearchData.Score.interesting.displayName=Interesting
-FileSearchData.Score.notable.displayName=Notable
-FileSearchData.Score.unknown.displayName=Unknown
-FileSearchDialog.jLabel1.text=File Type
-FileSearchDialog.dsCheckBox.text=Data source
-FileSearchDialog.cancelButton.text=Cancel
-FileSearchDialog.freqCheckBox.text=CR Frequency
-FileSearchDialog.sizeCheckBox.text=Size
-FileSearchDialog.kwCheckBox.text=Keyword
-FileSearchDialog.addParentButton.text=Add
-FileSearchDialog.deleteParentButton.text=Delete
-FileSearchDialog.parentFullRadioButton.text=Full
-FileSearchDialog.parentSubstringRadioButton.text=Substring
-FileSearchDialog.jLabel2.text=(All will be used)
-FileSearchDialog.jLabel3.text=Group by attribute:
-FileSearchDialog.jLabel4.text=Order groups by:
-FileSearchDialog.orderAttrRadioButton.text=Attribute
-FileSearchDialog.orderSizeRadioButton.text=Group Size
-FileSearchDialog.jLabel5.text=Order files by:
-FileSearchDialog.parentCheckBox.text=Parent
-FileSearchFiltering.concatenateSetNamesForDisplay.comma=,
-# {0} - Data source name
-# {1} - Data source ID
-FileSearchFiltering.DataSourceFilter.datasource={0}({1})
-# {0} - filters
-FileSearchFiltering.DataSourceFilter.desc=Data source(s): {0}
-FileSearchFiltering.DataSourceFilter.or=,
-# {0} - filters
-FileSearchFiltering.FileTypeFilter.desc=Type: {0}
-FileSearchFiltering.FileTypeFilter.or=,
-# {0} - filters
-FileSearchFiltering.FrequencyFilter.desc=Past occurrences: {0}
-FileSearchFiltering.FrequencyFilter.or=,
-# {0} - filters
-FileSearchFiltering.HashSetFilter.desc=Hash set hits in set(s): {0}
-# {0} - filters
-FileSearchFiltering.InterestingItemSetFilter.desc=Interesting item hits in set(s): {0}
-# {0} - filters
-FileSearchFiltering.KeywordListFilter.desc=Keywords in list(s): {0}
-FileSearchFiltering.KnownFilter.desc=which are not known
-# {0} - filters
-FileSearchFiltering.ObjectDetectionFilter.desc=Objects detected in set(s): {0}
-# {0} - filters
-FileSearchFiltering.ParentFilter.desc=Paths matching: {0}
-FileSearchFiltering.ParentFilter.exact=(exact match)
-FileSearchFiltering.ParentFilter.excluded=(excluded)
-FileSearchFiltering.ParentFilter.included=(included)
-FileSearchFiltering.ParentFilter.or=,
-FileSearchFiltering.ParentFilter.substring=(substring)
-FileSearchFiltering.ParentSearchTerm.excludeString=\ (exclude)
-FileSearchFiltering.ParentSearchTerm.fullString=\ (exact)
-FileSearchFiltering.ParentSearchTerm.includeString=\ (include)
-FileSearchFiltering.ParentSearchTerm.subString=\ (substring)
-FileSearchFiltering.PreviouslyNotableFilter.desc=that were previously marked as notable
-# {0} - filters
-FileSearchFiltering.ScoreFilter.desc=Score(s) of : {0}
-# {0} - filters
-FileSearchFiltering.SizeFilter.desc=Size(s): {0}
-FileSearchFiltering.SizeFilter.or=,
-# {0} - tag names
-FileSearchFiltering.TagsFilter.desc=Tagged {0}
-FileSearchFiltering.TagsFilter.or=,
-FileSearchFiltering.UserCreatedFilter.desc=that contain EXIF data
-FileSearchPanel.sortingPanel.border.title=Grouping
-FileSearchPanel.addButton.text=Add
-FileSearchPanel.substringRadioButton.text=Substring
-FileSearchPanel.fullRadioButton.text=Full
-FileSearchPanel.parentCheckbox.text=Parent Folder:
-FileSearchPanel.keywordCheckbox.text=Keyword:
-FileSearchPanel.crFrequencyCheckbox.text=Past Occurrences:
-FileSearchPanel.dataSourceCheckbox.text=Data Source:
-FileSearchPanel.sizeCheckbox.text=File Size:
-FileSearchPanel.orderGroupsByLabel.text=Order Groups By:
-FileSearchPanel.filtersScrollPane.border.title=Filters
-FileSearchPanel.parentLabel.text=(All will be used)
-FileSearchPanel.deleteButton.text=Delete
-FileSearchPanel.orderByLabel.text=Order Within Groups By:
-FileSearchPanel.groupByLabel.text=Group By:
-FileSearchDialog.searchButton.text=Search
-FileSearchDialog.hashCheckBox.text=Hash Set
-FileSearchDialog.intCheckBox.text=Interesting Items
-FileSearchDialog.tagsCheckBox.text=Tags
-FileSearchDialog.objCheckBox.text=Objects
-FileSearchDialog.exifCheckBox.text=Must contain EXIF data
-FileSearchDialog.notableCheckBox.text=Must have been tagged as notable
-FileSearchDialog.scoreCheckBox.text=Has score
-FileSearchPanel.hashSetCheckbox.text=Hash Set:
-FileSearchPanel.tagsCheckbox.text=Tag:
-FileSearchPanel.interestingItemsCheckbox.text=Interesting Item:
-FileSearchPanel.scoreCheckbox.text=Has Score:
-FileSearchPanel.notableCheckbox.text=Must have been tagged as notable
-FileSearchPanel.objectsCheckbox.text=Object Detected:
-FileSorter.SortingMethod.datasource.displayName=Data Source
-FileSorter.SortingMethod.filename.displayName=File Name
-FileSorter.SortingMethod.filesize.displayName=File Size
-FileSorter.SortingMethod.filetype.displayName=File Type
-FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency
-FileSorter.SortingMethod.fullPath.displayName=Full Path
-FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names
-GroupsListPanel.noResults.message.text=No results were found for the selected filters.\n\nReminder:\n -The File Type Identification module must be run on each data source you want to find results in.\n -The Hash Lookup module must be run on each data source if you want to filter by past occurrence.\n -The Exif module must be run on each data source if you are filtering by User Created content.
-GroupsListPanel.noResults.title.text=No results found
-ImageThumbnailPanel.isDeleted.text=All instances of file are deleted.
-# {0} - otherInstanceCount
-ImageThumbnailPanel.nameLabel.more.text=\ and {0} more
-OpenDiscoveryAction.resultsIncomplete.text=Discovery results may be incomplete
-ResultFile.score.interestingResult.description=At least one instance of the file has an interesting result associated with it.
-ResultFile.score.notableFile.description=At least one instance of the file was recognized as notable.
-ResultFile.score.notableTaggedFile.description=At least one instance of the file is tagged with a notable tag.
-ResultFile.score.taggedFile.description=At least one instance of the file has been tagged.
-# {0} - currentPage
-# {1} - totalPages
-ResultsPanel.currentPage.displayValue=Page: {0} of {1}
-ResultsPanel.currentPageLabel.text=Page: -
-ResultsPanel.documentPreview.text=Document preview creation cancelled.
-# {0} - selectedPage
-# {1} - maxPage
-ResultsPanel.invalidPageNumber.message=The selected page number {0} does not exist. Please select a value from 1 to {1}.
-ResultsPanel.invalidPageNumber.title=Invalid Page Number
-ResultsPanel.openInExternalViewer.name=Open in External Viewer
-ResultsPanel.pageControlsLabel.text=Pages:
-ResultsPanel.gotoPageLabel.text=Go to Page:
-ResultsPanel.pageSizeLabel.text=Page Size:
-DiscoveryExtractAction.title.extractFiles.text=Extract File
-FileSearchPanel.includeRadioButton.text=Include
-FileSearchPanel.excludeRadioButton.text=Exclude
-FileSearchPanel.knownFilesCheckbox.toolTipText=
-FileSearchPanel.knownFilesCheckbox.text=Hide known files
-GroupListPanel.groupKeyList.border.title=Groups
-FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings
-DocumentPanel.fileSizeLabel.toolTipText=
-DocumentPanel.isDeletedLabel.toolTipText=
-ImageThumbnailPanel.isDeletedLabel.toolTipText=
-FileSearchPanel.userCreatedCheckbox.text=Possibly User Created
-DiscoveryDialog.documentsButton.text=Documents
-DiscoveryDialog.videosButton.text=Videos
-DiscoveryDialog.imagesButton.text=Images
-DiscoveryDialog.searchButton.text=Search
-DetailsPanel.instancesList.border.title=Instances
-ResultsPanel.unableToCreate.text=Unable to create summary.
-ResultsPanel.viewFileInDir.name=View File in Directory
-SizeFilterPanel.sizeCheckbox.text=File Size:
-DataSourceFilterPanel.dataSourceCheckbox.text=Data Source:
-UserCreatedFilterPanel.userCreatedCheckbox.text=Possibly User Created
-# To change this license header, choose License Headers in Project Properties.
-# To change this template file, choose Tools | Templates
-# and open the template in the editor.
-HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
-InterestingItemFilterPanel.interestingItemsCheckbox.text=Interesting Item:
-ParentFolderFilterPanel.parentCheckbox.text=Parent Folder:
-ParentFolderFilterPanel.deleteButton.text=Delete
-ParentFolderFilterPanel.excludeRadioButton.text=Exclude
-ParentFolderFilterPanel.includeRadioButton.text=Include
-ParentFolderFilterPanel.substringRadioButton.text=Substring
-ParentFolderFilterPanel.fullRadioButton.text=Full
-ParentFolderFilterPanel.parentLabel.text=(All will be used)
-ParentFolderFilterPanel.addButton.text=Add
-ParentFolderFilterPanel.parentCheckbox.text_1=Parent Folder:
-ParentFolderFilterPanel.addButton.text_1=Add
-ParentFolderFilterPanel.deleteButton.text_1=Delete
-ParentFolderFilterPanel.excludeRadioButton.text_1=Exclude
-ParentFolderFilterPanel.substringRadioButton.text_1=Substring
-ParentFolderFilterPanel.includeRadioButton.text_1=Include
-ParentFolderFilterPanel.fullRadioButton.text_1=Full
-ParentFolderFilterPanel.parentLabel.text_1=(All will be used)
-InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item:
-UserCreatedFilterPanel.userCreatedCheckbox.text_1=Possibly User Created
-PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
-ObjectDetectedFilterPanel.text=Object Detected:
-DiscoveryDialog.sortingPanel.border.title=Step 3: Choose display settings
-DiscoveryDialog.groupByLabel.text=Group By:
-DiscoveryDialog.orderByLabel.text=Order Within Groups By:
-DiscoveryDialog.orderGroupsByLabel.text=Order Groups By:
-ImageFilterPanel.imageFiltersSplitPane.toolTipText=
-DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
-ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
-VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
-DiscoveryDialog.step1Label.text=Step 1: Choose result type
-ResultsSplitPaneDivider.hideButton.text=
-ResultsSplitPaneDivider.showButton.text=
-ResultsSplitPaneDivider.detailsLabel.text=Details Area
-VideoThumbnailPanel.bytes.text=bytes
-VideoThumbnailPanel.deleted.text=All instances of file are deleted.
-VideoThumbnailPanel.gigaBytes.text=GB
-VideoThumbnailPanel.kiloBytes.text=KB
-VideoThumbnailPanel.megaBytes.text=MB
-# {0} - otherInstanceCount
-VideoThumbnailPanel.nameLabel.more.text=\ and {0} more
-# {0} - fileSize
-# {1} - units
-VideoThumbnailPanel.sizeLabel.text=Size: {0} {1}
-VideoThumbnailPanel.terraBytes.text=TB
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryUiUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryUiUtils.java
deleted file mode 100644
index 55392f54f2..0000000000
--- a/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryUiUtils.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Autopsy
- *
- * Copyright 2020 Basis Technology Corp.
- * Contact: carrier sleuthkit org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.sleuthkit.autopsy.discovery;
-
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Point;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import javax.swing.ImageIcon;
-import javax.swing.JComponent;
-import javax.swing.JOptionPane;
-import javax.swing.JScrollPane;
-import javax.swing.JTextPane;
-import org.openide.util.ImageUtilities;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.datamodel.BlackboardArtifact;
-import org.sleuthkit.datamodel.BlackboardAttribute;
-import org.sleuthkit.datamodel.DataSource;
-import org.sleuthkit.datamodel.IngestJobInfo;
-import org.sleuthkit.datamodel.SleuthkitCase;
-import org.sleuthkit.datamodel.TskCoreException;
-
-/**
- * Utility class for the various user interface elements used by Discovery.
- */
-final class DiscoveryUiUtils {
-
- private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
- private static final int BYTE_UNIT_CONVERSION = 1000;
- private static final int ICON_SIZE = 16;
- private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
- private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
- private static final String DELETE_ICON_PATH = "org/sleuthkit/autopsy/images/file-icon-deleted.png";
- private static final String UNSUPPORTED_DOC_PATH = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
- private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
- private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
- private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false));
- private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, false));
-
- @NbBundle.Messages({"# {0} - fileSize",
- "# {1} - units",
- "DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
- "DiscoveryUiUtility.bytes.text=bytes",
- "DiscoveryUiUtility.kiloBytes.text=KB",
- "DiscoveryUiUtility.megaBytes.text=MB",
- "DiscoveryUiUtility.gigaBytes.text=GB",
- "DiscoveryUiUtility.terraBytes.text=TB"})
- /**
- * Convert a size in bytes to a string with representing the size in the
- * largest units which represent the value as being greater than or equal to
- * one. Result will be rounded down to the nearest whole number of those
- * units.
- *
- * @param bytes Size in bytes.
- */
- static String getFileSizeString(long bytes) {
- long size = bytes;
- int unitsSwitchValue = 0;
- while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
- size /= BYTE_UNIT_CONVERSION;
- unitsSwitchValue++;
- }
- String units;
- switch (unitsSwitchValue) {
- case 1:
- units = Bundle.DiscoveryUiUtility_kiloBytes_text();
- break;
- case 2:
- units = Bundle.DiscoveryUiUtility_megaBytes_text();
- break;
- case 3:
- units = Bundle.DiscoveryUiUtility_gigaBytes_text();
- break;
- case 4:
- units = Bundle.DiscoveryUiUtility_terraBytes_text();
- break;
- default:
- units = Bundle.DiscoveryUiUtility_bytes_text();
- break;
- }
- return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
- }
-
- /**
- * Get the image to use when the document type does not support image
- * extraction.
- *
- * @return An image that indicates we don't know if there are images.
- */
- static ImageIcon getUnsupportedImageThumbnail() {
- return UNSUPPORTED_DOCUMENT_THUMBNAIL;
- }
-
- /**
- * Get the names of the sets which exist in the case database for the
- * specified artifact and attribute types.
- *
- * @param artifactType The artifact type to get the list of sets for.
- * @param setNameAttribute The attribute type which contains the set names.
- *
- * @return A list of set names which exist in the case for the specified
- * artifact and attribute types.
- *
- * @throws TskCoreException
- */
- static List getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
- List arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
- List setNames = new ArrayList<>();
- for (BlackboardArtifact art : arts) {
- for (BlackboardAttribute attr : art.getAttributes()) {
- if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
- String setName = attr.getValueString();
- if (!setNames.contains(setName)) {
- setNames.add(setName);
- }
- }
- }
- }
- Collections.sort(setNames);
- return setNames;
- }
-
- /**
- * Helper method to see if point is on the icon.
- *
- * @param comp The component to check if the cursor is over the icon of
- * @param point The point the cursor is at.
- *
- * @return True if the point is over the icon, false otherwise.
- */
- static boolean isPointOnIcon(Component comp, Point point) {
- return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
- }
-
- /**
- * Method to set the icon and tool tip text for a label to show deleted
- * status.
- *
- * @param isDeleted True if the label should reflect deleted status,
- * false otherwise.
- * @param isDeletedLabel The label to set the icon and tooltip for.
- */
- static void setDeletedIcon(boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
- if (isDeleted) {
- isDeletedLabel.setIcon(DELETED_ICON);
- isDeletedLabel.setToolTipText(Bundle.ImageThumbnailPanel_isDeleted_text());
- } else {
- isDeletedLabel.setIcon(null);
- isDeletedLabel.setToolTipText(null);
- }
- }
-
- /**
- * Method to set the icon and tool tip text for a label to show the score.
- *
- * @param resultFile The result file which the label should reflect the
- * score of.
- * @param scoreLabel The label to set the icon and tooltip for.
- */
- static void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
- switch (resultFile.getScore()) {
- case NOTABLE_SCORE:
- scoreLabel.setIcon(NOTABLE_SCORE_ICON);
- break;
- case INTERESTING_SCORE:
- scoreLabel.setIcon(INTERESTING_SCORE_ICON);
- break;
- case NO_SCORE: // empty case - this is interpreted as an intentional fall-through
- default:
- scoreLabel.setIcon(null);
- break;
- }
- scoreLabel.setToolTipText(resultFile.getScoreDescription());
- }
-
- /**
- * Get the size of the icons used by the UI.
- *
- * @return
- */
- static int getIconSize() {
- return ICON_SIZE;
- }
-
- /**
- * Helper method to display an error message when the results of the
- * Discovery Top component may be incomplete.
- */
- static void displayErrorMessage(DiscoveryDialog dialog) {
- //check if modules run and assemble message
- try {
- SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
- Map dataSourceIngestModules = new HashMap<>();
- for (DataSource dataSource : skCase.getDataSources()) {
- dataSourceIngestModules.put(dataSource.getId(), new DataSourceModulesWrapper(dataSource.getName()));
- }
-
- for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
- dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
- }
- String message = "";
- for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
- message += dsmodulesWrapper.getMessage();
- }
- if (!message.isEmpty()) {
- JScrollPane messageScrollPane = new JScrollPane();
- JTextPane messageTextPane = new JTextPane();
- messageTextPane.setText(message);
- messageTextPane.setVisible(true);
- messageTextPane.setEditable(false);
- messageTextPane.setCaretPosition(0);
- messageScrollPane.setMaximumSize(new Dimension(600, 100));
- messageScrollPane.setPreferredSize(new Dimension(600, 100));
- messageScrollPane.setViewportView(messageTextPane);
- JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.OpenDiscoveryAction_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
- }
- } catch (NoCurrentCaseException | TskCoreException ex) {
- logger.log(Level.WARNING, "Exception while determining which modules have been run for Discovery", ex);
- }
- dialog.validateDialog();
- }
-
- /**
- * Private constructor for DiscoveryUiUtils utility class.
- */
- private DiscoveryUiUtils() {
- //private constructor in a utility class intentionally left blank
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/FileSearch.java b/Core/src/org/sleuthkit/autopsy/discovery/FileSearch.java
deleted file mode 100644
index 0590351793..0000000000
--- a/Core/src/org/sleuthkit/autopsy/discovery/FileSearch.java
+++ /dev/null
@@ -1,2314 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2019-2020 Basis Technology Corp.
- * Contact: carrier sleuthkit org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.sleuthkit.autopsy.discovery;
-
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.io.Files;
-import java.awt.Image;
-import java.awt.image.BufferedImage;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.Reader;
-import java.nio.file.Paths;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.logging.Level;
-import javax.imageio.ImageIO;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang.StringUtils;
-import org.imgscalr.Scalr;
-import org.netbeans.api.progress.ProgressHandle;
-import org.opencv.core.Mat;
-import org.opencv.highgui.VideoCapture;
-import org.openide.util.Lookup;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
-import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
-import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
-import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil;
-import org.sleuthkit.autopsy.centralrepository.datamodel.InstanceTableCallback;
-import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
-import org.sleuthkit.autopsy.coreutils.ImageUtils;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import static org.sleuthkit.autopsy.coreutils.VideoUtils.getVideoFileInTempDir;
-import org.sleuthkit.autopsy.datamodel.ContentUtils;
-import org.sleuthkit.autopsy.discovery.FileSearchData.FileSize;
-import org.sleuthkit.autopsy.discovery.FileSearchData.FileType;
-import org.sleuthkit.autopsy.discovery.FileSearchData.Frequency;
-import org.sleuthkit.datamodel.AbstractFile;
-import org.sleuthkit.datamodel.BlackboardArtifact;
-import org.sleuthkit.datamodel.BlackboardAttribute;
-import org.sleuthkit.datamodel.CaseDbAccessManager;
-import org.sleuthkit.datamodel.Content;
-import org.sleuthkit.datamodel.ContentTag;
-import org.sleuthkit.datamodel.SleuthkitCase;
-import org.sleuthkit.datamodel.TskCoreException;
-import org.sleuthkit.datamodel.TskData;
-import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
-import org.sleuthkit.autopsy.textextractors.TextExtractor;
-import org.sleuthkit.autopsy.textextractors.TextExtractorFactory;
-import org.sleuthkit.autopsy.textsummarizer.TextSummarizer;
-import org.sleuthkit.autopsy.textsummarizer.TextSummary;
-import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
-import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
-import org.sleuthkit.autopsy.texttranslation.TranslationException;
-
-/**
- * Main class to perform the file search.
- */
-class FileSearch {
-
- private final static Logger logger = Logger.getLogger(FileSearch.class.getName());
- private static final int MAXIMUM_CACHE_SIZE = 10;
- private static final String THUMBNAIL_FORMAT = "png"; //NON-NLS
- private static final String VIDEO_THUMBNAIL_DIR = "video-thumbnails"; //NON-NLS
- private static final Cache>> searchCache = CacheBuilder.newBuilder()
- .maximumSize(MAXIMUM_CACHE_SIZE)
- .build();
- private static final int PREVIEW_SIZE = 256;
- private static volatile TextSummarizer summarizerToUse = null;
- private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
-
- /**
- * Run the file search and returns the SearchResults object for debugging.
- * Caching new results for access at later time.
- *
- * @param userName The name of the user performing the search.
- * @param filters The filters to apply
- * @param groupAttributeType The attribute to use for grouping
- * @param groupSortingType The method to use to sort the groups
- * @param fileSortingMethod The method to use to sort the files within the
- * groups
- * @param caseDb The case database
- * @param centralRepoDb The central repository database. Can be null if
- * not needed.
- *
- * @return The raw search results
- *
- * @throws FileSearchException
- */
- static SearchResults runFileSearchDebug(String userName,
- List filters,
- AttributeType groupAttributeType,
- FileGroup.GroupSortingAlgorithm groupSortingType,
- FileSorter.SortingMethod fileSortingMethod,
- SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
- // Make a list of attributes that we want to add values for. This ensures the
- // ResultFile objects will have all needed fields set when it's time to group
- // and sort them. For example, if we're grouping by central repo frequency, we need
- // to make sure we've loaded those values before grouping.
- List attributesNeededForGroupingOrSorting = new ArrayList<>();
- attributesNeededForGroupingOrSorting.add(groupAttributeType);
- attributesNeededForGroupingOrSorting.addAll(fileSortingMethod.getRequiredAttributes());
-
- // Run the queries for each filter
- List resultFiles = FileSearchFiltering.runQueries(filters, caseDb, centralRepoDb);
-
- // Add the data to resultFiles for any attributes needed for sorting and grouping
- addAttributes(attributesNeededForGroupingOrSorting, resultFiles, caseDb, centralRepoDb);
-
- // Collect everything in the search results
- SearchResults searchResults = new SearchResults(groupSortingType, groupAttributeType, fileSortingMethod);
- searchResults.add(resultFiles);
-
- // Sort and group the results
- searchResults.sortGroupsAndFiles();
- Map> resultHashMap = searchResults.toLinkedHashMap();
- SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
- synchronized (searchCache) {
- searchCache.put(searchKey, resultHashMap);
- }
- return searchResults;
- }
-
- /**
- * Run the file search to get the group keys and sizes. Clears cache of
- * search results, caching new results for access at later time.
- *
- * @param userName The name of the user performing the search.
- * @param filters The filters to apply
- * @param groupAttributeType The attribute to use for grouping
- * @param groupSortingType The method to use to sort the groups
- * @param fileSortingMethod The method to use to sort the files within the
- * groups
- * @param caseDb The case database
- * @param centralRepoDb The central repository database. Can be null if
- * not needed.
- *
- * @return A LinkedHashMap grouped and sorted according to the parameters
- *
- * @throws FileSearchException
- */
- static Map getGroupSizes(String userName,
- List filters,
- AttributeType groupAttributeType,
- FileGroup.GroupSortingAlgorithm groupSortingType,
- FileSorter.SortingMethod fileSortingMethod,
- SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
- Map> searchResults = runFileSearch(userName, filters,
- groupAttributeType, groupSortingType, fileSortingMethod, caseDb, centralRepoDb);
- LinkedHashMap groupSizes = new LinkedHashMap<>();
- for (GroupKey groupKey : searchResults.keySet()) {
- groupSizes.put(groupKey, searchResults.get(groupKey).size());
- }
- return groupSizes;
- }
-
- /**
- * Get the files from the specified group from the cache, if the the group
- * was not cached perform a search caching the groups.
- *
- * @param userName The name of the user performing the search.
- * @param filters The filters to apply
- * @param groupAttributeType The attribute to use for grouping
- * @param groupSortingType The method to use to sort the groups
- * @param fileSortingMethod The method to use to sort the files within the
- * groups
- * @param groupKey The key which uniquely identifies the group to
- * get entries from
- * @param startingEntry The first entry to return
- * @param numberOfEntries The number of entries to return
- * @param caseDb The case database
- * @param centralRepoDb The central repository database. Can be null if
- * not needed.
- *
- * @return A LinkedHashMap grouped and sorted according to the parameters
- *
- * @throws FileSearchException
- */
- static List getFilesInGroup(String userName,
- List filters,
- AttributeType groupAttributeType,
- FileGroup.GroupSortingAlgorithm groupSortingType,
- FileSorter.SortingMethod fileSortingMethod,
- GroupKey groupKey,
- int startingEntry,
- int numberOfEntries,
- SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
- //the group should be in the cache at this point
- List filesInGroup = null;
- SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
- Map> resultsMap;
- synchronized (searchCache) {
- resultsMap = searchCache.getIfPresent(searchKey);
- }
- if (resultsMap != null) {
- filesInGroup = resultsMap.get(groupKey);
- }
- List page = new ArrayList<>();
- if (filesInGroup == null) {
- logger.log(Level.INFO, "Group {0} was not cached, performing search to cache all groups again", groupKey);
- runFileSearch(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod, caseDb, centralRepoDb);
- synchronized (searchCache) {
- resultsMap = searchCache.getIfPresent(searchKey.getKeyString());
- }
- if (resultsMap != null) {
- filesInGroup = resultsMap.get(groupKey);
- }
- if (filesInGroup == null) {
- logger.log(Level.WARNING, "Group {0} did not exist in cache or new search results", groupKey);
- return page; //group does not exist
- }
- }
- // Check that there is data after the starting point
- if (filesInGroup.size() < startingEntry) {
- logger.log(Level.WARNING, "Group only contains {0} files, starting entry of {1} is too large.", new Object[]{filesInGroup.size(), startingEntry});
- return page;
- }
- // Add files to the page
- for (int i = startingEntry; (i < startingEntry + numberOfEntries)
- && (i < filesInGroup.size()); i++) {
- page.add(filesInGroup.get(i));
- }
- return page;
- }
-
- /**
- * Get a summary for the specified AbstractFile. If no TextSummarizers exist
- * get the beginning of the file.
- *
- * @param file The AbstractFile to summarize.
- *
- * @return The summary or beginning of the specified file as a String.
- */
- @NbBundle.Messages({"FileSearch.documentSummary.noPreview=No preview available.",
- "FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview."})
- static TextSummary summarize(AbstractFile file) {
- TextSummary summary = null;
- TextSummarizer localSummarizer = summarizerToUse;
- if (localSummarizer == null) {
- synchronized (searchCache) {
- if (localSummarizer == null) {
- localSummarizer = getLocalSummarizer();
- }
- }
- }
- if (localSummarizer != null) {
- try {
- //a summary of length 40 seems to fit without vertical scroll bars
- summary = localSummarizer.summarize(file, 40);
- } catch (IOException ex) {
- return new TextSummary(Bundle.FileSearch_documentSummary_noPreview(), null, 0);
- }
- }
- if (summary == null || StringUtils.isBlank(summary.getSummaryText())) {
- //summary text was empty grab the beginning of the file
- summary = getDefaultSummary(file);
- }
- return summary;
- }
-
- private static TextSummary getDefaultSummary(AbstractFile file) {
- Image image = null;
- int countOfImages = 0;
- try {
- Content largestChild = null;
- for (Content child : file.getChildren()) {
- if (child instanceof AbstractFile && ImageUtils.isImageThumbnailSupported((AbstractFile) child)) {
- countOfImages++;
- if (largestChild == null || child.getSize() > largestChild.getSize()) {
- largestChild = child;
- }
- }
- }
- if (largestChild != null) {
- image = ImageUtils.getThumbnail(largestChild, ImageUtils.ICON_SIZE_LARGE);
- }
- } catch (TskCoreException ex) {
- logger.log(Level.WARNING, "Error getting children for file: " + file.getId(), ex);
- }
- image = image == null ? image : image.getScaledInstance(ImageUtils.ICON_SIZE_MEDIUM, ImageUtils.ICON_SIZE_MEDIUM,
- Image.SCALE_SMOOTH);
- String summaryText = null;
- if (file.getMd5Hash() != null) {
- try {
- summaryText = getSavedSummary(Paths.get(Case.getCurrentCaseThrows().getCacheDirectory(), "summaries", file.getMd5Hash() + "-default-" + PREVIEW_SIZE + "-translated.txt").toString());
- } catch (NoCurrentCaseException ex) {
- logger.log(Level.WARNING, "Unable to retrieve saved summary. No case is open.", ex);
- }
- }
- if (StringUtils.isBlank(summaryText)) {
- String firstLines = getFirstLines(file);
- String translatedFirstLines = getTranslatedVersion(firstLines);
- if (!StringUtils.isBlank(translatedFirstLines)) {
- summaryText = translatedFirstLines;
- if (file.getMd5Hash() != null) {
- try {
- saveSummary(summaryText, Paths.get(Case.getCurrentCaseThrows().getCacheDirectory(), "summaries", file.getMd5Hash() + "-default-" + PREVIEW_SIZE + "-translated.txt").toString());
- } catch (NoCurrentCaseException ex) {
- logger.log(Level.WARNING, "Unable to save translated summary. No case is open.", ex);
- }
- }
- } else {
- summaryText = firstLines;
- }
- }
- return new TextSummary(summaryText, image, countOfImages);
- }
-
- /**
- * Provide an English version of the specified String if it is not English,
- * translation is enabled, and it can be translated.
- *
- * @param documentString The String to provide an English version of.
- *
- * @return The English version of the provided String, or null if no
- * translation occurred.
- */
- private static String getTranslatedVersion(String documentString) {
- try {
- TextTranslationService translatorInstance = TextTranslationService.getInstance();
- if (translatorInstance.hasProvider()) {
- String translatedResult = translatorInstance.translate(documentString);
- if (translatedResult.isEmpty() == false) {
- return translatedResult;
- }
- }
- } catch (NoServiceProviderException | TranslationException ex) {
- logger.log(Level.INFO, "Error translating string for summary", ex);
- }
- return null;
- }
-
- /**
- * Find and load a saved summary from the case folder for the specified
- * file.
- *
- * @param summarySavePath The full path for the saved summary file.
- *
- * @return The summary found given the specified path, null if no summary
- * was found.
- */
- private static String getSavedSummary(String summarySavePath) {
- if (summarySavePath == null) {
- return null;
- }
- File savedFile = new File(summarySavePath);
- if (savedFile.exists()) {
- try (BufferedReader bReader = new BufferedReader(new FileReader(savedFile))) {
- // pass the path to the file as a parameter
- StringBuilder sBuilder = new StringBuilder();
- String sCurrentLine = bReader.readLine();
- while (sCurrentLine != null) {
- sBuilder.append(sCurrentLine).append('\n');
- sCurrentLine = bReader.readLine();
- }
- return sBuilder.toString();
- } catch (IOException ingored) {
- //summary file may not exist or may be incomplete in which case return null so a summary can be generated
- return null; //no saved summary was able to be found
- }
- } else {
- try { //if the file didn't exist make sure the parent directories exist before we move on to creating a summary
- Files.createParentDirs(savedFile);
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to create summaries directory in case folder for file at: " + summarySavePath, ex);
- }
- return null; //no saved summary was able to be found
- }
-
- }
-
- /**
- * Save a summary at the specified location.
- *
- * @param summary The text of the summary being saved.
- * @param summarySavePath The full path for the saved summary file.
- */
- private static void saveSummary(String summary, String summarySavePath) {
- if (summarySavePath == null) {
- return; //can't save a summary if we don't have a path
- }
- try (FileWriter myWriter = new FileWriter(summarySavePath)) {
- myWriter.write(summary);
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to save summary at: " + summarySavePath, ex);
- }
- }
-
- /**
- * Get the beginning of text from the specified AbstractFile.
- *
- * @param file The AbstractFile to get text from.
- *
- * @return The beginning of text from the specified AbstractFile.
- */
- private static String getFirstLines(AbstractFile file) {
- TextExtractor extractor;
- try {
- extractor = TextExtractorFactory.getExtractor(file, null);
- } catch (TextExtractorFactory.NoTextExtractorFound ignored) {
- //no extractor found, use Strings Extractor
- extractor = TextExtractorFactory.getStringsExtractor(file, null);
- }
-
- try (Reader reader = extractor.getReader()) {
- char[] cbuf = new char[PREVIEW_SIZE];
- reader.read(cbuf, 0, PREVIEW_SIZE);
- return new String(cbuf);
- } catch (IOException ex) {
- return Bundle.FileSearch_documentSummary_noBytes();
- } catch (TextExtractor.InitReaderException ex) {
- return Bundle.FileSearch_documentSummary_noPreview();
- }
- }
-
- /**
- * Get the first TextSummarizer found by a lookup of TextSummarizers.
- *
- * @return The first TextSummarizer found by a lookup of TextSummarizers.
- *
- * @throws IOException
- */
- private static TextSummarizer getLocalSummarizer() {
- Collection extends TextSummarizer> summarizers
- = Lookup.getDefault().lookupAll(TextSummarizer.class
- );
- if (!summarizers.isEmpty()) {
- summarizerToUse = summarizers.iterator().next();
- return summarizerToUse;
- }
- return null;
- }
-
- /**
- * Run the file search. Caching new results for access at later time.
- *
- * @param userName The name of the user performing the search.
- * @param filters The filters to apply
- * @param groupAttributeType The attribute to use for grouping
- * @param groupSortingType The method to use to sort the groups
- * @param fileSortingMethod The method to use to sort the files within the
- * groups
- * @param caseDb The case database
- * @param centralRepoDb The central repository database. Can be null if
- * not needed.
- *
- * @return A LinkedHashMap grouped and sorted according to the parameters
- *
- * @throws FileSearchException
- */
- private static Map> runFileSearch(String userName,
- List filters,
- AttributeType groupAttributeType,
- FileGroup.GroupSortingAlgorithm groupSortingType,
- FileSorter.SortingMethod fileSortingMethod,
- SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
-
- // Make a list of attributes that we want to add values for. This ensures the
- // ResultFile objects will have all needed fields set when it's time to group
- // and sort them. For example, if we're grouping by central repo frequency, we need
- // to make sure we've loaded those values before grouping.
- List attributesNeededForGroupingOrSorting = new ArrayList<>();
- attributesNeededForGroupingOrSorting.add(groupAttributeType);
- attributesNeededForGroupingOrSorting.addAll(fileSortingMethod.getRequiredAttributes());
-
- // Run the queries for each filter
- List resultFiles = FileSearchFiltering.runQueries(filters, caseDb, centralRepoDb);
-
- // Add the data to resultFiles for any attributes needed for sorting and grouping
- addAttributes(attributesNeededForGroupingOrSorting, resultFiles, caseDb, centralRepoDb);
-
- // Collect everything in the search results
- SearchResults searchResults = new SearchResults(groupSortingType, groupAttributeType, fileSortingMethod);
- searchResults.add(resultFiles);
- Map> resultHashMap = searchResults.toLinkedHashMap();
- SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
- synchronized (searchCache) {
- searchCache.put(searchKey, resultHashMap);
- }
- // Return a version of the results in general Java objects
- return resultHashMap;
- }
-
- /**
- * Add any attributes corresponding to the attribute list to the given
- * result files. For example, specifying the KeywordListAttribute will
- * populate the list of keyword set names in the ResultFile objects.
- *
- * @param attrs The attributes to add to the list of result files
- * @param resultFiles The result files
- * @param caseDb The case database
- * @param centralRepoDb The central repository database. Can be null if not
- * needed.
- *
- * @throws FileSearchException
- */
- private static void addAttributes(List attrs, List resultFiles, SleuthkitCase caseDb, CentralRepository centralRepoDb)
- throws FileSearchException {
- for (AttributeType attr : attrs) {
- attr.addAttributeToResultFiles(resultFiles, caseDb, centralRepoDb);
- }
- }
-
- /**
- * Computes the CR frequency of all the given hashes and updates the list of
- * files.
- *
- * @param hashesToLookUp Hashes to find the frequency of
- * @param currentFiles List of files to update with frequencies
- */
- private static void computeFrequency(Set hashesToLookUp, List currentFiles, CentralRepository centralRepoDb) {
-
- if (hashesToLookUp.isEmpty()) {
- return;
- }
-
- String hashes = String.join("','", hashesToLookUp);
- hashes = "'" + hashes + "'";
- try {
- CorrelationAttributeInstance.Type attributeType = centralRepoDb.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
- String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType);
-
- String selectClause = " value, COUNT(value) FROM "
- + "(SELECT DISTINCT case_id, value FROM " + tableName
- + " WHERE value IN ("
- + hashes
- + ")) AS foo GROUP BY value";
-
- FrequencyCallback callback = new FrequencyCallback(currentFiles);
- centralRepoDb.processSelectClause(selectClause, callback);
-
- } catch (CentralRepoException ex) {
- logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
- }
-
- }
-
- private static String createSetNameClause(List files,
- int artifactTypeID, int setNameAttrID) throws FileSearchException {
-
- // Concatenate the object IDs in the list of files
- String objIdList = ""; // NON-NLS
- for (ResultFile file : files) {
- if (!objIdList.isEmpty()) {
- objIdList += ","; // NON-NLS
- }
- objIdList += "\'" + file.getFirstInstance().getId() + "\'"; // NON-NLS
- }
-
- // Get pairs of (object ID, set name) for all files in the list of files that have
- // the given artifact type.
- return "blackboard_artifacts.obj_id AS object_id, blackboard_attributes.value_text AS set_name "
- + "FROM blackboard_artifacts "
- + "INNER JOIN blackboard_attributes ON blackboard_artifacts.artifact_id=blackboard_attributes.artifact_id "
- + "WHERE blackboard_attributes.artifact_type_id=\'" + artifactTypeID + "\' "
- + "AND blackboard_attributes.attribute_type_id=\'" + setNameAttrID + "\' "
- + "AND blackboard_artifacts.obj_id IN (" + objIdList + ") "; // NON-NLS
- }
-
- /**
- * Get the default image to display when a thumbnail is not available.
- *
- * @return The default video thumbnail.
- */
- private static BufferedImage getDefaultVideoThumbnail() {
- try {
- return ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));//NON-NLS
- } catch (IOException ex) {
- logger.log(Level.SEVERE, "Failed to load 'failed to create video' placeholder.", ex); //NON-NLS
- }
- return null;
- }
-
- /**
- * Get the video thumbnails for a file which exists in a
- * VideoThumbnailsWrapper and update the VideoThumbnailsWrapper to include
- * them.
- *
- * @param thumbnailWrapper the object which contains the file to generate
- * thumbnails for.
- *
- */
- @NbBundle.Messages({"# {0} - file name",
- "FileSearch.genVideoThumb.progress.text=extracting temporary file {0}"})
- static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
- AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
- String cacheDirectory;
- try {
- cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
- } catch (NoCurrentCaseException ex) {
- cacheDirectory = null;
- logger.log(Level.WARNING, "Unable to get cache directory, video thumbnails will not be saved", ex);
- }
- if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
- java.io.File tempFile;
- try {
- tempFile = getVideoFileInTempDir(file);
- } catch (NoCurrentCaseException ex) {
- logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS
- int[] framePositions = new int[]{
- 0,
- 0,
- 0,
- 0};
- thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
- return;
- }
- if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
- ProgressHandle progress = ProgressHandle.createHandle(Bundle.FileSearch_genVideoThumb_progress_text(file.getName()));
- progress.start(100);
- try {
- Files.createParentDirs(tempFile);
- if (Thread.interrupted()) {
- int[] framePositions = new int[]{
- 0,
- 0,
- 0,
- 0};
- thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
- return;
- }
- ContentUtils.writeToFile(file, tempFile, progress, null, true);
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Error extracting temporary file for " + file.getParentPath() + "/" + file.getName(), ex); //NON-NLS
- } finally {
- progress.finish();
- }
- }
- VideoCapture videoFile = new VideoCapture(); // will contain the video
- BufferedImage bufferedImage = null;
-
- try {
- if (!videoFile.open(tempFile.toString())) {
- logger.log(Level.WARNING, "Error opening {0} for preview generation.", file.getParentPath() + "/" + file.getName()); //NON-NLS
- int[] framePositions = new int[]{
- 0,
- 0,
- 0,
- 0};
- thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
- return;
- }
- double fps = videoFile.get(5); // gets frame per second
- double totalFrames = videoFile.get(7); // gets total frames
- if (fps <= 0 || totalFrames <= 0) {
- logger.log(Level.WARNING, "Error getting fps or total frames for {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
- int[] framePositions = new int[]{
- 0,
- 0,
- 0,
- 0};
- thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
- return;
- }
- if (Thread.interrupted()) {
- int[] framePositions = new int[]{
- 0,
- 0,
- 0,
- 0};
- thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
- return;
- }
-
- double duration = 1000 * (totalFrames / fps); //total milliseconds
-
- int[] framePositions = new int[]{
- (int) (duration * .01),
- (int) (duration * .25),
- (int) (duration * .5),
- (int) (duration * .75),};
-
- Mat imageMatrix = new Mat();
- List videoThumbnails = new ArrayList<>();
- if (cacheDirectory == null || file.getMd5Hash() == null) {
- cacheDirectory = null;
- } else {
- try {
- FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
- } catch (IOException ex) {
- cacheDirectory = null;
- logger.log(Level.WARNING, "Unable to make video thumbnails directory, thumbnails will not be saved", ex);
- }
- }
- for (int i = 0; i < framePositions.length; i++) {
- if (!videoFile.set(0, framePositions[i])) {
- logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
- // If we can't set the time, continue to the next frame position and try again.
-
- videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
- if (cacheDirectory != null) {
- try {
- ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
- Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
- }
- }
- continue;
- }
- // Read the frame into the image/matrix.
- if (!videoFile.read(imageMatrix)) {
- logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
- // If the image is bad for some reason, continue to the next frame position and try again.
- videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
- if (cacheDirectory != null) {
- try {
- ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
- Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
- }
- }
-
- continue;
- }
- // If the image is empty, return since no buffered image can be created.
- if (imageMatrix.empty()) {
- videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
- if (cacheDirectory != null) {
- try {
- ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
- Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
- }
- }
- continue;
- }
-
- int matrixColumns = imageMatrix.cols();
- int matrixRows = imageMatrix.rows();
-
- // Convert the matrix that contains the frame to a buffered image.
- if (bufferedImage == null) {
- bufferedImage = new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
- }
-
- byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
- imageMatrix.get(0, 0, data); //copy the image to data
-
- if (imageMatrix.channels() == 3) {
- for (int k = 0; k < data.length; k += 3) {
- byte temp = data[k];
- data[k] = data[k + 2];
- data[k + 2] = temp;
- }
- }
-
- bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
- if (Thread.interrupted()) {
- thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
- try {
- FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to delete directory for cancelled video thumbnail process", ex);
- }
- return;
- }
- BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
- //We are height limited here so it can be wider than it can be tall.Scalr maintains the aspect ratio.
- videoThumbnails.add(thumbnail);
- if (cacheDirectory != null) {
- try {
- ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
- Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
- } catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to save video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
- }
- }
- }
- thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
- } finally {
- videoFile.release(); // close the file}
- }
- } else {
- loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
- }
- }
-
- /**
- * Load the thumbnails that exist in the cache directory for the specified
- * video file.
- *
- * @param cacheDirectory The directory which exists for the video
- * thumbnails.
- * @param thumbnailWrapper The VideoThumbnailWrapper object which contains
- * information about the file and the thumbnails
- * associated with it.
- */
- private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
- int[] framePositions = new int[4];
- List videoThumbnails = new ArrayList<>();
- int thumbnailNumber = 0;
- String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
- for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
- try {
- videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
- } catch (IOException ex) {
- videoThumbnails.add(failedVideoThumbImage);
- logger.log(Level.WARNING, "Unable to read saved video thumbnail " + fileName + " for " + md5, ex);
- }
- int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
- framePositions[thumbnailNumber] = framePos;
- thumbnailNumber++;
- }
- thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
- }
-
- /**
- * Private helper method for creating video thumbnails, for use when no
- * thumbnails are created.
- *
- * @return List containing the default thumbnail.
- */
- private static List createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
- List videoThumbnails = new ArrayList<>();
- videoThumbnails.add(failedVideoThumbImage);
- videoThumbnails.add(failedVideoThumbImage);
- videoThumbnails.add(failedVideoThumbImage);
- videoThumbnails.add(failedVideoThumbImage);
- return videoThumbnails;
- }
-
- private FileSearch() {
- // Class should not be instantiated
- }
-
- /**
- * Base class for the grouping attributes.
- */
- abstract static class AttributeType {
-
- /**
- * For a given file, return the key for the group it belongs to for this
- * attribute type.
- *
- * @param file the result file to be grouped
- *
- * @return the key for the group this file goes in
- */
- abstract GroupKey getGroupKey(ResultFile file);
-
- /**
- * Add any extra data to the ResultFile object from this attribute.
- *
- * @param files The list of files to enhance
- * @param caseDb The case database
- * @param centralRepoDb The central repository database. Can be null if
- * not needed.
- *
- * @throws FileSearchException
- */
- void addAttributeToResultFiles(List files, SleuthkitCase caseDb, CentralRepository centralRepoDb) throws FileSearchException {
- // Default is to do nothing
- }
- }
-
- /**
- * The key used for grouping for each attribute type.
- */
- abstract static class GroupKey implements Comparable {
-
- /**
- * Get the string version of the group key for display. Each display
- * name should correspond to a unique GroupKey object.
- *
- * @return The display name for this key
- */
- abstract String getDisplayName();
-
- /**
- * Subclasses must implement equals().
- *
- * @param otherKey
- *
- * @return true if the keys are equal, false otherwise
- */
- @Override
- abstract public boolean equals(Object otherKey);
-
- /**
- * Subclasses must implement hashCode().
- *
- * @return the hash code
- */
- @Override
- abstract public int hashCode();
-
- /**
- * It should not happen with the current setup, but we need to cover the
- * case where two different GroupKey subclasses are compared against
- * each other. Use a lexicographic comparison on the class names.
- *
- * @param otherGroupKey The other group key
- *
- * @return result of alphabetical comparison on the class name
- */
- int compareClassNames(GroupKey otherGroupKey) {
- return this.getClass().getName().compareTo(otherGroupKey.getClass().getName());
- }
-
- @Override
- public String toString() {
- return getDisplayName();
- }
- }
-
- /**
- * Attribute for grouping/sorting by file size
- */
- static class FileSizeAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new FileSizeGroupKey(file);
- }
- }
-
- /**
- * Key representing a file size group
- */
- private static class FileSizeGroupKey extends GroupKey {
-
- private final FileSize fileSize;
-
- FileSizeGroupKey(ResultFile file) {
- if (file.getFileType() == FileType.VIDEO) {
- fileSize = FileSize.fromVideoSize(file.getFirstInstance().getSize());
- } else {
- fileSize = FileSize.fromImageSize(file.getFirstInstance().getSize());
- }
- }
-
- @Override
- String getDisplayName() {
- return getFileSize().toString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof FileSizeGroupKey) {
- FileSizeGroupKey otherFileSizeGroupKey = (FileSizeGroupKey) otherGroupKey;
- return Integer.compare(getFileSize().getRanking(), otherFileSizeGroupKey.getFileSize().getRanking());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof FileSizeGroupKey)) {
- return false;
- }
-
- FileSizeGroupKey otherFileSizeGroupKey = (FileSizeGroupKey) otherKey;
- return getFileSize().equals(otherFileSizeGroupKey.getFileSize());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getFileSize().getRanking());
- }
-
- /**
- * @return the fileSize
- */
- FileSize getFileSize() {
- return fileSize;
- }
- }
-
- /**
- * Attribute for grouping/sorting by parent path
- */
- static class ParentPathAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new ParentPathGroupKey(file);
- }
- }
-
- /**
- * Key representing a parent path group
- */
- private static class ParentPathGroupKey extends GroupKey {
-
- private String parentPath;
- private Long parentID;
-
- ParentPathGroupKey(ResultFile file) {
- Content parent;
- try {
- parent = file.getFirstInstance().getParent();
- } catch (TskCoreException ignored) {
- parent = null;
- }
- //Find the directory this file is in if it is an embedded file
- while (parent != null && parent instanceof AbstractFile && ((AbstractFile) parent).isFile()) {
- try {
- parent = parent.getParent();
- } catch (TskCoreException ignored) {
- parent = null;
- }
- }
- setParentPathAndID(parent, file);
- }
-
- /**
- * Helper method to set the parent path and parent ID.
- *
- * @param parent The parent content object.
- * @param file The ResultFile object.
- */
- private void setParentPathAndID(Content parent, ResultFile file) {
- if (parent != null) {
- try {
- parentPath = parent.getUniquePath();
- parentID = parent.getId();
- } catch (TskCoreException ignored) {
- //catch block left blank purposefully next if statement will handle case when exception takes place as well as when parent is null
- }
-
- }
- if (parentPath == null) {
- if (file.getFirstInstance().getParentPath() != null) {
- parentPath = file.getFirstInstance().getParentPath();
- } else {
- parentPath = ""; // NON-NLS
- }
- parentID = -1L;
- }
- }
-
- @Override
- String getDisplayName() {
- return getParentPath();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof ParentPathGroupKey) {
- ParentPathGroupKey otherParentPathGroupKey = (ParentPathGroupKey) otherGroupKey;
- int comparisonResult = getParentPath().compareTo(otherParentPathGroupKey.getParentPath());
- if (comparisonResult == 0) {
- comparisonResult = getParentID().compareTo(otherParentPathGroupKey.getParentID());
- }
- return comparisonResult;
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof ParentPathGroupKey)) {
- return false;
- }
-
- ParentPathGroupKey otherParentPathGroupKey = (ParentPathGroupKey) otherKey;
- return getParentPath().equals(otherParentPathGroupKey.getParentPath()) && getParentID().equals(otherParentPathGroupKey.getParentID());
- }
-
- @Override
- public int hashCode() {
- int hashCode = 11;
- hashCode = 61 * hashCode + Objects.hash(getParentPath());
- hashCode = 61 * hashCode + Objects.hash(getParentID());
- return hashCode;
- }
-
- /**
- * @return the parentPath
- */
- String getParentPath() {
- return parentPath;
- }
-
- /**
- * @return the parentID
- */
- Long getParentID() {
- return parentID;
- }
- }
-
- /**
- * Attribute for grouping/sorting by data source
- */
- static class DataSourceAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new DataSourceGroupKey(file);
- }
- }
-
- /**
- * Key representing a data source group
- */
- private static class DataSourceGroupKey extends GroupKey {
-
- private final long dataSourceID;
- private String displayName;
-
- @NbBundle.Messages({
- "# {0} - Data source name",
- "# {1} - Data source ID",
- "FileSearch.DataSourceGroupKey.datasourceAndID={0}(ID: {1})",
- "# {0} - Data source ID",
- "FileSearch.DataSourceGroupKey.idOnly=Data source (ID: {0})"})
- DataSourceGroupKey(ResultFile file) {
- dataSourceID = file.getFirstInstance().getDataSourceObjectId();
-
- try {
- // The data source should be cached so this won't actually be a database query.
- Content ds = file.getFirstInstance().getDataSource();
- displayName = Bundle.FileSearch_DataSourceGroupKey_datasourceAndID(ds.getName(), ds.getId());
- } catch (TskCoreException ex) {
- logger.log(Level.WARNING, "Error looking up data source with ID " + dataSourceID, ex); // NON-NLS
- displayName = Bundle.FileSearch_DataSourceGroupKey_idOnly(dataSourceID);
- }
- }
-
- @Override
- String getDisplayName() {
- return displayName;
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof DataSourceGroupKey) {
- DataSourceGroupKey otherDataSourceGroupKey = (DataSourceGroupKey) otherGroupKey;
- return Long.compare(getDataSourceID(), otherDataSourceGroupKey.getDataSourceID());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof DataSourceGroupKey)) {
- return false;
- }
-
- DataSourceGroupKey otherDataSourceGroupKey = (DataSourceGroupKey) otherKey;
- return getDataSourceID() == otherDataSourceGroupKey.getDataSourceID();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getDataSourceID());
- }
-
- /**
- * @return the dataSourceID
- */
- long getDataSourceID() {
- return dataSourceID;
- }
- }
-
- /**
- * Attribute for grouping/sorting by file type
- */
- static class FileTypeAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new FileTypeGroupKey(file);
- }
- }
-
- /**
- * Key representing a file type group
- */
- private static class FileTypeGroupKey extends GroupKey {
-
- private final FileType fileType;
-
- FileTypeGroupKey(ResultFile file) {
- fileType = file.getFileType();
- }
-
- @Override
- String getDisplayName() {
- return getFileType().toString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof FileTypeGroupKey) {
- FileTypeGroupKey otherFileTypeGroupKey = (FileTypeGroupKey) otherGroupKey;
- return Integer.compare(getFileType().getRanking(), otherFileTypeGroupKey.getFileType().getRanking());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof FileTypeGroupKey)) {
- return false;
- }
-
- FileTypeGroupKey otherFileTypeGroupKey = (FileTypeGroupKey) otherKey;
- return getFileType().equals(otherFileTypeGroupKey.getFileType());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getFileType().getRanking());
- }
-
- /**
- * @return the fileType
- */
- FileType getFileType() {
- return fileType;
- }
- }
-
- /**
- * Attribute for grouping/sorting by keyword lists
- */
- static class KeywordListAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new KeywordListGroupKey(file);
- }
-
- @Override
- void addAttributeToResultFiles(List files, SleuthkitCase caseDb,
- CentralRepository centralRepoDb) throws FileSearchException {
-
- // Get pairs of (object ID, keyword list name) for all files in the list of files that have
- // keyword list hits.
- String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(),
- BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
-
- SetKeywordListNamesCallback callback = new SetKeywordListNamesCallback(files);
- try {
- caseDb.getCaseDbAccessManager().select(selectQuery, callback);
- } catch (TskCoreException ex) {
- throw new FileSearchException("Error looking up keyword list attributes", ex); // NON-NLS
- }
- }
-
- /**
- * Callback to process the results of the CaseDbAccessManager select
- * query. Will add the keyword list names to the list of ResultFile
- * objects.
- */
- private static class SetKeywordListNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
-
- List resultFiles;
-
- /**
- * Create the callback.
- *
- * @param resultFiles List of files to add keyword list names to
- */
- SetKeywordListNamesCallback(List resultFiles) {
- this.resultFiles = resultFiles;
- }
-
- @Override
- public void process(ResultSet rs) {
- try {
- // Create a temporary map of object ID to ResultFile
- Map tempMap = new HashMap<>();
- for (ResultFile file : resultFiles) {
- tempMap.put(file.getFirstInstance().getId(), file);
- }
-
- while (rs.next()) {
- try {
- Long objId = rs.getLong("object_id"); // NON-NLS
- String keywordListName = rs.getString("set_name"); // NON-NLS
-
- tempMap.get(objId).addKeywordListName(keywordListName);
-
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
- }
- }
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Failed to get keyword list names", ex); // NON-NLS
- }
- }
- }
- }
-
- /**
- * Key representing a keyword list group
- */
- private static class KeywordListGroupKey extends GroupKey {
-
- private final List keywordListNames;
- private final String keywordListNamesString;
-
- @NbBundle.Messages({
- "FileSearch.KeywordListGroupKey.noKeywords=None"})
- KeywordListGroupKey(ResultFile file) {
- keywordListNames = file.getKeywordListNames();
-
- if (keywordListNames.isEmpty()) {
- keywordListNamesString = Bundle.FileSearch_KeywordListGroupKey_noKeywords();
- } else {
- keywordListNamesString = String.join(",", keywordListNames); // NON-NLS
- }
- }
-
- @Override
- String getDisplayName() {
- return getKeywordListNamesString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof KeywordListGroupKey) {
- KeywordListGroupKey otherKeywordListNamesGroupKey = (KeywordListGroupKey) otherGroupKey;
-
- // Put the empty list at the end
- if (getKeywordListNames().isEmpty()) {
- if (otherKeywordListNamesGroupKey.getKeywordListNames().isEmpty()) {
- return 0;
- } else {
- return 1;
- }
- } else if (otherKeywordListNamesGroupKey.getKeywordListNames().isEmpty()) {
- return -1;
- }
-
- return getKeywordListNamesString().compareTo(otherKeywordListNamesGroupKey.getKeywordListNamesString());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof KeywordListGroupKey)) {
- return false;
- }
-
- KeywordListGroupKey otherKeywordListGroupKey = (KeywordListGroupKey) otherKey;
- return getKeywordListNamesString().equals(otherKeywordListGroupKey.getKeywordListNamesString());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getKeywordListNamesString());
- }
-
- /**
- * @return the keywordListNames
- */
- List getKeywordListNames() {
- return Collections.unmodifiableList(keywordListNames);
- }
-
- /**
- * @return the keywordListNamesString
- */
- String getKeywordListNamesString() {
- return keywordListNamesString;
- }
- }
-
- /**
- * Attribute for grouping/sorting by frequency in the central repository
- */
- static class FrequencyAttribute extends AttributeType {
-
- static final int BATCH_SIZE = 50; // Number of hashes to look up at one time
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new FrequencyGroupKey(file);
- }
-
- @Override
- void addAttributeToResultFiles(List files, SleuthkitCase caseDb,
- CentralRepository centralRepoDb) throws FileSearchException {
- if (centralRepoDb == null) {
- for (ResultFile file : files) {
- if (file.getFrequency() == Frequency.UNKNOWN && file.getFirstInstance().getKnown() == TskData.FileKnown.KNOWN) {
- file.setFrequency(Frequency.KNOWN);
- }
- }
- } else {
- processResultFilesForCR(files, centralRepoDb);
- }
- }
-
- /**
- * Private helper method for adding Frequency attribute when CR is
- * enabled.
- *
- * @param files The list of ResultFiles to caluclate frequency
- * for.
- * @param centralRepoDb The central repository currently in use.
- */
- private void processResultFilesForCR(List files,
- CentralRepository centralRepoDb) {
- List currentFiles = new ArrayList<>();
- Set hashesToLookUp = new HashSet<>();
- for (ResultFile file : files) {
- if (file.getFirstInstance().getKnown() == TskData.FileKnown.KNOWN) {
- file.setFrequency(Frequency.KNOWN);
- }
- if (file.getFrequency() == Frequency.UNKNOWN
- && file.getFirstInstance().getMd5Hash() != null
- && !file.getFirstInstance().getMd5Hash().isEmpty()) {
- hashesToLookUp.add(file.getFirstInstance().getMd5Hash());
- currentFiles.add(file);
- }
- if (hashesToLookUp.size() >= BATCH_SIZE) {
- computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
-
- hashesToLookUp.clear();
- currentFiles.clear();
- }
- }
- computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
- }
- }
-
- /**
- * Callback to use with findInterCaseValuesByCount which generates a list of
- * values for common property search
- */
- private static class FrequencyCallback implements InstanceTableCallback {
-
- private final List files;
-
- private FrequencyCallback(List files) {
- this.files = new ArrayList<>(files);
- }
-
- @Override
- public void process(ResultSet resultSet) {
- try {
-
- while (resultSet.next()) {
- String hash = resultSet.getString(1);
- int count = resultSet.getInt(2);
- for (Iterator iterator = files.iterator(); iterator.hasNext();) {
- ResultFile file = iterator.next();
- if (file.getFirstInstance().getMd5Hash().equalsIgnoreCase(hash)) {
- file.setFrequency(Frequency.fromCount(count));
- iterator.remove();
- }
- }
- }
-
- // The files left had no matching entries in the CR, so mark them as unique
- for (ResultFile file : files) {
- file.setFrequency(Frequency.UNIQUE);
- }
- } catch (SQLException ex) {
- logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
- }
- }
- }
-
- /**
- * Key representing a central repository frequency group
- */
- private static class FrequencyGroupKey extends GroupKey {
-
- private final Frequency frequency;
-
- FrequencyGroupKey(ResultFile file) {
- frequency = file.getFrequency();
- }
-
- @Override
- String getDisplayName() {
- return getFrequency().toString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof FrequencyGroupKey) {
- FrequencyGroupKey otherFrequencyGroupKey = (FrequencyGroupKey) otherGroupKey;
- return Integer.compare(getFrequency().getRanking(), otherFrequencyGroupKey.getFrequency().getRanking());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof FrequencyGroupKey)) {
- return false;
- }
-
- FrequencyGroupKey otherFrequencyGroupKey = (FrequencyGroupKey) otherKey;
- return getFrequency().equals(otherFrequencyGroupKey.getFrequency());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getFrequency().getRanking());
- }
-
- /**
- * @return the frequency
- */
- Frequency getFrequency() {
- return frequency;
- }
- }
-
- /**
- * Attribute for grouping/sorting by hash set lists
- */
- static class HashHitsAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new HashHitsGroupKey(file);
- }
-
- @Override
- void addAttributeToResultFiles(List files, SleuthkitCase caseDb,
- CentralRepository centralRepoDb) throws FileSearchException {
-
- // Get pairs of (object ID, hash set name) for all files in the list of files that have
- // hash set hits.
- String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID(),
- BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
-
- HashSetNamesCallback callback = new HashSetNamesCallback(files);
- try {
- caseDb.getCaseDbAccessManager().select(selectQuery, callback);
- } catch (TskCoreException ex) {
- throw new FileSearchException("Error looking up hash set attributes", ex); // NON-NLS
- }
- }
-
- /**
- * Callback to process the results of the CaseDbAccessManager select
- * query. Will add the hash set names to the list of ResultFile objects.
- */
- private static class HashSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
-
- List resultFiles;
-
- /**
- * Create the callback.
- *
- * @param resultFiles List of files to add hash set names to
- */
- HashSetNamesCallback(List resultFiles) {
- this.resultFiles = resultFiles;
- }
-
- @Override
- public void process(ResultSet rs) {
- try {
- // Create a temporary map of object ID to ResultFile
- Map tempMap = new HashMap<>();
- for (ResultFile file : resultFiles) {
- tempMap.put(file.getFirstInstance().getId(), file);
- }
-
- while (rs.next()) {
- try {
- Long objId = rs.getLong("object_id"); // NON-NLS
- String hashSetName = rs.getString("set_name"); // NON-NLS
-
- tempMap.get(objId).addHashSetName(hashSetName);
-
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
- }
- }
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Failed to get hash set names", ex); // NON-NLS
- }
- }
- }
- }
-
- /**
- * Key representing a hash hits group
- */
- private static class HashHitsGroupKey extends GroupKey {
-
- private final List hashSetNames;
- private final String hashSetNamesString;
-
- @NbBundle.Messages({
- "FileSearch.HashHitsGroupKey.noHashHits=None"})
- HashHitsGroupKey(ResultFile file) {
- hashSetNames = file.getHashSetNames();
-
- if (hashSetNames.isEmpty()) {
- hashSetNamesString = Bundle.FileSearch_HashHitsGroupKey_noHashHits();
- } else {
- hashSetNamesString = String.join(",", hashSetNames); // NON-NLS
- }
- }
-
- @Override
- String getDisplayName() {
- return getHashSetNamesString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof HashHitsGroupKey) {
- HashHitsGroupKey otherHashHitsGroupKey = (HashHitsGroupKey) otherGroupKey;
-
- // Put the empty list at the end
- if (getHashSetNames().isEmpty()) {
- if (otherHashHitsGroupKey.getHashSetNames().isEmpty()) {
- return 0;
- } else {
- return 1;
- }
- } else if (otherHashHitsGroupKey.getHashSetNames().isEmpty()) {
- return -1;
- }
-
- return getHashSetNamesString().compareTo(otherHashHitsGroupKey.getHashSetNamesString());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof HashHitsGroupKey)) {
- return false;
- }
-
- HashHitsGroupKey otherHashHitsGroupKey = (HashHitsGroupKey) otherKey;
- return getHashSetNamesString().equals(otherHashHitsGroupKey.getHashSetNamesString());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getHashSetNamesString());
- }
-
- /**
- * @return the hashSetNames
- */
- List getHashSetNames() {
- return Collections.unmodifiableList(hashSetNames);
- }
-
- /**
- * @return the hashSetNamesString
- */
- String getHashSetNamesString() {
- return hashSetNamesString;
- }
- }
-
- /**
- * Attribute for grouping/sorting by interesting item set lists
- */
- static class InterestingItemAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new InterestingItemGroupKey(file);
- }
-
- @Override
- void addAttributeToResultFiles(List files, SleuthkitCase caseDb,
- CentralRepository centralRepoDb) throws FileSearchException {
-
- // Get pairs of (object ID, interesting item set name) for all files in the list of files that have
- // interesting file set hits.
- String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),
- BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
-
- InterestingFileSetNamesCallback callback = new InterestingFileSetNamesCallback(files);
- try {
- caseDb.getCaseDbAccessManager().select(selectQuery, callback);
- } catch (TskCoreException ex) {
- throw new FileSearchException("Error looking up interesting file set attributes", ex); // NON-NLS
- }
- }
-
- /**
- * Callback to process the results of the CaseDbAccessManager select
- * query. Will add the interesting file set names to the list of
- * ResultFile objects.
- */
- private static class InterestingFileSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
-
- List resultFiles;
-
- /**
- * Create the callback.
- *
- * @param resultFiles List of files to add interesting file set
- * names to
- */
- InterestingFileSetNamesCallback(List resultFiles) {
- this.resultFiles = resultFiles;
- }
-
- @Override
- public void process(ResultSet rs) {
- try {
- // Create a temporary map of object ID to ResultFile
- Map tempMap = new HashMap<>();
- for (ResultFile file : resultFiles) {
- tempMap.put(file.getFirstInstance().getId(), file);
- }
-
- while (rs.next()) {
- try {
- Long objId = rs.getLong("object_id"); // NON-NLS
- String setName = rs.getString("set_name"); // NON-NLS
-
- tempMap.get(objId).addInterestingSetName(setName);
-
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
- }
- }
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Failed to get interesting file set names", ex); // NON-NLS
- }
- }
- }
- }
-
- /**
- * Key representing a interesting item set group
- */
- private static class InterestingItemGroupKey extends GroupKey {
-
- private final List interestingItemSetNames;
- private final String interestingItemSetNamesString;
-
- @NbBundle.Messages({
- "FileSearch.InterestingItemGroupKey.noSets=None"})
- InterestingItemGroupKey(ResultFile file) {
- interestingItemSetNames = file.getInterestingSetNames();
-
- if (interestingItemSetNames.isEmpty()) {
- interestingItemSetNamesString = Bundle.FileSearch_InterestingItemGroupKey_noSets();
- } else {
- interestingItemSetNamesString = String.join(",", interestingItemSetNames); // NON-NLS
- }
- }
-
- @Override
- String getDisplayName() {
- return getInterestingItemSetNamesString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof InterestingItemGroupKey) {
- InterestingItemGroupKey otherInterestingItemGroupKey = (InterestingItemGroupKey) otherGroupKey;
-
- // Put the empty list at the end
- if (this.getInterestingItemSetNames().isEmpty()) {
- if (otherInterestingItemGroupKey.getInterestingItemSetNames().isEmpty()) {
- return 0;
- } else {
- return 1;
- }
- } else if (otherInterestingItemGroupKey.getInterestingItemSetNames().isEmpty()) {
- return -1;
- }
-
- return getInterestingItemSetNamesString().compareTo(otherInterestingItemGroupKey.getInterestingItemSetNamesString());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof InterestingItemGroupKey)) {
- return false;
- }
-
- InterestingItemGroupKey otherInterestingItemGroupKey = (InterestingItemGroupKey) otherKey;
- return getInterestingItemSetNamesString().equals(otherInterestingItemGroupKey.getInterestingItemSetNamesString());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getInterestingItemSetNamesString());
- }
-
- /**
- * @return the interestingItemSetNames
- */
- List getInterestingItemSetNames() {
- return Collections.unmodifiableList(interestingItemSetNames);
- }
-
- /**
- * @return the interestingItemSetNamesString
- */
- String getInterestingItemSetNamesString() {
- return interestingItemSetNamesString;
- }
- }
-
- /**
- * Attribute for grouping/sorting by objects detected
- */
- static class ObjectDetectedAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new ObjectDetectedGroupKey(file);
- }
-
- @Override
- void addAttributeToResultFiles(List files, SleuthkitCase caseDb,
- CentralRepository centralRepoDb) throws FileSearchException {
-
- // Get pairs of (object ID, object type name) for all files in the list of files that have
- // objects detected
- String selectQuery = createSetNameClause(files, BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID(),
- BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID());
-
- ObjectDetectedNamesCallback callback = new ObjectDetectedNamesCallback(files);
- try {
- caseDb.getCaseDbAccessManager().select(selectQuery, callback);
- } catch (TskCoreException ex) {
- throw new FileSearchException("Error looking up object detected attributes", ex); // NON-NLS
- }
- }
-
- /**
- * Callback to process the results of the CaseDbAccessManager select
- * query. Will add the object type names to the list of ResultFile
- * objects.
- */
- private static class ObjectDetectedNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
-
- List resultFiles;
-
- /**
- * Create the callback.
- *
- * @param resultFiles List of files to add object detected names to
- */
- ObjectDetectedNamesCallback(List resultFiles) {
- this.resultFiles = resultFiles;
- }
-
- @Override
- public void process(ResultSet rs) {
- try {
- // Create a temporary map of object ID to ResultFile
- Map tempMap = new HashMap<>();
- for (ResultFile file : resultFiles) {
- tempMap.put(file.getFirstInstance().getId(), file);
- }
-
- while (rs.next()) {
- try {
- Long objId = rs.getLong("object_id"); // NON-NLS
- String setName = rs.getString("set_name"); // NON-NLS
-
- tempMap.get(objId).addObjectDetectedName(setName);
-
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
- }
- }
- } catch (SQLException ex) {
- logger.log(Level.SEVERE, "Failed to get object detected names", ex); // NON-NLS
- }
- }
- }
- }
-
- /**
- * Key representing an object detected group
- */
- private static class ObjectDetectedGroupKey extends GroupKey {
-
- private final List objectDetectedNames;
- private final String objectDetectedNamesString;
-
- @NbBundle.Messages({
- "FileSearch.ObjectDetectedGroupKey.noSets=None"})
- ObjectDetectedGroupKey(ResultFile file) {
- objectDetectedNames = file.getObjectDetectedNames();
-
- if (objectDetectedNames.isEmpty()) {
- objectDetectedNamesString = Bundle.FileSearch_ObjectDetectedGroupKey_noSets();
- } else {
- objectDetectedNamesString = String.join(",", objectDetectedNames); // NON-NLS
- }
- }
-
- @Override
- String getDisplayName() {
- return getObjectDetectedNamesString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof ObjectDetectedGroupKey) {
- ObjectDetectedGroupKey otherObjectDetectedGroupKey = (ObjectDetectedGroupKey) otherGroupKey;
-
- // Put the empty list at the end
- if (this.getObjectDetectedNames().isEmpty()) {
- if (otherObjectDetectedGroupKey.getObjectDetectedNames().isEmpty()) {
- return 0;
- } else {
- return 1;
- }
- } else if (otherObjectDetectedGroupKey.getObjectDetectedNames().isEmpty()) {
- return -1;
- }
-
- return getObjectDetectedNamesString().compareTo(otherObjectDetectedGroupKey.getObjectDetectedNamesString());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof ObjectDetectedGroupKey)) {
- return false;
- }
-
- ObjectDetectedGroupKey otherObjectDetectedGroupKey = (ObjectDetectedGroupKey) otherKey;
- return getObjectDetectedNamesString().equals(otherObjectDetectedGroupKey.getObjectDetectedNamesString());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getObjectDetectedNamesString());
- }
-
- /**
- * @return the objectDetectedNames
- */
- List getObjectDetectedNames() {
- return Collections.unmodifiableList(objectDetectedNames);
- }
-
- /**
- * @return the objectDetectedNamesString
- */
- String getObjectDetectedNamesString() {
- return objectDetectedNamesString;
- }
- }
-
- /**
- * Attribute for grouping/sorting by tag name
- */
- static class FileTagAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new FileTagGroupKey(file);
- }
-
- @Override
- void addAttributeToResultFiles(List files, SleuthkitCase caseDb,
- CentralRepository centralRepoDb) throws FileSearchException {
-
- try {
- for (ResultFile resultFile : files) {
- List contentTags = caseDb.getContentTagsByContent(resultFile.getFirstInstance());
-
- for (ContentTag tag : contentTags) {
- resultFile.addTagName(tag.getName().getDisplayName());
- }
- }
- } catch (TskCoreException ex) {
- throw new FileSearchException("Error looking up file tag attributes", ex); // NON-NLS
- }
- }
- }
-
- /**
- * Represents a key for a specific search for a specific user.
- */
- private static class SearchKey implements Comparable {
-
- private final String keyString;
-
- /**
- * Construct a new SearchKey with all information that defines a search.
- *
- * @param userName The name of the user performing the search.
- * @param filters The FileFilters being used for the search.
- * @param groupAttributeType The AttributeType to group by.
- * @param groupSortingType The algorithm to sort the groups by.
- * @param fileSortingMethod The method to sort the files by.
- */
- SearchKey(String userName, List filters,
- AttributeType groupAttributeType,
- FileGroup.GroupSortingAlgorithm groupSortingType,
- FileSorter.SortingMethod fileSortingMethod) {
- StringBuilder searchStringBuilder = new StringBuilder();
- searchStringBuilder.append(userName);
- for (FileSearchFiltering.FileFilter filter : filters) {
- searchStringBuilder.append(filter.toString());
- }
- searchStringBuilder.append(groupAttributeType).append(groupSortingType).append(fileSortingMethod);
- keyString = searchStringBuilder.toString();
- }
-
- @Override
- public int compareTo(SearchKey otherSearchKey) {
- return getKeyString().compareTo(otherSearchKey.getKeyString());
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof SearchKey)) {
- return false;
- }
-
- SearchKey otherSearchKey = (SearchKey) otherKey;
- return getKeyString().equals(otherSearchKey.getKeyString());
- }
-
- @Override
- public int hashCode() {
- int hash = 5;
- hash = 79 * hash + Objects.hashCode(getKeyString());
- return hash;
- }
-
- /**
- * @return the keyString
- */
- String getKeyString() {
- return keyString;
- }
- }
-
- /**
- * Key representing a file tag group
- */
- private static class FileTagGroupKey extends GroupKey {
-
- private final List tagNames;
- private final String tagNamesString;
-
- @NbBundle.Messages({
- "FileSearch.FileTagGroupKey.noSets=None"})
- FileTagGroupKey(ResultFile file) {
- tagNames = file.getTagNames();
-
- if (tagNames.isEmpty()) {
- tagNamesString = Bundle.FileSearch_FileTagGroupKey_noSets();
- } else {
- tagNamesString = String.join(",", tagNames); // NON-NLS
- }
- }
-
- @Override
- String getDisplayName() {
- return getTagNamesString();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- if (otherGroupKey instanceof FileTagGroupKey) {
- FileTagGroupKey otherFileTagGroupKey = (FileTagGroupKey) otherGroupKey;
-
- // Put the empty list at the end
- if (getTagNames().isEmpty()) {
- if (otherFileTagGroupKey.getTagNames().isEmpty()) {
- return 0;
- } else {
- return 1;
- }
- } else if (otherFileTagGroupKey.getTagNames().isEmpty()) {
- return -1;
- }
-
- return getTagNamesString().compareTo(otherFileTagGroupKey.getTagNamesString());
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
-
- if (!(otherKey instanceof FileTagGroupKey)) {
- return false;
- }
-
- FileTagGroupKey otherFileTagGroupKey = (FileTagGroupKey) otherKey;
- return getTagNamesString().equals(otherFileTagGroupKey.getTagNamesString());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getTagNamesString());
- }
-
- /**
- * @return the tagNames
- */
- List getTagNames() {
- return Collections.unmodifiableList(tagNames);
- }
-
- /**
- * @return the tagNamesString
- */
- String getTagNamesString() {
- return tagNamesString;
- }
- }
-
- /**
- * Default attribute used to make one group
- */
- static class NoGroupingAttribute extends AttributeType {
-
- @Override
- GroupKey getGroupKey(ResultFile file) {
- return new NoGroupingGroupKey();
- }
- }
-
- /**
- * Dummy key for when there is no grouping. All files will have the same
- * key.
- */
- private static class NoGroupingGroupKey extends GroupKey {
-
- NoGroupingGroupKey() {
- // Nothing to save - all files will get the same GroupKey
- }
-
- @NbBundle.Messages({
- "FileSearch.NoGroupingGroupKey.allFiles=All Files"})
- @Override
- String getDisplayName() {
- return Bundle.FileSearch_NoGroupingGroupKey_allFiles();
- }
-
- @Override
- public int compareTo(GroupKey otherGroupKey) {
- // As long as the other key is the same type, they are equal
- if (otherGroupKey instanceof NoGroupingGroupKey) {
- return 0;
- } else {
- return compareClassNames(otherGroupKey);
- }
- }
-
- @Override
- public boolean equals(Object otherKey) {
- if (otherKey == this) {
- return true;
- }
- // As long as the other key is the same type, they are equal
- return otherKey instanceof NoGroupingGroupKey;
- }
-
- @Override
- public int hashCode() {
- return 0;
- }
- }
-
- /**
- * Enum for the attribute types that can be used for grouping.
- */
- @NbBundle.Messages({
- "FileSearch.GroupingAttributeType.fileType.displayName=File Type",
- "FileSearch.GroupingAttributeType.frequency.displayName=Past Occurrences",
- "FileSearch.GroupingAttributeType.keywordList.displayName=Keyword",
- "FileSearch.GroupingAttributeType.size.displayName=File Size",
- "FileSearch.GroupingAttributeType.datasource.displayName=Data Source",
- "FileSearch.GroupingAttributeType.parent.displayName=Parent Folder",
- "FileSearch.GroupingAttributeType.hash.displayName=Hash Set",
- "FileSearch.GroupingAttributeType.interestingItem.displayName=Interesting Item",
- "FileSearch.GroupingAttributeType.tag.displayName=Tag",
- "FileSearch.GroupingAttributeType.object.displayName=Object Detected",
- "FileSearch.GroupingAttributeType.none.displayName=None"})
- enum GroupingAttributeType {
- FILE_SIZE(new FileSizeAttribute(), Bundle.FileSearch_GroupingAttributeType_size_displayName()),
- FREQUENCY(new FrequencyAttribute(), Bundle.FileSearch_GroupingAttributeType_frequency_displayName()),
- KEYWORD_LIST_NAME(new KeywordListAttribute(), Bundle.FileSearch_GroupingAttributeType_keywordList_displayName()),
- DATA_SOURCE(new DataSourceAttribute(), Bundle.FileSearch_GroupingAttributeType_datasource_displayName()),
- PARENT_PATH(new ParentPathAttribute(), Bundle.FileSearch_GroupingAttributeType_parent_displayName()),
- HASH_LIST_NAME(new HashHitsAttribute(), Bundle.FileSearch_GroupingAttributeType_hash_displayName()),
- INTERESTING_ITEM_SET(new InterestingItemAttribute(), Bundle.FileSearch_GroupingAttributeType_interestingItem_displayName()),
- FILE_TAG(new FileTagAttribute(), Bundle.FileSearch_GroupingAttributeType_tag_displayName()),
- OBJECT_DETECTED(new ObjectDetectedAttribute(), Bundle.FileSearch_GroupingAttributeType_object_displayName()),
- NO_GROUPING(new NoGroupingAttribute(), Bundle.FileSearch_GroupingAttributeType_none_displayName());
-
- private final AttributeType attributeType;
- private final String displayName;
-
- GroupingAttributeType(AttributeType attributeType, String displayName) {
- this.attributeType = attributeType;
- this.displayName = displayName;
- }
-
- @Override
- public String toString() {
- return displayName;
- }
-
- AttributeType getAttributeType() {
- return attributeType;
- }
-
- /**
- * Get the list of enums that are valid for grouping images.
- *
- * @return enums that can be used to group images
- */
- static List getOptionsForGrouping() {
- return Arrays.asList(FILE_SIZE, FREQUENCY, PARENT_PATH, OBJECT_DETECTED, HASH_LIST_NAME, INTERESTING_ITEM_SET);
- }
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/FileSearchData.java b/Core/src/org/sleuthkit/autopsy/discovery/FileSearchData.java
deleted file mode 100644
index 81bb2ca641..0000000000
--- a/Core/src/org/sleuthkit/autopsy/discovery/FileSearchData.java
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2019 Basis Technology Corp.
- * Contact: carrier sleuthkit org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.sleuthkit.autopsy.discovery;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.coreutils.FileTypeUtils;
-
-/**
- * Utility enums for FileSearch.
- */
-final class FileSearchData {
-
- private final static long BYTES_PER_MB = 1000000;
-
- /**
- * Enum representing how often the file occurs in the Central Repository.
- */
- @NbBundle.Messages({
- "FileSearchData.Frequency.unique.displayName=Unique (1)",
- "FileSearchData.Frequency.rare.displayName=Rare (2-10)",
- "FileSearchData.Frequency.common.displayName=Common (11 - 100)",
- "FileSearchData.Frequency.verycommon.displayName=Very Common (100+)",
- "FileSearchData.Frequency.known.displayName=Known (NSRL)",
- "FileSearchData.Frequency.unknown.displayName=Unknown",})
- enum Frequency {
- UNIQUE(0, 1, Bundle.FileSearchData_Frequency_unique_displayName()),
- RARE(1, 10, Bundle.FileSearchData_Frequency_rare_displayName()),
- COMMON(2, 100, Bundle.FileSearchData_Frequency_common_displayName()),
- VERY_COMMON(3, 0, Bundle.FileSearchData_Frequency_verycommon_displayName()),
- KNOWN(4, 0, Bundle.FileSearchData_Frequency_known_displayName()),
- UNKNOWN(5, 0, Bundle.FileSearchData_Frequency_unknown_displayName());
-
- private final int ranking;
- private final String displayName;
- private final int maxOccur;
-
- Frequency(int ranking, int maxOccur, String displayName) {
- this.ranking = ranking;
- this.maxOccur = maxOccur;
- this.displayName = displayName;
- }
-
- /**
- * Get the rank for sorting.
- *
- * @return the rank (lower should be displayed first)
- */
- int getRanking() {
- return ranking;
- }
-
- /**
- * Get the enum matching the given occurrence count.
- *
- * @param count Number of times a file is in the Central Repository.
- *
- * @return the corresponding enum
- */
- static Frequency fromCount(long count) {
- if (count <= UNIQUE.getMaxOccur()) {
- return UNIQUE;
- } else if (count <= RARE.getMaxOccur()) {
- return RARE;
- } else if (count <= COMMON.getMaxOccur()) {
- return COMMON;
- }
- return VERY_COMMON;
- }
-
- /**
- * Get the list of enums that are valid for filtering when a CR is
- * enabled.
- *
- * @return enums that can be used to filter with a CR.
- */
- static List getOptionsForFilteringWithCr() {
- return Arrays.asList(UNIQUE, RARE, COMMON, VERY_COMMON, KNOWN);
- }
-
- /**
- * Get the list of enums that are valid for filtering when no CR is
- * enabled.
- *
- * @return enums that can be used to filter without a CR.
- */
- static List getOptionsForFilteringWithoutCr() {
- return Arrays.asList(KNOWN, UNKNOWN);
- }
-
- @Override
- public String toString() {
- return displayName;
- }
-
- /**
- * @return the maxOccur
- */
- int getMaxOccur() {
- return maxOccur;
- }
- }
-
- /**
- * Enum representing the file size
- */
- @NbBundle.Messages({
- "FileSearchData.FileSize.XXLARGE.displayName=XXLarge",
- "FileSearchData.FileSize.XLARGE.displayName=XLarge",
- "FileSearchData.FileSize.LARGE.displayName=Large",
- "FileSearchData.FileSize.MEDIUM.displayName=Medium",
- "FileSearchData.FileSize.SMALL.displayName=Small",
- "FileSearchData.FileSize.XSMALL.displayName=XSmall",
- "FileSearchData.FileSize.10PlusGb=: 10GB+",
- "FileSearchData.FileSize.5gbto10gb=: 5-10GB",
- "FileSearchData.FileSize.1gbto5gb=: 1-5GB",
- "FileSearchData.FileSize.100mbto1gb=: 100MB-1GB",
- "FileSearchData.FileSize.200PlusMb=: 200MB+",
- "FileSearchData.FileSize.50mbto200mb=: 50-200MB",
- "FileSearchData.FileSize.500kbto100mb=: 500KB-100MB",
- "FileSearchData.FileSize.1mbto50mb=: 1-50MB",
- "FileSearchData.FileSize.100kbto1mb=: 100KB-1MB",
- "FileSearchData.FileSize.16kbto100kb=: 16-100KB",
- "FileSearchData.FileSize.upTo500kb=: 0-500KB",
- "FileSearchData.FileSize.upTo16kb=: 0-16KB",})
- enum FileSize {
- XXLARGE_VIDEO(0, 10000 * BYTES_PER_MB, -1, Bundle.FileSearchData_FileSize_XXLARGE_displayName(), Bundle.FileSearchData_FileSize_10PlusGb()),
- XLARGE_VIDEO(1, 5000 * BYTES_PER_MB, 10000 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_XLARGE_displayName(), Bundle.FileSearchData_FileSize_5gbto10gb()),
- LARGE_VIDEO(2, 1000 * BYTES_PER_MB, 5000 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_LARGE_displayName(), Bundle.FileSearchData_FileSize_1gbto5gb()),
- MEDIUM_VIDEO(3, 100 * BYTES_PER_MB, 1000 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_MEDIUM_displayName(), Bundle.FileSearchData_FileSize_100mbto1gb()),
- SMALL_VIDEO(4, 500000, 100 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_SMALL_displayName(), Bundle.FileSearchData_FileSize_500kbto100mb()),
- XSMALL_VIDEO(5, 0, 500000, Bundle.FileSearchData_FileSize_XSMALL_displayName(), Bundle.FileSearchData_FileSize_upTo500kb()),
- XXLARGE_IMAGE(6, 200 * BYTES_PER_MB, -1, Bundle.FileSearchData_FileSize_XXLARGE_displayName(), Bundle.FileSearchData_FileSize_200PlusMb()),
- XLARGE_IMAGE(7, 50 * BYTES_PER_MB, 200 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_XLARGE_displayName(), Bundle.FileSearchData_FileSize_50mbto200mb()),
- LARGE_IMAGE(8, 1 * BYTES_PER_MB, 50 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_LARGE_displayName(), Bundle.FileSearchData_FileSize_1mbto50mb()),
- MEDIUM_IMAGE(9, 100000, 1 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_MEDIUM_displayName(), Bundle.FileSearchData_FileSize_100kbto1mb()),
- SMALL_IMAGE(10, 16000, 100000, Bundle.FileSearchData_FileSize_SMALL_displayName(), Bundle.FileSearchData_FileSize_16kbto100kb()),
- XSMALL_IMAGE(11, 0, 16000, Bundle.FileSearchData_FileSize_XSMALL_displayName(), Bundle.FileSearchData_FileSize_upTo16kb());
-
- private final int ranking; // Must be unique for each value
- private final long minBytes; // Note that the size must be strictly greater than this to match
- private final long maxBytes;
- private final String sizeGroup;
- private final String displaySize;
- final static long NO_MAXIMUM = -1;
-
- FileSize(int ranking, long minB, long maxB, String displayName, String displaySize) {
- this.ranking = ranking;
- this.minBytes = minB;
- if (maxB >= 0) {
- this.maxBytes = maxB;
- } else {
- this.maxBytes = NO_MAXIMUM;
- }
- this.sizeGroup = displayName;
- this.displaySize = displaySize;
- }
-
- /**
- * Get the enum corresponding to the given file size for image files.
- * The file size must be strictly greater than minBytes.
- *
- * @param size the file size
- *
- * @return the enum whose range contains the file size
- */
- static FileSize fromImageSize(long size) {
- if (size > XXLARGE_IMAGE.getMinBytes()) {
- return XXLARGE_IMAGE;
- } else if (size > XLARGE_IMAGE.getMinBytes()) {
- return XLARGE_IMAGE;
- } else if (size > LARGE_IMAGE.getMinBytes()) {
- return LARGE_IMAGE;
- } else if (size > MEDIUM_IMAGE.getMinBytes()) {
- return MEDIUM_IMAGE;
- } else if (size > SMALL_IMAGE.getMinBytes()) {
- return SMALL_IMAGE;
- } else {
- return XSMALL_IMAGE;
- }
- }
-
- /**
- * Get the enum corresponding to the given file size for video files.
- * The file size must be strictly greater than minBytes.
- *
- * @param size the file size
- *
- * @return the enum whose range contains the file size
- */
- static FileSize fromVideoSize(long size) {
- if (size > XXLARGE_VIDEO.getMinBytes()) {
- return XXLARGE_VIDEO;
- } else if (size > XLARGE_VIDEO.getMinBytes()) {
- return XLARGE_VIDEO;
- } else if (size > LARGE_VIDEO.getMinBytes()) {
- return LARGE_VIDEO;
- } else if (size > MEDIUM_VIDEO.getMinBytes()) {
- return MEDIUM_VIDEO;
- } else if (size > SMALL_VIDEO.getMinBytes()) {
- return SMALL_VIDEO;
- } else {
- return XSMALL_VIDEO;
- }
- }
-
- /**
- * Get the upper limit of the range.
- *
- * @return the maximum file size that will fit in this range.
- */
- long getMaxBytes() {
- return maxBytes;
- }
-
- /**
- * Get the lower limit of the range.
- *
- * @return the maximum file size that is not part of this range
- */
- long getMinBytes() {
- return minBytes;
- }
-
- /**
- * Get the rank for sorting.
- *
- * @return the rank (lower should be displayed first)
- */
- int getRanking() {
- return ranking;
- }
-
- @Override
- public String toString() {
- return sizeGroup + displaySize;
- }
-
- String getSizeGroup(){
- return sizeGroup;
- }
-
- /**
- * Get the list of enums that are valid for most file sizes.
- *
- * @return Enums that can be used to filter most file including images
- * by size.
- */
- static List getDefaultSizeOptions() {
- return Arrays.asList(XXLARGE_IMAGE, XLARGE_IMAGE, LARGE_IMAGE, MEDIUM_IMAGE, SMALL_IMAGE, XSMALL_IMAGE);
- }
-
- /**
- * Get the list of enums that are valid for video sizes.
- *
- * @return enums that can be used to filter videos by size.
- */
- static List getOptionsForVideos() {
- return Arrays.asList(XXLARGE_VIDEO, XLARGE_VIDEO, LARGE_VIDEO, MEDIUM_VIDEO, SMALL_VIDEO, XSMALL_VIDEO);
- }
- }
-
- //Discovery uses a different list of document mime types than FileTypeUtils.FileTypeCategory.DOCUMENTS
- private static final ImmutableSet DOCUMENT_MIME_TYPES
- = new ImmutableSet.Builder()
- .add("text/html", //NON-NLS
- "text/csv", //NON-NLS
- "application/rtf", //NON-NLS
- "application/pdf", //NON-NLS
- "application/xhtml+xml", //NON-NLS
- "application/x-msoffice", //NON-NLS
- "application/msword", //NON-NLS
- "application/msword2", //NON-NLS
- "application/vnd.wordperfect", //NON-NLS
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
- "application/vnd.ms-powerpoint", //NON-NLS
- "application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
- "application/vnd.ms-excel", //NON-NLS
- "application/vnd.ms-excel.sheet.4", //NON-NLS
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
- "application/vnd.oasis.opendocument.presentation", //NON-NLS
- "application/vnd.oasis.opendocument.spreadsheet", //NON-NLS
- "application/vnd.oasis.opendocument.text" //NON-NLS
- ).build();
-
- private static final ImmutableSet IMAGE_UNSUPPORTED_DOC_TYPES
- = new ImmutableSet.Builder()
- .add("application/pdf", //NON-NLS
- "application/xhtml+xml").build(); //NON-NLS
-
- static Collection getDocTypesWithoutImageExtraction() {
- return Collections.unmodifiableCollection(IMAGE_UNSUPPORTED_DOC_TYPES);
- }
-
- /**
- * Enum representing the file type. We don't simply use
- * FileTypeUtils.FileTypeCategory because: - Some file types categories
- * overlap - It is convenient to have the "OTHER" option for files that
- * don't match the given types
- */
- @NbBundle.Messages({
- "FileSearchData.FileType.Audio.displayName=Audio",
- "FileSearchData.FileType.Video.displayName=Video",
- "FileSearchData.FileType.Image.displayName=Image",
- "FileSearchData.FileType.Documents.displayName=Documents",
- "FileSearchData.FileType.Executables.displayName=Executables",
- "FileSearchData.FileType.Other.displayName=Other/Unknown"})
- enum FileType {
-
- IMAGE(0, Bundle.FileSearchData_FileType_Image_displayName(), FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes()),
- AUDIO(1, Bundle.FileSearchData_FileType_Audio_displayName(), FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes()),
- VIDEO(2, Bundle.FileSearchData_FileType_Video_displayName(), FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes()),
- EXECUTABLE(3, Bundle.FileSearchData_FileType_Executables_displayName(), FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes()),
- DOCUMENTS(4, Bundle.FileSearchData_FileType_Documents_displayName(), DOCUMENT_MIME_TYPES),
- OTHER(5, Bundle.FileSearchData_FileType_Other_displayName(), new ArrayList<>());
-
- private final int ranking; // For ordering in the UI
- private final String displayName;
- private final Collection mediaTypes;
-
- FileType(int value, String displayName, Collection mediaTypes) {
- this.ranking = value;
- this.displayName = displayName;
- this.mediaTypes = mediaTypes;
- }
-
- /**
- * Get the MIME types matching this category.
- *
- * @return Collection of MIME type strings
- */
- Collection getMediaTypes() {
- return Collections.unmodifiableCollection(mediaTypes);
- }
-
- @Override
- public String toString() {
- return displayName;
- }
-
- /**
- * Get the rank for sorting.
- *
- * @return the rank (lower should be displayed first)
- */
- int getRanking() {
- return ranking;
- }
-
- /**
- * Get the enum matching the given MIME type.
- *
- * @param mimeType The MIME type for the file
- *
- * @return the corresponding enum (will be OTHER if no types matched)
- */
- static FileType fromMIMEtype(String mimeType) {
- for (FileType type : FileType.values()) {
- if (type.getMediaTypes().contains(mimeType)) {
- return type;
- }
- }
- return OTHER;
- }
-
- /**
- * Get the list of enums that are valid for filtering.
- *
- * @return enums that can be used to filter
- */
- static List getOptionsForFiltering() {
- return Arrays.asList(IMAGE, VIDEO);
- }
- }
-
- /**
- * Enum representing the score of the file.
- */
- @NbBundle.Messages({
- "FileSearchData.Score.notable.displayName=Notable",
- "FileSearchData.Score.interesting.displayName=Interesting",
- "FileSearchData.Score.unknown.displayName=Unknown",})
- enum Score {
- NOTABLE(0, Bundle.FileSearchData_Score_notable_displayName()),
- INTERESTING(1, Bundle.FileSearchData_Score_interesting_displayName()),
- UNKNOWN(2, Bundle.FileSearchData_Score_unknown_displayName());
-
- private final int ranking;
- private final String displayName;
-
- Score(int ranking, String displayName) {
- this.ranking = ranking;
- this.displayName = displayName;
- }
-
- /**
- * Get the rank for sorting.
- *
- * @return the rank (lower should be displayed first)
- */
- int getRanking() {
- return ranking;
- }
-
- /**
- * Get the list of enums that are valid for filtering.
- *
- * @return enums that can be used to filter
- */
- static List getOptionsForFiltering() {
- return Arrays.asList(NOTABLE, INTERESTING);
- }
-
- @Override
- public String toString() {
- return displayName;
- }
- }
-
- private FileSearchData() {
- // Class should not be instantiated
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/FileSorter.java b/Core/src/org/sleuthkit/autopsy/discovery/FileSorter.java
deleted file mode 100644
index fc74dd5713..0000000000
--- a/Core/src/org/sleuthkit/autopsy/discovery/FileSorter.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2019 Basis Technology Corp.
- * Contact: carrier sleuthkit org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.sleuthkit.autopsy.discovery;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import org.openide.util.NbBundle;
-import org.sleuthkit.datamodel.TskCoreException;
-
-/**
- * Class used to sort ResultFiles using the supplied method.
- */
-class FileSorter implements Comparator {
-
- private final List> comparators = new ArrayList<>();
-
- /**
- * Set up the sorter using the supplied sorting method. The sorting is
- * defined by a list of ResultFile comparators. These comparators will be
- * run in order until one returns a non-zero result.
- *
- * @param method The method that should be used to sort the files
- */
- FileSorter(SortingMethod method) {
-
- // Set up the primary comparators that should applied to the files
- switch (method) {
- case BY_DATA_SOURCE:
- comparators.add(getDataSourceComparator());
- break;
- case BY_FILE_SIZE:
- comparators.add(getFileSizeComparator());
- break;
- case BY_FILE_TYPE:
- comparators.add(getFileTypeComparator());
- comparators.add(getMIMETypeComparator());
- break;
- case BY_FREQUENCY:
- comparators.add(getFrequencyComparator());
- break;
- case BY_KEYWORD_LIST_NAMES:
- comparators.add(getKeywordListNameComparator());
- break;
- case BY_FULL_PATH:
- comparators.add(getParentPathComparator());
- break;
- case BY_FILE_NAME:
- comparators.add(getFileNameComparator());
- break;
- default:
- // The default comparator will be added afterward
- break;
- }
-
- // Add the default comparator to the end. This will ensure a consistent sort
- // order regardless of the order the files were added to the list.
- comparators.add(getDefaultComparator());
- }
-
- @Override
- public int compare(ResultFile file1, ResultFile file2) {
-
- int result = 0;
- for (Comparator comp : comparators) {
- result = comp.compare(file1, file2);
- if (result != 0) {
- return result;
- }
- }
-
- // The files are the same
- return result;
- }
-
- /**
- * Compare files using data source ID. Will order smallest to largest.
- *
- * @return -1 if file1 has the lower data source ID, 0 if equal, 1 otherwise
- */
- private static Comparator getDataSourceComparator() {
- return (ResultFile file1, ResultFile file2) -> Long.compare(file1.getFirstInstance().getDataSourceObjectId(), file2.getFirstInstance().getDataSourceObjectId());
- }
-
- /**
- * Compare files using their FileType enum. Orders based on the ranking in
- * the FileType enum.
- *
- * @return -1 if file1 has the lower FileType value, 0 if equal, 1 otherwise
- */
- private static Comparator getFileTypeComparator() {
- return (ResultFile file1, ResultFile file2) -> Integer.compare(file1.getFileType().getRanking(), file2.getFileType().getRanking());
- }
-
- /**
- * Compare files using a concatenated version of keyword list names.
- * Alphabetical by the list names with files with no keyword list hits going
- * last.
- *
- * @return -1 if file1 has the earliest combined keyword list name, 0 if
- * equal, 1 otherwise
- */
- private static Comparator getKeywordListNameComparator() {
- return (ResultFile file1, ResultFile file2) -> {
- // Put empty lists at the bottom
- if (file1.getKeywordListNames().isEmpty()) {
- if (file2.getKeywordListNames().isEmpty()) {
- return 0;
- }
- return 1;
- } else if (file2.getKeywordListNames().isEmpty()) {
- return -1;
- }
-
- String list1 = String.join(",", file1.getKeywordListNames());
- String list2 = String.join(",", file2.getKeywordListNames());
- return compareStrings(list1, list2);
- };
- }
-
- /**
- * Compare files based on parent path. Order alphabetically.
- *
- * @return -1 if file1's path comes first alphabetically, 0 if equal, 1
- * otherwise
- */
- private static Comparator getParentPathComparator() {
-
- return new Comparator() {
- @Override
- public int compare(ResultFile file1, ResultFile file2) {
- String file1ParentPath;
- try {
- file1ParentPath = file1.getFirstInstance().getParent().getUniquePath();
- } catch (TskCoreException ingored) {
- file1ParentPath = file1.getFirstInstance().getParentPath();
- }
- String file2ParentPath;
- try {
- file2ParentPath = file2.getFirstInstance().getParent().getUniquePath();
- } catch (TskCoreException ingored) {
- file2ParentPath = file2.getFirstInstance().getParentPath();
- }
- return compareStrings(file1ParentPath.toLowerCase(), file2ParentPath.toLowerCase());
- }
- };
- }
-
- /**
- * Compare files based on number of occurrences in the central repository.
- * Order from most rare to least rare Frequency enum.
- *
- * @return -1 if file1's rarity is lower than file2, 0 if equal, 1 otherwise
- */
- private static Comparator getFrequencyComparator() {
- return (ResultFile file1, ResultFile file2) -> Integer.compare(file1.getFrequency().getRanking(), file2.getFrequency().getRanking());
- }
-
- /**
- * Compare files based on MIME type. Order is alphabetical.
- *
- * @return -1 if file1's MIME type comes before file2's, 0 if equal, 1
- * otherwise
- */
- private static Comparator getMIMETypeComparator() {
- return (ResultFile file1, ResultFile file2) -> compareStrings(file1.getFirstInstance().getMIMEType(), file2.getFirstInstance().getMIMEType());
- }
-
- /**
- * Compare files based on size. Order large to small.
- *
- * @return -1 if file1 is larger than file2, 0 if equal, 1 otherwise
- */
- private static Comparator getFileSizeComparator() {
- return (ResultFile file1, ResultFile file2) -> -1 * Long.compare(file1.getFirstInstance().getSize(), file2.getFirstInstance().getSize()) // Sort large to small
- ;
- }
-
- /**
- * Compare files based on file name. Order alphabetically.
- *
- * @return -1 if file1 comes before file2, 0 if equal, 1 otherwise
- */
- private static Comparator getFileNameComparator() {
- return (ResultFile file1, ResultFile file2) -> compareStrings(file1.getFirstInstance().getName().toLowerCase(), file2.getFirstInstance().getName().toLowerCase());
- }
-
- /**
- * A final default comparison between two ResultFile objects. Currently this
- * is on file name and then object ID. It can be changed but should always
- * include something like the object ID to ensure a consistent sorting when
- * the rest of the compared fields are the same.
- *
- * @return -1 if file1 comes before file2, 0 if equal, 1 otherwise
- */
- private static Comparator getDefaultComparator() {
- return (ResultFile file1, ResultFile file2) -> {
- // Compare file names and then object ID (to ensure a consistent sort)
- int result = getFileNameComparator().compare(file1, file2);
- if (result == 0) {
- return Long.compare(file1.getFirstInstance().getId(), file2.getFirstInstance().getId());
- }
- return result;
- };
- }
-
- /**
- * Compare two strings alphabetically. Nulls are allowed.
- *
- * @param s1
- * @param s2
- *
- * @return -1 if s1 comes before s2, 0 if equal, 1 otherwise
- */
- private static int compareStrings(String s1, String s2) {
- String string1 = s1 == null ? "" : s1;
- String string2 = s2 == null ? "" : s2;
- return string1.compareTo(string2);
- }
-
- /**
- * Enum for selecting the primary method for sorting result files.
- */
- @NbBundle.Messages({
- "FileSorter.SortingMethod.datasource.displayName=Data Source",
- "FileSorter.SortingMethod.filename.displayName=File Name",
- "FileSorter.SortingMethod.filesize.displayName=File Size",
- "FileSorter.SortingMethod.filetype.displayName=File Type",
- "FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency",
- "FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names",
- "FileSorter.SortingMethod.fullPath.displayName=Full Path"})
- enum SortingMethod {
- BY_FILE_NAME(new ArrayList<>(),
- Bundle.FileSorter_SortingMethod_filename_displayName()), // Sort alphabetically by file name
- BY_DATA_SOURCE(new ArrayList<>(),
- Bundle.FileSorter_SortingMethod_datasource_displayName()), // Sort in increasing order of data source ID
- BY_FILE_SIZE(new ArrayList<>(),
- Bundle.FileSorter_SortingMethod_filesize_displayName()), // Sort in decreasing order of size
- BY_FILE_TYPE(Arrays.asList(new FileSearch.FileTypeAttribute()),
- Bundle.FileSorter_SortingMethod_filetype_displayName()), // Sort in order of file type (defined in FileType enum), with secondary sort on MIME type
- BY_FREQUENCY(Arrays.asList(new FileSearch.FrequencyAttribute()),
- Bundle.FileSorter_SortingMethod_frequency_displayName()), // Sort by decreasing rarity in the central repository
- BY_KEYWORD_LIST_NAMES(Arrays.asList(new FileSearch.KeywordListAttribute()),
- Bundle.FileSorter_SortingMethod_keywordlist_displayName()), // Sort alphabetically by list of keyword list names found
- BY_FULL_PATH(new ArrayList<>(),
- Bundle.FileSorter_SortingMethod_fullPath_displayName()); // Sort alphabetically by path
-
- private final String displayName;
- private final List requiredAttributes;
-
- SortingMethod(List attributes, String displayName) {
- this.requiredAttributes = attributes;
- this.displayName = displayName;
- }
-
- @Override
- public String toString() {
- return displayName;
- }
-
- List getRequiredAttributes() {
- return Collections.unmodifiableList(requiredAttributes);
- }
-
- /**
- * Get the list of enums that are valid for ordering images.
- *
- * @return enums that can be used to ordering images.
- */
- static List getOptionsForOrdering() {
- return Arrays.asList(BY_FILE_SIZE, BY_FULL_PATH, BY_FILE_NAME, BY_DATA_SOURCE);
- }
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/discovery/search/AbstractFilter.java
new file mode 100644
index 0000000000..bb2e258f2e
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/AbstractFilter.java
@@ -0,0 +1,74 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.discovery.search;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
+import org.sleuthkit.datamodel.SleuthkitCase;
+
+/**
+ * Base class for the filters.
+ */
+public abstract class AbstractFilter {
+
+ /**
+ * Returns part of a query on the table that can be AND-ed with other pieces
+ *
+ * @return the SQL query or an empty string if there is no SQL query for
+ * this filter.
+ */
+ public abstract String getWhereClause();
+
+ /**
+ * Indicates whether this filter needs to use the secondary, non-SQL method
+ * applyAlternateFilter().
+ *
+ * @return false by default
+ */
+ public boolean useAlternateFilter() {
+ return false;
+ }
+
+ /**
+ * Run a secondary filter that does not operate on table.
+ *
+ * @param currentResults The current list of matching results; empty if no
+ * filters have yet been run.
+ * @param caseDb The case database
+ * @param centralRepoDb The central repo database. Can be null if the
+ * filter does not require it.
+ *
+ * @return The list of results that match this filter (and any that came
+ * before it)
+ *
+ * @throws DiscoveryException
+ */
+ public List applyAlternateFilter(List currentResults, SleuthkitCase caseDb,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+ return new ArrayList<>();
+ }
+
+ /**
+ * Get a description of the selected filter.
+ *
+ * @return A description of the filter
+ */
+ public abstract String getDesc();
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED
new file mode 100644
index 0000000000..037868e838
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED
@@ -0,0 +1,132 @@
+DiscoveryAttributes.GroupingAttributeType.datasource.displayName=Data Source
+DiscoveryAttributes.GroupingAttributeType.fileType.displayName=File Type
+DiscoveryAttributes.GroupingAttributeType.firstDate.displayName=First Activity Date
+DiscoveryAttributes.GroupingAttributeType.frequency.displayName=Past Occurrences
+DiscoveryAttributes.GroupingAttributeType.hash.displayName=Hash Set
+DiscoveryAttributes.GroupingAttributeType.interestingItem.displayName=Interesting Item
+DiscoveryAttributes.GroupingAttributeType.keywordList.displayName=Keyword
+DiscoveryAttributes.GroupingAttributeType.mostRecentDate.displayName=Most Recent Activity Date
+DiscoveryAttributes.GroupingAttributeType.none.displayName=None
+DiscoveryAttributes.GroupingAttributeType.numberOfVisits.displayName=Number of Visits
+DiscoveryAttributes.GroupingAttributeType.object.displayName=Object Detected
+DiscoveryAttributes.GroupingAttributeType.parent.displayName=Parent Folder
+DiscoveryAttributes.GroupingAttributeType.size.displayName=File Size
+DiscoveryAttributes.GroupingAttributeType.tag.displayName=Tag
+# {0} - Data source name
+# {1} - Data source ID
+DiscoveryKeyUtils.DataSourceGroupKey.datasourceAndID={0}(ID: {1})
+# {0} - Data source ID
+DiscoveryKeyUtils.DataSourceGroupKey.idOnly=Data source (ID: {0})
+DiscoveryKeyUtils.FileTagGroupKey.noSets=None
+DiscoveryKeyUtils.FirstActivityDateGroupKey.noDate=No Date Available
+DiscoveryKeyUtils.HashHitsGroupKey.noHashHits=None
+DiscoveryKeyUtils.InterestingItemGroupKey.noSets=None
+DiscoveryKeyUtils.KeywordListGroupKey.noKeywords=None
+DiscoveryKeyUtils.MostRecentActivityDateGroupKey.noDate=No Date Available
+DiscoveryKeyUtils.NoGroupingGroupKey.allFiles=All Files
+# {0} - totalVisits
+DiscoveryKeyUtils.NumberOfVisitsGroupKey.displayName={0} visits
+DiscoveryKeyUtils.NumberOfVisitsGroupKey.noVisits=No visits
+DiscoveryKeyUtils.ObjectDetectedGroupKey.noSets=None
+FileGroup.groupSortingAlgorithm.groupName.text=Group Name
+FileGroup.groupSortingAlgorithm.groupSize.text=Group Size
+FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview.
+FileSearch.documentSummary.noPreview=No preview available.
+FileSearchFiltering.concatenateSetNamesForDisplay.comma=,
+# {0} - filters
+FileSearchFiltering.HashSetFilter.desc=Hash set hits in set(s): {0}
+FileSearchFiltering.KnownFilter.desc=which are not known
+FileSearchFiltering.PreviouslyNotableFilter.desc=that were previously marked as notable
+# {0} - tag names
+FileSearchFiltering.TagsFilter.desc=Tagged {0}
+FileSearchFiltering.TagsFilter.or=,
+FileSearchFiltering.UserCreatedFilter.desc=that contain EXIF data
+FileSorter.SortingMethod.datasource.displayName=Data Source
+FileSorter.SortingMethod.domain.displayName=Domain
+FileSorter.SortingMethod.filename.displayName=File Name
+FileSorter.SortingMethod.filesize.displayName=File Size
+FileSorter.SortingMethod.filetype.displayName=File Type
+FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency
+FileSorter.SortingMethod.fullPath.displayName=Full Path
+FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names
+ResultFile.score.interestingResult.description=At least one instance of the file has an interesting result associated with it.
+ResultFile.score.notableFile.description=At least one instance of the file was recognized as notable.
+ResultFile.score.notableTaggedFile.description=At least one instance of the file is tagged with a notable tag.
+ResultFile.score.taggedFile.description=At least one instance of the file has been tagged.
+SearchData.AttributeType.Domain.displayName=Domain
+SearchData.FileSize.100kbto1mb=: 100KB-1MB
+SearchData.FileSize.100mbto1gb=: 100MB-1GB
+SearchData.FileSize.10PlusGb=: 10GB+
+SearchData.FileSize.16kbto100kb=: 16-100KB
+SearchData.FileSize.1gbto5gb=: 1-5GB
+SearchData.FileSize.1mbto50mb=: 1-50MB
+SearchData.FileSize.200PlusMb=: 200MB+
+SearchData.FileSize.500kbto100mb=: 500KB-100MB
+SearchData.FileSize.50mbto200mb=: 50-200MB
+SearchData.FileSize.5gbto10gb=: 5-10GB
+SearchData.FileSize.LARGE.displayName=Large
+SearchData.FileSize.MEDIUM.displayName=Medium
+SearchData.FileSize.SMALL.displayName=Small
+SearchData.FileSize.upTo16kb=: 0-16KB
+SearchData.FileSize.upTo500kb=: 0-500KB
+SearchData.FileSize.XLARGE.displayName=XLarge
+SearchData.FileSize.XSMALL.displayName=XSmall
+SearchData.FileSize.XXLARGE.displayName=XXLarge
+SearchData.FileType.Audio.displayName=Audio
+SearchData.FileType.Documents.displayName=Document
+SearchData.FileType.Executables.displayName=Executable
+SearchData.FileType.Image.displayName=Image
+SearchData.FileType.Other.displayName=Other/Unknown
+SearchData.FileType.Video.displayName=Video
+SearchData.Frequency.common.displayName=Common (11 - 100)
+SearchData.Frequency.known.displayName=Known (NSRL)
+SearchData.Frequency.rare.displayName=Rare (2-10)
+SearchData.Frequency.unique.displayName=Unique (1)
+SearchData.Frequency.unknown.displayName=Unknown
+SearchData.Frequency.verycommon.displayName=Very Common (100+)
+SearchData.Score.interesting.displayName=Interesting
+SearchData.Score.notable.displayName=Notable
+SearchData.Score.unknown.displayName=Unknown
+# {0} - artifactTypes
+SearchFiltering.artifactTypeFilter.desc=Result type(s): {0}
+SearchFiltering.artifactTypeFilter.or=,
+# {0} - Data source name
+# {1} - Data source ID
+SearchFiltering.DataSourceFilter.datasource={0}({1})
+# {0} - filters
+SearchFiltering.DataSourceFilter.desc=Data source(s): {0}
+SearchFiltering.DataSourceFilter.or=,
+# {0} - startDate
+SearchFiltering.dateRangeFilter.after=after: {0}
+SearchFiltering.dateRangeFilter.and=\ and
+# {0} - endDate
+SearchFiltering.dateRangeFilter.before=before: {0}
+SearchFiltering.dateRangeFilter.lable=Activity date
+# {0} - filters
+SearchFiltering.FileTypeFilter.desc=Type: {0}
+SearchFiltering.FileTypeFilter.or=,
+# {0} - filters
+SearchFiltering.FrequencyFilter.desc=Past occurrences: {0}
+SearchFiltering.FrequencyFilter.or=,
+# {0} - filters
+SearchFiltering.InterestingItemSetFilter.desc=Interesting item hits in set(s): {0}
+# {0} - filters
+SearchFiltering.KeywordListFilter.desc=Keywords in list(s): {0}
+# {0} - filters
+SearchFiltering.ObjectDetectionFilter.desc=Objects detected in set(s): {0}
+# {0} - filters
+SearchFiltering.ParentFilter.desc=Paths matching: {0}
+SearchFiltering.ParentFilter.exact=(exact match)
+SearchFiltering.ParentFilter.excluded=(excluded)
+SearchFiltering.ParentFilter.included=(included)
+SearchFiltering.ParentFilter.or=,
+SearchFiltering.ParentFilter.substring=(substring)
+SearchFiltering.ParentSearchTerm.excludeString=\ (exclude)
+SearchFiltering.ParentSearchTerm.fullString=\ (exact)
+SearchFiltering.ParentSearchTerm.includeString=\ (include)
+SearchFiltering.ParentSearchTerm.subString=\ (substring)
+# {0} - filters
+SearchFiltering.ScoreFilter.desc=Score(s) of : {0}
+# {0} - filters
+SearchFiltering.SizeFilter.desc=Size(s): {0}
+SearchFiltering.SizeFilter.or=,
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java
new file mode 100644
index 0000000000..637e982f98
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java
@@ -0,0 +1,896 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.discovery.search;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.InstanceTableCallback;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.CaseDbAccessManager;
+import org.sleuthkit.datamodel.ContentTag;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.TskData;
+import java.util.StringJoiner;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizer;
+
+/**
+ * Class which contains the search attributes which can be specified for
+ * Discovery.
+ */
+public class DiscoveryAttributes {
+
+ private final static Logger logger = Logger.getLogger(DiscoveryAttributes.class.getName());
+
+ /**
+ * Base class for the grouping attributes.
+ */
+ public abstract static class AttributeType {
+
+ /**
+ * For a given Result, return the key for the group it belongs to for
+ * this attribute type.
+ *
+ * @param result The result to be grouped.
+ *
+ * @return The key for the group this result goes in.
+ */
+ public abstract DiscoveryKeyUtils.GroupKey getGroupKey(Result result);
+
+ /**
+ * Add any extra data to the ResultFile object from this attribute.
+ *
+ * @param files The list of results to enhance.
+ * @param caseDb The case database.
+ * @param centralRepoDb The central repository database. Can be null if
+ * not needed.
+ *
+ * @throws DiscoveryException
+ */
+ public void addAttributeToResults(List results, SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
+ // Default is to do nothing
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by file size.
+ */
+ public static class FileSizeAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
+ return new DiscoveryKeyUtils.FileSizeGroupKey(result);
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by parent path.
+ */
+ public static class ParentPathAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
+ return new DiscoveryKeyUtils.ParentPathGroupKey((ResultFile) file);
+ }
+ }
+
+ /**
+ * Default attribute used to make one group.
+ */
+ static class NoGroupingAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
+ return new DiscoveryKeyUtils.NoGroupingGroupKey();
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by data source.
+ */
+ static class DataSourceAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
+ return new DiscoveryKeyUtils.DataSourceGroupKey(result);
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by file type.
+ */
+ static class FileTypeAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
+ return new DiscoveryKeyUtils.FileTypeGroupKey(file);
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by keyword lists.
+ */
+ static class KeywordListAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
+ return new DiscoveryKeyUtils.KeywordListGroupKey((ResultFile) file);
+ }
+
+ @Override
+ public void addAttributeToResults(List results, SleuthkitCase caseDb,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+
+ // Get pairs of (object ID, keyword list name) for all files in the list of files that have
+ // keyword list hits.
+ String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(),
+ BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
+
+ SetKeywordListNamesCallback callback = new SetKeywordListNamesCallback(results);
+ try {
+ caseDb.getCaseDbAccessManager().select(selectQuery, callback);
+ } catch (TskCoreException ex) {
+ throw new DiscoveryException("Error looking up keyword list attributes", ex); // NON-NLS
+ }
+ }
+
+ /**
+ * Callback to process the results of the CaseDbAccessManager select
+ * query. Will add the keyword list names to the list of ResultFile
+ * objects.
+ */
+ private static class SetKeywordListNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
+
+ List resultFiles;
+
+ /**
+ * Create the callback.
+ *
+ * @param resultFiles List of files to add keyword list names to.
+ */
+ SetKeywordListNamesCallback(List resultFiles) {
+ this.resultFiles = resultFiles;
+ }
+
+ @Override
+ public void process(ResultSet rs) {
+ try {
+ // Create a temporary map of object ID to ResultFile
+ Map tempMap = new HashMap<>();
+ for (Result result : resultFiles) {
+ if (result.getType() == SearchData.Type.DOMAIN) {
+ break;
+ }
+ ResultFile file = (ResultFile) result;
+ tempMap.put(file.getFirstInstance().getId(), file);
+ }
+
+ while (rs.next()) {
+ try {
+ Long objId = rs.getLong("object_id"); // NON-NLS
+ String keywordListName = rs.getString("set_name"); // NON-NLS
+
+ tempMap.get(objId).addKeywordListName(keywordListName);
+
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Failed to get keyword list names", ex); // NON-NLS
+ }
+ }
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by frequency in the central repository.
+ */
+ static class FrequencyAttribute extends AttributeType {
+
+ static final int BATCH_SIZE = 50; // Number of hashes to look up at one time
+
+ static final int DOMAIN_BATCH_SIZE = 500; // Number of domains to look up at one time
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
+ return new DiscoveryKeyUtils.FrequencyGroupKey(file);
+ }
+
+ @Override
+ public void addAttributeToResults(List results, SleuthkitCase caseDb,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+ if (centralRepoDb == null) {
+ for (Result result : results) {
+ if (result.getFrequency() == SearchData.Frequency.UNKNOWN && result.getKnown() == TskData.FileKnown.KNOWN) {
+ result.setFrequency(SearchData.Frequency.KNOWN);
+ }
+ }
+ } else {
+ processResultFilesForCR(results, centralRepoDb);
+ }
+ }
+
+ /**
+ * Private helper method for adding Frequency attribute when CR is
+ * enabled.
+ *
+ * @param files The list of ResultFiles to caluclate frequency
+ * for.
+ * @param centralRepoDb The central repository currently in use.
+ */
+ private void processResultFilesForCR(List results,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+ List currentFiles = new ArrayList<>();
+ Set hashesToLookUp = new HashSet<>();
+ List domainsToQuery = new ArrayList<>();
+ for (Result result : results) {
+ if (result.getKnown() == TskData.FileKnown.KNOWN) {
+ result.setFrequency(SearchData.Frequency.KNOWN);
+ }
+
+ if (result.getType() != SearchData.Type.DOMAIN) {
+ ResultFile file = (ResultFile) result;
+ if (file.getFrequency() == SearchData.Frequency.UNKNOWN
+ && file.getFirstInstance().getMd5Hash() != null
+ && !file.getFirstInstance().getMd5Hash().isEmpty()) {
+ hashesToLookUp.add(file.getFirstInstance().getMd5Hash());
+ currentFiles.add(file);
+ }
+
+ if (hashesToLookUp.size() >= BATCH_SIZE) {
+ computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
+
+ hashesToLookUp.clear();
+ currentFiles.clear();
+ }
+ } else {
+ ResultDomain domainInstance = (ResultDomain) result;
+ if (domainInstance.getFrequency() != SearchData.Frequency.UNKNOWN) {
+ // Frequency already calculated, skipping...
+ continue;
+ }
+ domainsToQuery.add(domainInstance);
+
+ if (domainsToQuery.size() == DOMAIN_BATCH_SIZE) {
+ queryDomainFrequency(domainsToQuery, centralRepoDb);
+
+ domainsToQuery.clear();
+ }
+ }
+ }
+
+ queryDomainFrequency(domainsToQuery, centralRepoDb);
+ computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
+ }
+ }
+
+ /**
+ * Query to get the frequency of a domain.
+ *
+ * @param domainsToQuery List of domains to check the frequency of.
+ * @param centralRepository The central repository to query.
+ *
+ * @throws DiscoveryException
+ */
+ private static void queryDomainFrequency(List domainsToQuery, CentralRepository centralRepository) throws DiscoveryException {
+ if (domainsToQuery.isEmpty()) {
+ return;
+ }
+ try {
+ final Map> resultDomainTable = new HashMap<>();
+ final StringJoiner joiner = new StringJoiner(", ");
+
+ final CorrelationAttributeInstance.Type attributeType = centralRepository.getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID);
+ for (ResultDomain domainInstance : domainsToQuery) {
+ try {
+ final String domainValue = domainInstance.getDomain();
+ final String normalizedDomain = CorrelationAttributeNormalizer.normalize(attributeType, domainValue);
+ final List bucket = resultDomainTable.getOrDefault(normalizedDomain, new ArrayList<>());
+ bucket.add(domainInstance);
+ resultDomainTable.put(normalizedDomain, bucket);
+ joiner.add("'" + normalizedDomain + "'");
+ } catch (CorrelationAttributeNormalizationException ex) {
+ logger.log(Level.INFO, String.format("Domain [%s] failed normalization, skipping...", domainInstance.getDomain()));
+ }
+ }
+
+ final String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType);
+ final String domainFrequencyQuery = " value AS domain_name, COUNT(*) AS frequency "
+ + "FROM " + tableName + " "
+ + "WHERE value IN (" + joiner + ") "
+ + "GROUP BY value";
+
+ final DomainFrequencyCallback frequencyCallback = new DomainFrequencyCallback(resultDomainTable);
+ centralRepository.processSelectClause(domainFrequencyQuery, frequencyCallback);
+
+ if (frequencyCallback.getCause() != null) {
+ throw frequencyCallback.getCause();
+ }
+ } catch (CentralRepoException | SQLException ex) {
+ throw new DiscoveryException("Fatal exception encountered querying the CR.", ex);
+ }
+ }
+
+ /**
+ * Callback to get the frequency of domain.
+ */
+ private static class DomainFrequencyCallback implements InstanceTableCallback {
+
+ private final Map> domainLookup;
+ private SQLException sqlCause;
+
+ /**
+ * Construct a new DomainFrequencyCallback.
+ *
+ * @param domainLookup The map to get domain from.
+ */
+ private DomainFrequencyCallback(Map> domainLookup) {
+ this.domainLookup = domainLookup;
+ }
+
+ @Override
+ public void process(ResultSet resultSet) {
+ try {
+ while (resultSet.next()) {
+ String domain = resultSet.getString("domain_name");
+ Long frequency = resultSet.getLong("frequency");
+
+ List domainInstances = domainLookup.get(domain);
+ for (ResultDomain domainInstance : domainInstances) {
+ domainInstance.setFrequency(SearchData.Frequency.fromCount(frequency));
+ }
+ }
+ } catch (SQLException ex) {
+ this.sqlCause = ex;
+ }
+ }
+
+ /**
+ * Get the SQL exception if one occurred during this callback.
+ *
+ * @return
+ */
+ SQLException getCause() {
+ return this.sqlCause;
+ }
+ }
+
+ /**
+ * Callback to use with findInterCaseValuesByCount which generates a list of
+ * values for common property search
+ */
+ private static class FrequencyCallback implements InstanceTableCallback {
+
+ private final List files;
+
+ /**
+ * Construct a new FrequencyCallback.
+ *
+ * @param resultFiles List of files to add hash set names to.
+ */
+ private FrequencyCallback(List files) {
+ this.files = new ArrayList<>(files);
+ }
+
+ @Override
+ public void process(ResultSet resultSet) {
+ try {
+
+ while (resultSet.next()) {
+ String hash = resultSet.getString(1);
+ int count = resultSet.getInt(2);
+ for (Iterator iterator = files.iterator(); iterator.hasNext();) {
+ ResultFile file = iterator.next();
+ if (file.getFirstInstance().getMd5Hash().equalsIgnoreCase(hash)) {
+ file.setFrequency(SearchData.Frequency.fromCount(count));
+ iterator.remove();
+ }
+ }
+ }
+
+ // The files left had no matching entries in the CR, so mark them as unique
+ for (ResultFile file : files) {
+ file.setFrequency(SearchData.Frequency.UNIQUE);
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
+ }
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by hash set lists.
+ */
+ static class HashHitsAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
+ if (result.getType() == SearchData.Type.DOMAIN) {
+ return null;
+ }
+ return new DiscoveryKeyUtils.HashHitsGroupKey((ResultFile) result);
+ }
+
+ @Override
+ public void addAttributeToResults(List results, SleuthkitCase caseDb,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+
+ // Get pairs of (object ID, hash set name) for all files in the list of files that have
+ // hash set hits.
+ String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID(),
+ BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
+
+ HashSetNamesCallback callback = new HashSetNamesCallback(results);
+ try {
+ caseDb.getCaseDbAccessManager().select(selectQuery, callback);
+ } catch (TskCoreException ex) {
+ throw new DiscoveryException("Error looking up hash set attributes", ex); // NON-NLS
+ }
+ }
+
+ /**
+ * Callback to process the results of the CaseDbAccessManager select
+ * query. Will add the hash set names to the list of ResultFile objects.
+ */
+ private static class HashSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
+
+ List results;
+
+ /**
+ * Create the callback.
+ *
+ * @param resultFiles List of files to add hash set names to.
+ */
+ HashSetNamesCallback(List results) {
+ this.results = results;
+ }
+
+ @Override
+ public void process(ResultSet rs) {
+ try {
+ // Create a temporary map of object ID to ResultFile
+ Map tempMap = new HashMap<>();
+ for (Result result : results) {
+ if (result.getType() == SearchData.Type.DOMAIN) {
+ return;
+ }
+ ResultFile file = (ResultFile) result;
+ tempMap.put(file.getFirstInstance().getId(), file);
+ }
+
+ while (rs.next()) {
+ try {
+ Long objId = rs.getLong("object_id"); // NON-NLS
+ String hashSetName = rs.getString("set_name"); // NON-NLS
+
+ tempMap.get(objId).addHashSetName(hashSetName);
+
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Failed to get hash set names", ex); // NON-NLS
+ }
+ }
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by interesting item set lists.
+ */
+ static class InterestingItemAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
+ return new DiscoveryKeyUtils.InterestingItemGroupKey((ResultFile) file);
+ }
+
+ @Override
+ public void addAttributeToResults(List results, SleuthkitCase caseDb,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+
+ // Get pairs of (object ID, interesting item set name) for all files in the list of files that have
+ // interesting file set hits.
+ String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),
+ BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
+
+ InterestingFileSetNamesCallback callback = new InterestingFileSetNamesCallback(results);
+ try {
+ caseDb.getCaseDbAccessManager().select(selectQuery, callback);
+ } catch (TskCoreException ex) {
+ throw new DiscoveryException("Error looking up interesting file set attributes", ex); // NON-NLS
+ }
+ }
+
+ /**
+ * Callback to process the results of the CaseDbAccessManager select
+ * query. Will add the interesting file set names to the list of
+ * ResultFile objects.
+ */
+ private static class InterestingFileSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
+
+ List results;
+
+ /**
+ * Create the callback.
+ *
+ * @param resultFiles List of files to add interesting file set
+ * names to.
+ */
+ InterestingFileSetNamesCallback(List results) {
+ this.results = results;
+ }
+
+ @Override
+ public void process(ResultSet rs) {
+ try {
+ // Create a temporary map of object ID to ResultFile
+ Map tempMap = new HashMap<>();
+ for (Result result : results) {
+ if (result.getType() == SearchData.Type.DOMAIN) {
+ return;
+ }
+ ResultFile file = (ResultFile) result;
+ tempMap.put(file.getFirstInstance().getId(), file);
+ }
+
+ while (rs.next()) {
+ try {
+ Long objId = rs.getLong("object_id"); // NON-NLS
+ String setName = rs.getString("set_name"); // NON-NLS
+
+ tempMap.get(objId).addInterestingSetName(setName);
+
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Failed to get interesting file set names", ex); // NON-NLS
+ }
+ }
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by date of most recent activity.
+ */
+ static class MostRecentActivityDateAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
+ return new DiscoveryKeyUtils.MostRecentActivityDateGroupKey(result);
+ }
+
+ }
+
+ /**
+ * Attribute for grouping/sorting by date of first activity.
+ */
+ static class FirstActivityDateAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
+ return new DiscoveryKeyUtils.FirstActivityDateGroupKey(result);
+ }
+
+ }
+
+ /**
+ * Attribute for grouping/sorting by number of visits.
+ */
+ static class NumberOfVisitsAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
+ return new DiscoveryKeyUtils.NumberOfVisitsGroupKey(result);
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by objects detected.
+ */
+ static class ObjectDetectedAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
+ return new DiscoveryKeyUtils.ObjectDetectedGroupKey((ResultFile) file);
+ }
+
+ @Override
+ public void addAttributeToResults(List results, SleuthkitCase caseDb,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+
+ // Get pairs of (object ID, object type name) for all files in the list of files that have
+ // objects detected
+ String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID(),
+ BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID());
+
+ ObjectDetectedNamesCallback callback = new ObjectDetectedNamesCallback(results);
+ try {
+ caseDb.getCaseDbAccessManager().select(selectQuery, callback);
+ } catch (TskCoreException ex) {
+ throw new DiscoveryException("Error looking up object detected attributes", ex); // NON-NLS
+ }
+ }
+
+ /**
+ * Callback to process the results of the CaseDbAccessManager select
+ * query. Will add the object type names to the list of ResultFile
+ * objects.
+ */
+ private static class ObjectDetectedNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
+
+ List results;
+
+ /**
+ * Create the callback.
+ *
+ * @param resultFiles List of files to add object detected names to.
+ */
+ ObjectDetectedNamesCallback(List results) {
+ this.results = results;
+ }
+
+ @Override
+ public void process(ResultSet rs) {
+ try {
+ // Create a temporary map of object ID to ResultFile
+ Map tempMap = new HashMap<>();
+ for (Result result : results) {
+ if (result.getType() == SearchData.Type.DOMAIN) {
+ return;
+ }
+ ResultFile file = (ResultFile) result;
+ tempMap.put(file.getFirstInstance().getId(), file);
+ }
+
+ while (rs.next()) {
+ try {
+ Long objId = rs.getLong("object_id"); // NON-NLS
+ String setName = rs.getString("set_name"); // NON-NLS
+
+ tempMap.get(objId).addObjectDetectedName(setName);
+
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Failed to get object detected names", ex); // NON-NLS
+ }
+ }
+ }
+ }
+
+ /**
+ * Attribute for grouping/sorting by tag name.
+ */
+ static class FileTagAttribute extends AttributeType {
+
+ @Override
+ public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
+ return new DiscoveryKeyUtils.FileTagGroupKey((ResultFile) file);
+ }
+
+ @Override
+ public void addAttributeToResults(List results, SleuthkitCase caseDb,
+ CentralRepository centralRepoDb) throws DiscoveryException {
+
+ try {
+ for (Result result : results) {
+ if (result.getType() == SearchData.Type.DOMAIN) {
+ return;
+ }
+ ResultFile file = (ResultFile) result;
+ List contentTags = caseDb.getContentTagsByContent(file.getFirstInstance());
+
+ for (ContentTag tag : contentTags) {
+ result.addTagName(tag.getName().getDisplayName());
+ }
+ }
+ } catch (TskCoreException ex) {
+ throw new DiscoveryException("Error looking up file tag attributes", ex); // NON-NLS
+ }
+ }
+ }
+
+ /**
+ * Enum for the attribute types that can be used for grouping.
+ */
+ @NbBundle.Messages({
+ "DiscoveryAttributes.GroupingAttributeType.fileType.displayName=File Type",
+ "DiscoveryAttributes.GroupingAttributeType.frequency.displayName=Past Occurrences",
+ "DiscoveryAttributes.GroupingAttributeType.keywordList.displayName=Keyword",
+ "DiscoveryAttributes.GroupingAttributeType.size.displayName=File Size",
+ "DiscoveryAttributes.GroupingAttributeType.datasource.displayName=Data Source",
+ "DiscoveryAttributes.GroupingAttributeType.parent.displayName=Parent Folder",
+ "DiscoveryAttributes.GroupingAttributeType.hash.displayName=Hash Set",
+ "DiscoveryAttributes.GroupingAttributeType.interestingItem.displayName=Interesting Item",
+ "DiscoveryAttributes.GroupingAttributeType.tag.displayName=Tag",
+ "DiscoveryAttributes.GroupingAttributeType.object.displayName=Object Detected",
+ "DiscoveryAttributes.GroupingAttributeType.mostRecentDate.displayName=Most Recent Activity Date",
+ "DiscoveryAttributes.GroupingAttributeType.firstDate.displayName=First Activity Date",
+ "DiscoveryAttributes.GroupingAttributeType.numberOfVisits.displayName=Number of Visits",
+ "DiscoveryAttributes.GroupingAttributeType.none.displayName=None"})
+ public enum GroupingAttributeType {
+ FILE_SIZE(new FileSizeAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_size_displayName()),
+ FREQUENCY(new FrequencyAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_frequency_displayName()),
+ KEYWORD_LIST_NAME(new KeywordListAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_keywordList_displayName()),
+ DATA_SOURCE(new DataSourceAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_datasource_displayName()),
+ PARENT_PATH(new ParentPathAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_parent_displayName()),
+ HASH_LIST_NAME(new HashHitsAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_hash_displayName()),
+ INTERESTING_ITEM_SET(new InterestingItemAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_interestingItem_displayName()),
+ FILE_TAG(new FileTagAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_tag_displayName()),
+ OBJECT_DETECTED(new ObjectDetectedAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_object_displayName()),
+ MOST_RECENT_DATE(new MostRecentActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_mostRecentDate_displayName()),
+ FIRST_DATE(new FirstActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_firstDate_displayName()),
+ NUMBER_OF_VISITS(new NumberOfVisitsAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_numberOfVisits_displayName()),
+ NO_GROUPING(new NoGroupingAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_none_displayName());
+
+ private final AttributeType attributeType;
+ private final String displayName;
+
+ /**
+ * Construct a new GroupingAttributeType enum value.
+ *
+ * @param attributeType The type of attribute this enum value was
+ * constructed for.
+ * @param displayName The display name for this grouping attribute
+ * type.
+ */
+ GroupingAttributeType(AttributeType attributeType, String displayName) {
+ this.attributeType = attributeType;
+ this.displayName = displayName;
+ }
+
+ @Override
+ public String toString() {
+ return displayName;
+ }
+
+ /**
+ * Get the type of attribute this enum value was constructed for.
+ *
+ * @return The type of attribute this enum value was constructed for.
+ */
+ public AttributeType getAttributeType() {
+ return attributeType;
+ }
+
+ /**
+ * Get the list of enums that are valid for grouping files.
+ *
+ * @return Enums that can be used to group files.
+ */
+ public static List getOptionsForGroupingForFiles() {
+ return Arrays.asList(FILE_SIZE, FREQUENCY, PARENT_PATH, OBJECT_DETECTED, HASH_LIST_NAME, INTERESTING_ITEM_SET);
+ }
+
+ /**
+ * Get the list of enums that are valid for grouping files.
+ *
+ * @return Enums that can be used to group files.
+ */
+ public static List getOptionsForGroupingForDomains() {
+ return Arrays.asList(FREQUENCY, MOST_RECENT_DATE, FIRST_DATE, NUMBER_OF_VISITS);
+ }
+ }
+
+ /**
+ * Computes the CR frequency of all the given hashes and updates the list of
+ * files.
+ *
+ * @param hashesToLookUp Hashes to find the frequency of.
+ * @param currentFiles List of files to update with frequencies.
+ * @param centralRepoDb The central repository being used.
+ */
+ private static void computeFrequency(Set hashesToLookUp, List currentFiles, CentralRepository centralRepoDb) {
+
+ if (hashesToLookUp.isEmpty()) {
+ return;
+ }
+
+ String hashes = String.join("','", hashesToLookUp);
+ hashes = "'" + hashes + "'";
+ try {
+ CorrelationAttributeInstance.Type attributeType = centralRepoDb.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
+ String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType);
+
+ String selectClause = " value, COUNT(value) FROM "
+ + "(SELECT DISTINCT case_id, value FROM " + tableName
+ + " WHERE value IN ("
+ + hashes
+ + ")) AS foo GROUP BY value";
+
+ FrequencyCallback callback = new FrequencyCallback(currentFiles);
+ centralRepoDb.processSelectClause(selectClause, callback);
+
+ } catch (CentralRepoException ex) {
+ logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
+ }
+
+ }
+
+ /**
+ * Private helper method to create a set name clause to be used in queries.
+ *
+ * @param results The list of results to create the set name clause
+ * for.
+ * @param artifactTypeID The Blackboard Artifact type ID for the artifact
+ * type.
+ * @param setNameAttrID The set name attribute id.
+ *
+ * @return The String to use as a set name clause in queries.
+ *
+ * @throws DiscoveryException
+ */
+ private static String createSetNameClause(List results,
+ int artifactTypeID, int setNameAttrID) throws DiscoveryException {
+
+ // Concatenate the object IDs in the list of files
+ String objIdList = ""; // NON-NLS
+ for (Result result : results) {
+ if (result.getType() == SearchData.Type.DOMAIN) {
+ break;
+ }
+ ResultFile file = (ResultFile) result;
+ if (!objIdList.isEmpty()) {
+ objIdList += ","; // NON-NLS
+ }
+ objIdList += "\'" + file.getFirstInstance().getId() + "\'"; // NON-NLS
+ }
+
+ // Get pairs of (object ID, set name) for all files in the list of files that have
+ // the given artifact type.
+ return "blackboard_artifacts.obj_id AS object_id, blackboard_attributes.value_text AS set_name "
+ + "FROM blackboard_artifacts "
+ + "INNER JOIN blackboard_attributes ON blackboard_artifacts.artifact_id=blackboard_attributes.artifact_id "
+ + "WHERE blackboard_attributes.artifact_type_id=\'" + artifactTypeID + "\' "
+ + "AND blackboard_attributes.attribute_type_id=\'" + setNameAttrID + "\' "
+ + "AND blackboard_artifacts.obj_id IN (" + objIdList
+ + ") "; // NON-NLS
+ }
+
+ /**
+ * Private constructor for DiscoveryAttributes class.
+ */
+ private DiscoveryAttributes() {
+ // Class should not be instantiated
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryEventUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java
similarity index 58%
rename from Core/src/org/sleuthkit/autopsy/discovery/DiscoveryEventUtils.java
rename to Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java
index ea5fca6676..67c69904fa 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryEventUtils.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java
@@ -16,20 +16,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.sleuthkit.autopsy.discovery;
+package org.sleuthkit.autopsy.discovery.search;
import com.google.common.eventbus.EventBus;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import org.sleuthkit.autopsy.discovery.FileSearch.GroupKey;
-import org.sleuthkit.autopsy.discovery.FileSearchData.FileType;
+import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
+import org.sleuthkit.autopsy.discovery.search.SearchData.Type;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Class to handle event bus and events for discovery tool.
*/
-final class DiscoveryEventUtils {
+public final class DiscoveryEventUtils {
private final static EventBus discoveryEventBus = new EventBus();
@@ -38,7 +38,7 @@ final class DiscoveryEventUtils {
*
* @return The discovery event bus.
*/
- static EventBus getDiscoveryEventBus() {
+ public static EventBus getDiscoveryEventBus() {
return discoveryEventBus;
}
@@ -52,38 +52,39 @@ final class DiscoveryEventUtils {
/**
* Event to signal the start of a search being performed.
*/
- static final class SearchStartedEvent {
+ public static final class SearchStartedEvent {
- private final FileType fileType;
+ private final Type type;
/**
- * Construct a new SearchStartedEvent
+ * Construct a new SearchStartedEvent.
*
- * @param type The type of file the search event is for.
+ * @param type The type of result the search event is for.
*/
- SearchStartedEvent(FileType type) {
- this.fileType = type;
+ public SearchStartedEvent(Type type) {
+ this.type = type;
}
/**
- * Get the type of file the search is being performed for.
+ * Get the type of result the search is being performed for.
*
- * @return The type of files being searched for.
+ * @return The type of results being searched for.
*/
- FileType getType() {
- return fileType;
+ public Type getType() {
+ return type;
}
+
}
/**
* Event to signal that the Instances list should have selection cleared.
*/
- static final class ClearInstanceSelectionEvent {
+ public static final class ClearInstanceSelectionEvent {
/**
* Construct a new ClearInstanceSelectionEvent.
*/
- ClearInstanceSelectionEvent() {
+ public ClearInstanceSelectionEvent() {
//no arg constructor
}
}
@@ -91,21 +92,23 @@ final class DiscoveryEventUtils {
/**
* Event to signal that the Instances list should be populated.
*/
- static final class PopulateInstancesListEvent {
+ public static final class PopulateInstancesListEvent {
private final List instances;
/**
* Construct a new PopulateInstancesListEvent.
*/
- PopulateInstancesListEvent(List files) {
+ public PopulateInstancesListEvent(List files) {
instances = files;
}
/**
- * @return the instances
+ * Get the list of AbstractFiles for the instances list.
+ *
+ * @return The list of AbstractFiles for the instances list.
*/
- List getInstances() {
+ public List getInstances() {
return Collections.unmodifiableList(instances);
}
}
@@ -113,13 +116,13 @@ final class DiscoveryEventUtils {
/**
* Event to signal the completion of a search being performed.
*/
- static final class SearchCompleteEvent {
+ public static final class SearchCompleteEvent {
private final Map groupMap;
- private final List searchFilters;
- private final FileSearch.AttributeType groupingAttribute;
- private final FileGroup.GroupSortingAlgorithm groupSort;
- private final FileSorter.SortingMethod fileSortMethod;
+ private final List searchFilters;
+ private final DiscoveryAttributes.AttributeType groupingAttribute;
+ private final Group.GroupSortingAlgorithm groupSort;
+ private final ResultsSorter.SortingMethod sortMethod;
/**
* Construct a new SearchCompleteEvent,
@@ -130,16 +133,16 @@ final class DiscoveryEventUtils {
* search.
* @param groupingAttribute The grouping attribute used by the search.
* @param groupSort The sorting algorithm used for groups.
- * @param fileSortMethod The sorting method used for files.
+ * @param sortMethod The sorting method used for results.
*/
- SearchCompleteEvent(Map groupMap, List searchfilters,
- FileSearch.AttributeType groupingAttribute, FileGroup.GroupSortingAlgorithm groupSort,
- FileSorter.SortingMethod fileSortMethod) {
+ public SearchCompleteEvent(Map groupMap, List searchfilters,
+ DiscoveryAttributes.AttributeType groupingAttribute, Group.GroupSortingAlgorithm groupSort,
+ ResultsSorter.SortingMethod sortMethod) {
this.groupMap = groupMap;
this.searchFilters = searchfilters;
this.groupingAttribute = groupingAttribute;
this.groupSort = groupSort;
- this.fileSortMethod = fileSortMethod;
+ this.sortMethod = sortMethod;
}
/**
@@ -147,16 +150,16 @@ final class DiscoveryEventUtils {
*
* @return The map of groups which were found by the search.
*/
- Map getGroupMap() {
+ public Map getGroupMap() {
return Collections.unmodifiableMap(groupMap);
}
/**
- * Get the file filters used by the search.
+ * Get the filters used by the search.
*
* @return The search filters which were used by the search.
*/
- List getFilters() {
+ public List getFilters() {
return Collections.unmodifiableList(searchFilters);
}
@@ -165,7 +168,7 @@ final class DiscoveryEventUtils {
*
* @return The grouping attribute used by the search.
*/
- FileSearch.AttributeType getGroupingAttr() {
+ public DiscoveryAttributes.AttributeType getGroupingAttr() {
return groupingAttribute;
}
@@ -174,17 +177,17 @@ final class DiscoveryEventUtils {
*
* @return The sorting algorithm used for groups.
*/
- FileGroup.GroupSortingAlgorithm getGroupSort() {
+ public Group.GroupSortingAlgorithm getGroupSort() {
return groupSort;
}
/**
- * Get the sorting method used for files.
+ * Get the sorting method used for results.
*
- * @return The sorting method used for files.
+ * @return The sorting method used for results.
*/
- FileSorter.SortingMethod getFileSort() {
- return fileSortMethod;
+ public ResultsSorter.SortingMethod getResultSort() {
+ return sortMethod;
}
}
@@ -193,31 +196,31 @@ final class DiscoveryEventUtils {
* Event to signal the completion of page retrieval and include the page
* contents.
*/
- static final class PageRetrievedEvent {
+ public static final class PageRetrievedEvent {
- private final List results;
+ private final List results;
private final int page;
- private final FileType resultType;
+ private final Type resultType;
/**
* Construct a new PageRetrievedEvent.
*
- * @param resultType The type of files which exist in the page.
+ * @param resultType The type of results which exist in the page.
* @param page The number of the page which was retrieved.
- * @param results The list of files in the page retrieved.
+ * @param results The list of results in the page retrieved.
*/
- PageRetrievedEvent(FileType resultType, int page, List results) {
+ public PageRetrievedEvent(Type resultType, int page, List results) {
this.results = results;
this.page = page;
this.resultType = resultType;
}
/**
- * Get the list of files in the page retrieved.
+ * Get the list of results in the page retrieved.
*
- * @return The list of files in the page retrieved.
+ * @return The list of results in the page retrieved.
*/
- List getSearchResults() {
+ public List getSearchResults() {
return Collections.unmodifiableList(results);
}
@@ -226,16 +229,16 @@ final class DiscoveryEventUtils {
*
* @return The number of the page which was retrieved.
*/
- int getPageNumber() {
+ public int getPageNumber() {
return page;
}
/**
- * Get the type of files which exist in the page.
+ * Get the type of results which exist in the page.
*
- * @return The type of files which exist in the page.
+ * @return The type of results which exist in the page.
*/
- FileType getType() {
+ public Type getType() {
return resultType;
}
}
@@ -243,25 +246,25 @@ final class DiscoveryEventUtils {
/**
* Event to signal that there were no results for the search.
*/
- static final class NoResultsEvent {
+ public static final class NoResultsEvent {
/**
* Construct a new NoResultsEvent.
*/
- NoResultsEvent() {
+ public NoResultsEvent() {
//no arg constructor
}
}
/**
- * Event to signal that a search has been cancelled
+ * Event to signal that a search has been cancelled.
*/
- static final class SearchCancelledEvent {
+ public static final class SearchCancelledEvent {
/**
* Construct a new SearchCancelledEvent.
*/
- SearchCancelledEvent() {
+ public SearchCancelledEvent() {
//no arg constructor
}
@@ -270,15 +273,15 @@ final class DiscoveryEventUtils {
/**
* Event to signal that a group has been selected.
*/
- static final class GroupSelectedEvent {
+ public static final class GroupSelectedEvent {
- private final FileType resultType;
+ private final Type resultType;
private final GroupKey groupKey;
private final int groupSize;
- private final List searchfilters;
- private final FileSearch.AttributeType groupingAttribute;
- private final FileGroup.GroupSortingAlgorithm groupSort;
- private final FileSorter.SortingMethod fileSortMethod;
+ private final List searchfilters;
+ private final DiscoveryAttributes.AttributeType groupingAttribute;
+ private final Group.GroupSortingAlgorithm groupSort;
+ private final ResultsSorter.SortingMethod sortMethod;
/**
* Construct a new GroupSelectedEvent.
@@ -287,31 +290,32 @@ final class DiscoveryEventUtils {
* search.
* @param groupingAttribute The grouping attribute used by the search.
* @param groupSort The sorting algorithm used for groups.
- * @param fileSortMethod The sorting method used for files.
+ * @param sortMethod The sorting method used for results.
* @param groupKey The key associated with the group which was
* selected.
- * @param groupSize The number of files in the group which was
+ * @param groupSize The number of results in the group which was
* selected.
- * @param resultType The type of files which exist in the group.
+ * @param resultType The type of results which exist in the
+ * group.
*/
- GroupSelectedEvent(List searchfilters,
- FileSearch.AttributeType groupingAttribute, FileGroup.GroupSortingAlgorithm groupSort,
- FileSorter.SortingMethod fileSortMethod, GroupKey groupKey, int groupSize, FileType resultType) {
+ public GroupSelectedEvent(List searchfilters,
+ DiscoveryAttributes.AttributeType groupingAttribute, Group.GroupSortingAlgorithm groupSort,
+ ResultsSorter.SortingMethod sortMethod, GroupKey groupKey, int groupSize, Type resultType) {
this.searchfilters = searchfilters;
this.groupingAttribute = groupingAttribute;
this.groupSort = groupSort;
- this.fileSortMethod = fileSortMethod;
+ this.sortMethod = sortMethod;
this.groupKey = groupKey;
this.groupSize = groupSize;
this.resultType = resultType;
}
/**
- * Get the type of files which exist in the group.
+ * Get the type of results which exist in the group.
*
- * @return The type of files which exist in the group.
+ * @return The type of results which exist in the group.
*/
- FileType getResultType() {
+ public Type getResultType() {
return resultType;
}
@@ -322,16 +326,16 @@ final class DiscoveryEventUtils {
* @return The group key which is used to uniquely identify the group
* selected.
*/
- GroupKey getGroupKey() {
+ public GroupKey getGroupKey() {
return groupKey;
}
/**
- * Get the number of files in the group which was selected.
+ * Get the number of results in the group which was selected.
*
- * @return The number of files in the group which was selected.
+ * @return The number of results in the group which was selected.
*/
- int getGroupSize() {
+ public int getGroupSize() {
return groupSize;
}
@@ -340,25 +344,25 @@ final class DiscoveryEventUtils {
*
* @return The sorting algorithm used for groups.
*/
- FileGroup.GroupSortingAlgorithm getGroupSort() {
+ public Group.GroupSortingAlgorithm getGroupSort() {
return groupSort;
}
/**
- * Get the sorting method used for files in the group.
+ * Get the sorting method used for results in the group.
*
- * @return The sorting method used for files.
+ * @return The sorting method used for results.
*/
- FileSorter.SortingMethod getFileSort() {
- return fileSortMethod;
+ public ResultsSorter.SortingMethod getResultSort() {
+ return sortMethod;
}
/**
- * Get the file filters which were used by the search
+ * Get the result filters which were used by the search.
*
* @return The search filters which were used by the search.
*/
- List getFilters() {
+ public List getFilters() {
return Collections.unmodifiableList(searchfilters);
}
@@ -367,7 +371,7 @@ final class DiscoveryEventUtils {
*
* @return The grouping attribute used by the search.
*/
- FileSearch.AttributeType getGroupingAttr() {
+ public DiscoveryAttributes.AttributeType getGroupingAttr() {
return groupingAttribute;
}
@@ -376,7 +380,7 @@ final class DiscoveryEventUtils {
/**
* Event to signal that the visibility of the Details area should change.
*/
- static class DetailsVisibleEvent {
+ public static class DetailsVisibleEvent {
private final boolean showDetailsArea;
@@ -386,7 +390,7 @@ final class DiscoveryEventUtils {
* @param isVisible True if the details area should be visible, false
* otherwise.
*/
- DetailsVisibleEvent(boolean isVisible) {
+ public DetailsVisibleEvent(boolean isVisible) {
showDetailsArea = isVisible;
}
@@ -395,7 +399,7 @@ final class DiscoveryEventUtils {
*
* @return True if the details area should be visible, false otherwise.
*/
- boolean isShowDetailsArea() {
+ public boolean isShowDetailsArea() {
return showDetailsArea;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/FileSearchException.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryException.java
similarity index 82%
rename from Core/src/org/sleuthkit/autopsy/discovery/FileSearchException.java
rename to Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryException.java
index df22e26488..13a4f87fa0 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/FileSearchException.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryException.java
@@ -16,12 +16,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.sleuthkit.autopsy.discovery;
+package org.sleuthkit.autopsy.discovery.search;
/**
- * Exception type used for FileSearch.
+ * Exception type used for Discovery search.
*/
-final public class FileSearchException extends Exception {
+final public class DiscoveryException extends Exception {
private static final long serialVersionUID = 1L;
@@ -30,7 +30,7 @@ final public class FileSearchException extends Exception {
*
* @param message The message to associate with this exception.
*/
- FileSearchException(String message) {
+ DiscoveryException(String message) {
super(message);
}
@@ -40,7 +40,7 @@ final public class FileSearchException extends Exception {
* @param message The message to associate with this exception.
* @param cause The Throwable cause of the exception.
*/
- FileSearchException(String message, Throwable cause) {
+ DiscoveryException(String message, Throwable cause) {
super(message, cause);
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java
new file mode 100644
index 0000000000..9fe4fbf946
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java
@@ -0,0 +1,1390 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.discovery.search;
+
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Utility class for constructing keys for groups and searches.
+ */
+public class DiscoveryKeyUtils {
+
+ private final static Logger logger = Logger.getLogger(DiscoveryKeyUtils.class.getName());
+
+ /**
+ * Represents a key for a specific search for a specific user.
+ */
+ static class SearchKey implements Comparable {
+
+ private final String keyString;
+ private final Group.GroupSortingAlgorithm groupSortingType;
+ private final DiscoveryAttributes.AttributeType groupAttributeType;
+ private final ResultsSorter.SortingMethod sortingMethod;
+ private final List filters;
+ private final SleuthkitCase sleuthkitCase;
+ private final CentralRepository centralRepository;
+
+ /**
+ * Construct a new SearchKey with all information that defines a search.
+ *
+ * @param userName The name of the user performing the search.
+ * @param filters The Filters being used for the search.
+ * @param groupAttributeType The AttributeType to group by.
+ * @param groupSortingType The algorithm to sort the groups by.
+ * @param sortingMethod The method to sort the results by.
+ * @param sleuthkitCase The SleuthkitCase being searched.
+ * @param centralRepository The Central Repository being searched.
+ */
+ SearchKey(String userName, List filters,
+ DiscoveryAttributes.AttributeType groupAttributeType,
+ Group.GroupSortingAlgorithm groupSortingType,
+ ResultsSorter.SortingMethod sortingMethod,
+ SleuthkitCase sleuthkitCase, CentralRepository centralRepository) {
+ this.groupAttributeType = groupAttributeType;
+ this.groupSortingType = groupSortingType;
+ this.sortingMethod = sortingMethod;
+ this.filters = filters;
+
+ StringBuilder searchStringBuilder = new StringBuilder();
+ searchStringBuilder.append(userName);
+ for (AbstractFilter filter : filters) {
+ searchStringBuilder.append(filter.toString());
+ }
+ searchStringBuilder.append(groupAttributeType).append(groupSortingType).append(sortingMethod);
+ keyString = searchStringBuilder.toString();
+ this.sleuthkitCase = sleuthkitCase;
+ this.centralRepository = centralRepository;
+ }
+
+ /**
+ * Construct a SearchKey without a SleuthkitCase or CentralRepositry
+ * instance.
+ *
+ * @param userName The name of the user performing the search.
+ * @param filters The Filters being used for the search.
+ * @param groupAttributeType The AttributeType to group by.
+ * @param groupSortingType The algorithm to sort the groups by.
+ * @param sortingMethod The method to sort the results by.
+ */
+ SearchKey(String userName, List filters,
+ DiscoveryAttributes.AttributeType groupAttributeType,
+ Group.GroupSortingAlgorithm groupSortingType,
+ ResultsSorter.SortingMethod sortingMethod) {
+ this(userName, filters, groupAttributeType, groupSortingType,
+ sortingMethod, null, null);
+ }
+
+ @Override
+ public int compareTo(SearchKey otherSearchKey) {
+ return getKeyString().compareTo(otherSearchKey.getKeyString());
+ }
+
+ @Override
+ public boolean equals(Object otherKey) {
+ if (otherKey == this) {
+ return true;
+ }
+
+ if (!(otherKey instanceof SearchKey)) {
+ return false;
+ }
+
+ SearchKey otherSearchKey = (SearchKey) otherKey;
+ if (this.sleuthkitCase != otherSearchKey.getSleuthkitCase()
+ || this.centralRepository != otherSearchKey.getCentralRepository()) {
+ return false;
+ }
+
+ return getKeyString().equals(otherSearchKey.getKeyString());
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 79 * hash + Objects.hashCode(getKeyString());
+ return hash;
+ }
+
+ /**
+ * Get the String representation of this key.
+ *
+ * @return The String representation of this key.
+ */
+ String getKeyString() {
+ return keyString;
+ }
+
+ /**
+ * Get the list of filters associated with this key.
+ *
+ * @return The list of filters associated with this key.
+ */
+ List getFilters() {
+ return Collections.unmodifiableList(this.filters);
+ }
+
+ /**
+ * Get the group sorting type for this key.
+ *
+ * @return The group sorting type for this key.
+ */
+ Group.GroupSortingAlgorithm getGroupSortingType() {
+ return groupSortingType;
+ }
+
+ /**
+ * Get the grouping attribute for this key.
+ *
+ * @return The grouping attribute for this key.
+ */
+ DiscoveryAttributes.AttributeType getGroupAttributeType() {
+ return groupAttributeType;
+ }
+
+ /**
+ * Get the SortingMethod for this key.
+ *
+ * @return The SortingMethod for this key.
+ */
+ ResultsSorter.SortingMethod getFileSortingMethod() {
+ return sortingMethod;
+ }
+
+ /**
+ * Get the case database for this key.
+ *
+ * @return The case database for this key.
+ */
+ SleuthkitCase getSleuthkitCase() {
+ return this.sleuthkitCase;
+ }
+
+ /**
+ * Get the central repository for this key.
+ *
+ * @return The central repository for this key.
+ */
+ CentralRepository getCentralRepository() {
+ return this.centralRepository;
+ }
+ }
+
+ /**
+ * The key used for grouping for each attribute type.
+ */
+ public abstract static class GroupKey implements Comparable {
+
+ /**
+ * Get the string version of the group key for display. Each display
+ * name should correspond to a unique GroupKey object.
+ *
+ * @return The display name for this key
+ */
+ abstract String getDisplayName();
+
+ /**
+ * Subclasses must implement equals().
+ *
+ * @param otherKey The GroupKey to compare to this key.
+ *
+ * @return true if the keys are equal, false otherwise
+ */
+ @Override
+ abstract public boolean equals(Object otherKey);
+
+ /**
+ * Subclasses must implement hashCode().
+ *
+ * @return The hash code for the GroupKey.
+ */
+ @Override
+ abstract public int hashCode();
+
+ /**
+ * It should not happen with the current setup, but we need to cover the
+ * case where two different GroupKey subclasses are compared against
+ * each other. Use a lexicographic comparison on the class names.
+ *
+ * @param otherGroupKey The other group key.
+ *
+ * @return Result of alphabetical comparison on the class name.
+ */
+ int compareClassNames(GroupKey otherGroupKey) {
+ return this.getClass().getName().compareTo(otherGroupKey.getClass().getName());
+ }
+
+ @Override
+ public String toString() {
+ return getDisplayName();
+ }
+ }
+
+ /**
+ * Key representing a file size group.
+ */
+ static class FileSizeGroupKey extends GroupKey {
+
+ private final SearchData.FileSize fileSize;
+
+ /**
+ * Construct a new FileSizeGroupKey.
+ *
+ * @param file The file to create the group key for.
+ */
+ FileSizeGroupKey(Result file) {
+ ResultFile resultFile = (ResultFile) file;
+ if (resultFile.getFileType() == SearchData.Type.VIDEO) {
+ fileSize = SearchData.FileSize.fromVideoSize(resultFile.getFirstInstance().getSize());
+ } else {
+ fileSize = SearchData.FileSize.fromImageSize(resultFile.getFirstInstance().getSize());
+ }
+ }
+
+ @Override
+ String getDisplayName() {
+ return getFileSize().toString();
+ }
+
+ @Override
+ public int compareTo(GroupKey otherGroupKey) {
+ if (otherGroupKey instanceof FileSizeGroupKey) {
+ FileSizeGroupKey otherFileSizeGroupKey = (FileSizeGroupKey) otherGroupKey;
+ return Integer.compare(getFileSize().getRanking(), otherFileSizeGroupKey.getFileSize().getRanking());
+ } else {
+ return compareClassNames(otherGroupKey);
+ }
+ }
+
+ @Override
+ public boolean equals(Object otherKey) {
+ if (otherKey == this) {
+ return true;
+ }
+
+ if (!(otherKey instanceof FileSizeGroupKey)) {
+ return false;
+ }
+
+ FileSizeGroupKey otherFileSizeGroupKey = (FileSizeGroupKey) otherKey;
+ return getFileSize().equals(otherFileSizeGroupKey.getFileSize());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getFileSize().getRanking());
+ }
+
+ /**
+ * The size of the file.
+ *
+ * @return The size of the file.
+ */
+ SearchData.FileSize getFileSize() {
+ return fileSize;
+ }
+ }
+
+ /**
+ * Key representing a file type group.
+ */
+ static class FileTypeGroupKey extends GroupKey {
+
+ private final SearchData.Type fileType;
+
+ /**
+ * Construct a new FileTypeGroupKey.
+ *
+ * @param file The file to create the group key for.
+ */
+ FileTypeGroupKey(Result file) {
+ fileType = ((ResultFile) file).getFileType();
+ }
+
+ @Override
+ String getDisplayName() {
+ return getFileType().toString();
+ }
+
+ @Override
+ public int compareTo(GroupKey otherGroupKey) {
+ if (otherGroupKey instanceof FileTypeGroupKey) {
+ FileTypeGroupKey otherFileTypeGroupKey = (FileTypeGroupKey) otherGroupKey;
+ return Integer.compare(getFileType().getRanking(), otherFileTypeGroupKey.getFileType().getRanking());
+ } else {
+ return compareClassNames(otherGroupKey);
+ }
+ }
+
+ @Override
+ public boolean equals(Object otherKey) {
+ if (otherKey == this) {
+ return true;
+ }
+
+ if (!(otherKey instanceof FileTypeGroupKey)) {
+ return false;
+ }
+
+ FileTypeGroupKey otherFileTypeGroupKey = (FileTypeGroupKey) otherKey;
+ return getFileType().equals(otherFileTypeGroupKey.getFileType());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getFileType().getRanking());
+ }
+
+ /**
+ * Get the type of file the group exists for.
+ *
+ * @return The type of file the group exists for.
+ */
+ SearchData.Type getFileType() {
+ return fileType;
+ }
+ }
+
+ /**
+ * Key representing a keyword list group.
+ */
+ static class KeywordListGroupKey extends GroupKey {
+
+ private final List keywordListNames;
+ private final String keywordListNamesString;
+
+ /**
+ * Construct a new KeywordListGroupKey.
+ *
+ * @param file The file to create the group key for.
+ */
+ @NbBundle.Messages({
+ "DiscoveryKeyUtils.KeywordListGroupKey.noKeywords=None"})
+ KeywordListGroupKey(ResultFile file) {
+ keywordListNames = file.getKeywordListNames();
+ if (keywordListNames.isEmpty()) {
+ keywordListNamesString = Bundle.DiscoveryKeyUtils_KeywordListGroupKey_noKeywords();
+ } else {
+ keywordListNamesString = String.join(",", keywordListNames); // NON-NLS
+ }
+ }
+
+ @Override
+ String getDisplayName() {
+ return getKeywordListNamesString();
+ }
+
+ @Override
+ public int compareTo(GroupKey otherGroupKey) {
+ if (otherGroupKey instanceof KeywordListGroupKey) {
+ KeywordListGroupKey otherKeywordListNamesGroupKey = (KeywordListGroupKey) otherGroupKey;
+
+ // Put the empty list at the end
+ if (getKeywordListNames().isEmpty()) {
+ if (otherKeywordListNamesGroupKey.getKeywordListNames().isEmpty()) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if (otherKeywordListNamesGroupKey.getKeywordListNames().isEmpty()) {
+ return -1;
+ }
+
+ return getKeywordListNamesString().compareTo(otherKeywordListNamesGroupKey.getKeywordListNamesString());
+ } else {
+ return compareClassNames(otherGroupKey);
+ }
+ }
+
+ @Override
+ public boolean equals(Object otherKey) {
+ if (otherKey == this) {
+ return true;
+ }
+
+ if (!(otherKey instanceof KeywordListGroupKey)) {
+ return false;
+ }
+
+ KeywordListGroupKey otherKeywordListGroupKey = (KeywordListGroupKey) otherKey;
+ return getKeywordListNamesString().equals(otherKeywordListGroupKey.getKeywordListNamesString());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getKeywordListNamesString());
+ }
+
+ /**
+ * Get the list of keywords this group is for.
+ *
+ * @return The list of keywords this group is for.
+ */
+ List getKeywordListNames() {
+ return Collections.unmodifiableList(keywordListNames);
+ }
+
+ /**
+ * Get the string which represents the keyword names represented by this
+ * group key.
+ *
+ * @return The string which represents the keyword names represented by
+ * this group key.
+ */
+ String getKeywordListNamesString() {
+ return keywordListNamesString;
+ }
+ }
+
+ /**
+ * Key representing a file tag group.
+ */
+ static class FileTagGroupKey extends GroupKey {
+
+ private final List