diff --git a/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java
index c5298bc64e..25bacd3761 100644
--- a/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java
+++ b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java
@@ -44,7 +44,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
*/
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.apputils.ResetWindowsAction")
@ActionReferences(value = {
- @ActionReference(path = "Menu/Window", position = 105)})
+ @ActionReference(path = "Menu/Window", position = 205)})
@ActionRegistration(displayName = "#CTL_ResetWindowsAction", lazy = false)
@NbBundle.Messages({"CTL_ResetWindowsAction=Reset Windows"})
public final class ResetWindowsAction extends CallableSystemAction {
diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml
index f9f537541d..3a8865f643 100644
--- a/Core/src/org/sleuthkit/autopsy/core/layer.xml
+++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml
@@ -250,18 +250,10 @@
-
-
-
-
-
-
-
-
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResults.java b/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResults.java
new file mode 100644
index 0000000000..fc51d97cc9
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResults.java
@@ -0,0 +1,95 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 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.datamodel;
+
+import org.openide.nodes.Children;
+import org.openide.util.NbBundle;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+
+/**
+ * Analysis Results node support.
+ */
+@NbBundle.Messages({
+ "AnalysisResults_name=Analysis Results",})
+public class AnalysisResults implements AutopsyVisitableItem {
+
+ /**
+ * Returns the name of this node that is the key in the children object.
+ *
+ * @return The name of this node that is the key in the children object.
+ */
+ public static String getName() {
+ return Bundle.AnalysisResults_name();
+ }
+
+ /**
+ * Parent node of all analysis results.
+ */
+ static class RootNode extends Artifacts.BaseArtifactNode {
+
+ /**
+ * Main constructor.
+ *
+ * @param filteringDSObjId The data source object id for which results
+ * should be filtered. If no filtering should
+ * occur, this number should be less than or
+ * equal to 0.
+ */
+ RootNode(long filteringDSObjId) {
+ super(Children.create(new Artifacts.TypeFactory(BlackboardArtifact.Category.ANALYSIS_RESULT, filteringDSObjId), true),
+ "org/sleuthkit/autopsy/images/analysis_result.png",
+ AnalysisResults.getName(),
+ AnalysisResults.getName());
+ }
+ }
+
+ private final long datasourceObjId;
+
+ /**
+ * Main constructor.
+ */
+ public AnalysisResults() {
+ this(0);
+ }
+
+ /**
+ * Main constructor.
+ *
+ * @param dsObjId The data source object id.
+ */
+ public AnalysisResults(long dsObjId) {
+ this.datasourceObjId = dsObjId;
+ }
+
+ @Override
+ public T accept(AutopsyItemVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ /**
+ * Returns whether or not there is a data source object for which results
+ * should be filtered.
+ *
+ * @return Whether or not there is a data source object for which results
+ * should be filtered.
+ */
+ Long getFilteringDataSourceObjId() {
+ return datasourceObjId;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java
new file mode 100644
index 0000000000..d39bef3ee9
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java
@@ -0,0 +1,704 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-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.datamodel;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.openide.nodes.ChildFactory;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.nodes.Sheet;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.Lookups;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
+import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
+import org.sleuthkit.datamodel.BlackboardArtifact.Category;
+import org.python.google.common.collect.Sets;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ACCOUNT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_DATA_SOURCE_USAGE;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_EMAIL_MSG;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_HASHSET_HIT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_GEN_INFO;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_KEYWORD_HIT;
+
+/**
+ * Classes for creating nodes for BlackboardArtifacts.
+ */
+public class Artifacts {
+
+ private static final Set INGEST_JOB_EVENTS_OF_INTEREST
+ = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
+
+ /**
+ * Base class for a parent node of artifacts.
+ */
+ static class BaseArtifactNode extends DisplayableItemNode {
+
+ /**
+ * Main constructor.
+ *
+ * @param children The children of the node.
+ * @param icon The icon for the node.
+ * @param name The name identifier of the node.
+ * @param displayName The display name for the node.
+ */
+ BaseArtifactNode(Children children, String icon, String name, String displayName) {
+ super(children, Lookups.singleton(name));
+ super.setName(name);
+ super.setDisplayName(displayName);
+ this.setIconBaseWithExtension(icon); //NON-NLS
+ }
+
+ @Override
+ public boolean isLeafTypeNode() {
+ return false;
+ }
+
+ @Override
+ public T accept(DisplayableItemNodeVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ @Override
+ protected Sheet createSheet() {
+ Sheet sheet = super.createSheet();
+ Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
+ if (sheetSet == null) {
+ sheetSet = Sheet.createPropertiesSet();
+ sheet.put(sheetSet);
+ }
+
+ sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.name"),
+ NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.displayName"),
+ NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.desc"),
+ super.getDisplayName()));
+ return sheet;
+ }
+
+ @Override
+ public String getItemType() {
+ return getClass().getName();
+ }
+ }
+
+ /**
+ * A key to be used with the type factory.
+ */
+ private static class TypeNodeKey {
+
+ private final UpdatableCountTypeNode node;
+ private final Set applicableTypes;
+
+ /**
+ * Constructor generating a generic TypeNode for a given artifact type.
+ *
+ * @param type The type for the key.
+ * @param dsObjId The data source object id if filtering should occur.
+ * If no filtering should occur, this number should be
+ * less than or equal to 0.
+ */
+ TypeNodeKey(BlackboardArtifact.Type type, long dsObjId) {
+ this(new TypeNode(type, dsObjId), type);
+ }
+
+ /**
+ * Constructor for any UpdatableCountTypeNode.
+ *
+ * @param typeNode The UpdatableCountTypeNode.
+ * @param types The blackboard artifact types corresponding to this
+ * node.
+ */
+ TypeNodeKey(UpdatableCountTypeNode typeNode, BlackboardArtifact.Type... types) {
+ this.node = typeNode;
+ this.applicableTypes = Stream.of(types)
+ .filter(t -> t != null)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Returns the node associated with this key.
+ *
+ * @return The node associated with this key.
+ */
+ UpdatableCountTypeNode getNode() {
+ return node;
+ }
+
+ /**
+ * Returns the blackboard artifact types associated with this key.
+ *
+ * @return The blackboard artifact types associated with this key.
+ */
+ Set getApplicableTypes() {
+ return applicableTypes;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 61 * hash + Objects.hashCode(this.applicableTypes);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final TypeNodeKey other = (TypeNodeKey) obj;
+ if (!Objects.equals(this.applicableTypes, other.applicableTypes)) {
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ /**
+ * Factory for showing a list of artifact types (i.e. all the data artifact
+ * types).
+ */
+ static class TypeFactory extends ChildFactory.Detachable implements RefreshThrottler.Refresher {
+
+ private static final Logger logger = Logger.getLogger(TypeNode.class.getName());
+
+ /**
+ * Types that should not be shown in the tree.
+ */
+ @SuppressWarnings("deprecation")
+ private static final Set IGNORED_TYPES = Sets.newHashSet(
+ // these are shown in other parts of the UI (and different node types)
+ TSK_DATA_SOURCE_USAGE,
+ TSK_GEN_INFO,
+ new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE),
+ TSK_TL_EVENT,
+ //This is not meant to be shown in the UI at all. It is more of a meta artifact.
+ TSK_ASSOCIATED_OBJECT
+ );
+
+ /**
+ * Returns a Children key to be use for a particular artifact type.
+ *
+ * @param type The artifact type.
+ * @param skCase The relevant Sleuthkit case in order to create the
+ * node.
+ * @param dsObjId The data source object id to use for filtering. If id
+ * is less than or equal to 0, no filtering will occur.
+ *
+ * @return The generated key.
+ */
+ private static TypeNodeKey getTypeKey(BlackboardArtifact.Type type, SleuthkitCase skCase, long dsObjId) {
+ int typeId = type.getTypeID();
+ if (TSK_EMAIL_MSG.getTypeID() == typeId) {
+ EmailExtracted.RootNode emailNode = new EmailExtracted(skCase, dsObjId).new RootNode();
+ return new TypeNodeKey(emailNode, TSK_EMAIL_MSG);
+
+ } else if (TSK_ACCOUNT.getTypeID() == typeId) {
+ Accounts.AccountsRootNode accountsNode = new Accounts(skCase, dsObjId).new AccountsRootNode();
+ return new TypeNodeKey(accountsNode, TSK_ACCOUNT);
+
+ } else if (TSK_KEYWORD_HIT.getTypeID() == typeId) {
+ KeywordHits.RootNode keywordsNode = new KeywordHits(skCase, dsObjId).new RootNode();
+ return new TypeNodeKey(keywordsNode, TSK_KEYWORD_HIT);
+
+ } else if (TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == typeId
+ || TSK_INTERESTING_FILE_HIT.getTypeID() == typeId) {
+
+ InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, dsObjId).new RootNode();
+ return new TypeNodeKey(interestingHitsNode,
+ TSK_INTERESTING_ARTIFACT_HIT,
+ TSK_INTERESTING_FILE_HIT);
+
+ } else if (TSK_HASHSET_HIT.getTypeID() == typeId) {
+ HashsetHits.RootNode hashsetHits = new HashsetHits(skCase, dsObjId).new RootNode();
+ return new TypeNodeKey(hashsetHits, TSK_HASHSET_HIT);
+
+ } else {
+ return new TypeNodeKey(type, dsObjId);
+ }
+ }
+
+ // maps the artifact type to its child node
+ private final Map typeNodeMap = new HashMap<>();
+ private final long filteringDSObjId;
+
+ /**
+ * RefreshThrottler is used to limit the number of refreshes performed
+ * when CONTENT_CHANGED and DATA_ADDED ingest module events are
+ * received.
+ */
+ private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
+ private final Category category;
+
+ /**
+ * Main constructor.
+ *
+ * @param category The category of types to be displayed.
+ * @param filteringDSObjId The data source object id to use for
+ * filtering. If id is less than or equal to 0,
+ * no filtering will occur.
+ */
+ TypeFactory(Category category, long filteringDSObjId) {
+ super();
+ this.filteringDSObjId = filteringDSObjId;
+ this.category = category;
+ }
+
+ private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
+ String eventType = evt.getPropertyName();
+ if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
+ // case was closed. Remove listeners so that we don't get called with a stale case handle
+ if (evt.getNewValue() == null) {
+ removeNotify();
+ }
+ } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
+ || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
+ /**
+ * This is a stop gap measure until a different way of handling
+ * the closing of cases is worked out. Currently, remote events
+ * may be received for a case that is already closed.
+ */
+ try {
+ Case.getCurrentCaseThrows();
+ refresh(false);
+ } catch (NoCurrentCaseException notUsed) {
+ /**
+ * Case is closed, do nothing.
+ */
+ }
+ }
+ };
+
+ @Override
+ protected void addNotify() {
+ refreshThrottler.registerForIngestModuleEvents();
+ IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
+ }
+
+ @Override
+ protected void removeNotify() {
+ refreshThrottler.unregisterEventListener();
+ IngestManager.getInstance().removeIngestJobEventListener(pcl);
+ Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
+ typeNodeMap.clear();
+ }
+
+ @Override
+ protected boolean createKeys(List list) {
+ try {
+ // Get all types in use
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ List types = (this.filteringDSObjId > 0)
+ ? skCase.getBlackboard().getArtifactTypesInUse(this.filteringDSObjId)
+ : skCase.getArtifactTypesInUse();
+
+ List allKeysSorted = types.stream()
+ // filter types by category and ensure they are not in the list of ignored types
+ .filter(tp -> category.equals(tp.getCategory()) && !IGNORED_TYPES.contains(tp))
+ .map(tp -> {
+ // if typeNodeMap already contains key, update the relevant node and return the node
+ if (typeNodeMap.containsKey(tp)) {
+ TypeNodeKey typeKey = typeNodeMap.get(tp);
+ typeKey.getNode().updateDisplayName();
+ return typeKey;
+ } else {
+ // if key is not in map, create the type key and add to map
+ TypeNodeKey newTypeKey = getTypeKey(tp, skCase, filteringDSObjId);
+ for (BlackboardArtifact.Type recordType : newTypeKey.getApplicableTypes()) {
+ typeNodeMap.put(recordType, newTypeKey);
+ }
+ return newTypeKey;
+ }
+ })
+ // ensure record is returned
+ .filter(record -> record != null)
+ // there are potentially multiple types that apply to the same node (i.e. Interesting Files / Artifacts)
+ // ensure the keys are distinct
+ .distinct()
+ // sort by display name
+ .sorted((a, b) -> {
+ String aSafe = (a.getNode() == null || a.getNode().getDisplayName() == null) ? "" : a.getNode().getDisplayName();
+ String bSafe = (b.getNode() == null || b.getNode().getDisplayName() == null) ? "" : b.getNode().getDisplayName();
+ return aSafe.compareToIgnoreCase(bSafe);
+ })
+ .collect(Collectors.toList());
+
+ list.addAll(allKeysSorted);
+
+ } catch (NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, "Trying to access case when no case is open.", ex); //NON-NLS
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error getting list of artifacts in use: " + ex.getLocalizedMessage()); //NON-NLS
+ }
+ return true;
+ }
+
+ @Override
+ protected Node createNodeForKey(TypeNodeKey key) {
+ return key.getNode();
+ }
+
+ @Override
+ public void refresh() {
+ refresh(false);
+ }
+
+ @Override
+ public boolean isRefreshRequired(PropertyChangeEvent evt) {
+ String eventType = evt.getPropertyName();
+ if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
+ /**
+ * This is a stop gap measure until a different way of handling
+ * the closing of cases is worked out. Currently, remote events
+ * may be received for a case that is already closed.
+ */
+ try {
+ Case.getCurrentCaseThrows();
+ /**
+ * Due to some unresolved issues with how cases are closed,
+ * it is possible for the event to have a null oldValue if
+ * the event is a remote event.
+ */
+ final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
+ if (null != event && category.equals(event.getBlackboardArtifactType().getCategory())
+ && !(IGNORED_TYPES.contains(event.getBlackboardArtifactType()))) {
+ return true;
+ }
+ } catch (NoCurrentCaseException notUsed) {
+ /**
+ * Case is closed, do nothing.
+ */
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Abstract class for type(s) nodes. This class allows for displaying a
+ * count artifacts with the type(s) associated with this node.
+ */
+ public static abstract class UpdatableCountTypeNode extends DisplayableItemNode {
+
+ private static final Logger logger = Logger.getLogger(UpdatableCountTypeNode.class.getName());
+
+ private final Set types;
+ private final long filteringDSObjId;
+ private long childCount = 0;
+ private final String baseName;
+
+ /**
+ * Main constructor.
+ *
+ * @param children The Children to associated with this node.
+ * @param lookup The Lookup to use with this name.
+ * @param baseName The display name. The Node.displayName will
+ * be of format "[baseName] ([count])".
+ * @param filteringDSObjId The data source object id to use for
+ * filtering. If id is less than or equal to 0,
+ * no filtering will occur.
+ * @param types The types associated with this type node.
+ */
+ public UpdatableCountTypeNode(Children children, Lookup lookup, String baseName,
+ long filteringDSObjId, BlackboardArtifact.Type... types) {
+
+ super(children, lookup);
+ this.types = Stream.of(types).collect(Collectors.toSet());
+ this.filteringDSObjId = filteringDSObjId;
+ this.baseName = baseName;
+ updateDisplayName();
+ }
+
+ /**
+ * Returns the count of artifacts associated with these type(s).
+ *
+ * @return The count of artifacts associated with these type(s).
+ */
+ protected long getChildCount() {
+ return this.childCount;
+ }
+
+ /**
+ * Fetches the count to be displayed from the case.
+ *
+ * @param skCase The relevant SleuthkitCase.
+ *
+ * @return The count to be displayed.
+ *
+ * @throws TskCoreException
+ */
+ protected long fetchChildCount(SleuthkitCase skCase) throws TskCoreException {
+ int count = 0;
+ for (BlackboardArtifact.Type type : this.types) {
+ if (filteringDSObjId > 0) {
+ count += skCase.getBlackboard().getArtifactsCount(type.getTypeID(), filteringDSObjId);
+ } else {
+ count += skCase.getBlackboardArtifactsTypeCount(type.getTypeID());
+ }
+ }
+ return count;
+ }
+
+ /**
+ * When this method is called, the count to be displayed will be
+ * updated.
+ */
+ void updateDisplayName() {
+ try {
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ this.childCount = fetchChildCount(skCase);
+ } catch (NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, "Error fetching data when case closed.", ex);
+ } catch (TskCoreException ex) {
+ logger.log(Level.WARNING, "Error getting child count", ex); //NON-NLS
+ }
+ super.setDisplayName(this.baseName + " \u200E(\u200E" + this.childCount + ")\u200E");
+ }
+ }
+
+ /**
+ * Default node encapsulating a blackboard artifact type. This is used on
+ * the left-hand navigation side of the Autopsy UI as the parent node for
+ * all of the artifacts of a given type. Its children will be
+ * BlackboardArtifactNode objects.
+ */
+ static class TypeNode extends UpdatableCountTypeNode {
+
+ private final BlackboardArtifact.Type type;
+
+ /**
+ * Main constructor.
+ *
+ * @param type The blackboard artifact type for this node.
+ * @param filteringDSObjId The data source object id to use for
+ * filtering. If id is less than or equal to 0,
+ * no filtering will occur.
+ */
+ TypeNode(BlackboardArtifact.Type type, long filteringDSObjId) {
+ super(Children.create(new ArtifactFactory(type, filteringDSObjId), true),
+ Lookups.singleton(type.getDisplayName()),
+ type.getDisplayName(),
+ filteringDSObjId,
+ type);
+
+ super.setName(type.getTypeName());
+ this.type = type;
+ String iconPath = IconsUtil.getIconFilePath(type.getTypeID());
+ setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath);
+ }
+
+ @Override
+ protected Sheet createSheet() {
+ Sheet sheet = super.createSheet();
+ Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
+ if (sheetSet == null) {
+ sheetSet = Sheet.createPropertiesSet();
+ sheet.put(sheetSet);
+ }
+
+ sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.name"),
+ NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.displayName"),
+ NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.desc"),
+ type.getDisplayName()));
+
+ sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.name"),
+ NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.displayName"),
+ NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.desc"),
+ getChildCount()));
+
+ return sheet;
+ }
+
+ @Override
+ public T accept(DisplayableItemNodeVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ @Override
+ public boolean isLeafTypeNode() {
+ return true;
+ }
+
+ @Override
+ public String getItemType() {
+ return getClass().getName() + type.getDisplayName();
+ }
+ }
+
+ /**
+ * Creates children for a given artifact type
+ */
+ private static class ArtifactFactory extends BaseChildFactory implements RefreshThrottler.Refresher {
+
+ private static final Logger logger = Logger.getLogger(ArtifactFactory.class.getName());
+ private final BlackboardArtifact.Type type;
+
+ /**
+ * RefreshThrottler is used to limit the number of refreshes performed
+ * when CONTENT_CHANGED and DATA_ADDED ingest module events are
+ * received.
+ */
+ private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
+ private final long filteringDSObjId;
+
+ /**
+ * Main constructor.
+ *
+ * @param type The blackboard artifact type for this node.
+ * @param filteringDSObjId The data source object id to use for
+ * filtering. If id is less than or equal to 0,
+ * no filtering will occur.
+ */
+ ArtifactFactory(BlackboardArtifact.Type type, long filteringDSObjId) {
+ super(type.getTypeName());
+ this.type = type;
+ this.filteringDSObjId = filteringDSObjId;
+ }
+
+ private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
+ String eventType = evt.getPropertyName();
+ if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
+ || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
+ /**
+ * Checking for a current case is a stop gap measure until a
+ * different way of handling the closing of cases is worked out.
+ * Currently, remote events may be received for a case that is
+ * already closed.
+ */
+ try {
+ Case.getCurrentCaseThrows();
+ refresh(false);
+ } catch (NoCurrentCaseException notUsed) {
+ /**
+ * Case is closed, do nothing.
+ */
+ }
+ }
+ };
+
+ @Override
+ protected void onAdd() {
+ refreshThrottler.registerForIngestModuleEvents();
+ IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
+ }
+
+ @Override
+ protected void onRemove() {
+ refreshThrottler.unregisterEventListener();
+ IngestManager.getInstance().removeIngestJobEventListener(pcl);
+ }
+
+ @Override
+ protected Node createNodeForKey(BlackboardArtifact key) {
+ return new BlackboardArtifactNode(key);
+ }
+
+ @Override
+ protected List makeKeys() {
+ try {
+ List arts;
+ arts = (filteringDSObjId > 0)
+ ? Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().getArtifacts(type.getTypeID(), filteringDSObjId)
+ : Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifacts(type.getTypeID());
+
+ for (BlackboardArtifact art : arts) {
+ //Cache attributes while we are off the EDT.
+ //See JIRA-5969
+ art.getAttributes();
+ }
+ return arts;
+ } catch (NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, "Trying to access case when no case is open.", ex); //NON-NLS
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void refresh() {
+ refresh(false);
+ }
+
+ @Override
+ public boolean isRefreshRequired(PropertyChangeEvent evt) {
+ String eventType = evt.getPropertyName();
+ if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
+
+ /**
+ * Checking for a current case is a stop gap measure until a
+ * different way of handling the closing of cases is worked out.
+ * Currently, remote events may be received for a case that is
+ * already closed.
+ */
+ try {
+ Case.getCurrentCaseThrows();
+ /**
+ * Even with the check above, it is still possible that the
+ * case will be closed in a different thread before this
+ * code executes. If that happens, it is possible for the
+ * event to have a null oldValue.
+ */
+ final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
+ if (null != event && event.getBlackboardArtifactType().equals(type)) {
+ return true;
+ }
+
+ } catch (NoCurrentCaseException notUsed) {
+ /**
+ * Case is closed, do nothing.
+ */
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
index aabb21bbec..9791d0d5fd 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
@@ -53,8 +53,6 @@ public interface AutopsyItemVisitor {
T visit(FileSize.FileSizeFilter fsf);
- T visit(ExtractedContent ec);
-
T visit(KeywordHits kh);
T visit(HashsetHits hh);
@@ -63,8 +61,6 @@ public interface AutopsyItemVisitor {
T visit(InterestingHits ih);
- T visit(Results r);
-
T visit(Tags tagsNodeKey);
T visit(Reports reportsItem);
@@ -85,15 +81,15 @@ public interface AutopsyItemVisitor {
T visit(DataSourcesByType aThis);
+ T visit(AnalysisResults aThis);
+
+ T visit(DataArtifacts aThis);
+
+
static abstract public class Default implements AutopsyItemVisitor {
protected abstract T defaultVisit(AutopsyVisitableItem ec);
- @Override
- public T visit(ExtractedContent ec) {
- return defaultVisit(ec);
- }
-
@Override
public T visit(FileTypesByExtension sf) {
return defaultVisit(sf);
@@ -199,11 +195,6 @@ public interface AutopsyItemVisitor {
return defaultVisit(personGrouping);
}
- @Override
- public T visit(Results r) {
- return defaultVisit(r);
- }
-
@Override
public T visit(FileTypes ft) {
return defaultVisit(ft);
@@ -233,5 +224,15 @@ public interface AutopsyItemVisitor {
public T visit(DataSourcesByType dataSourceHosts) {
return defaultVisit(dataSourceHosts);
}
+
+ @Override
+ public T visit(DataArtifacts aThis) {
+ return defaultVisit(aThis);
+ }
+
+ @Override
+ public T visit(AnalysisResults aThis) {
+ return defaultVisit(aThis);
+ }
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java
index 22b340d5f1..845591e261 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java
@@ -111,24 +111,27 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable keys = new ArrayList<>(Arrays.asList(
new DataSourcesByType(),
- new Views(tskCase),
- new Results(tskCase),
+ new Views(Case.getCurrentCaseThrows().getSleuthkitCase()),
+ new DataArtifacts(),
+ new AnalysisResults(),
+ new OsAccounts(Case.getCurrentCaseThrows().getSleuthkitCase()),
new Tags(),
- new Reports()));
+ new Reports()
+ ));
list.addAll(keys);
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
index 83225be1c2..7da750b503 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
@@ -171,7 +171,7 @@ TagNameNode.createSheet.name.displayName=Name
TagsNode.displayName.text=Tags
TagsNode.createSheet.name.name=Name
TagsNode.createSheet.name.displayName=Name
-ViewsNode.name.text=Views
+ViewsNode.name.text=File Views
ViewsNode.createSheet.name.name=Name
ViewsNode.createSheet.name.displayName=Name
ViewsNode.createSheet.name.desc=no description
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
index d2a7f32bf7..2feefb56c7 100755
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
@@ -39,6 +39,7 @@ AbstractAbstractFileNode.useridColLbl=UserID
AbstractContentNode.nodescription=no description
AbstractContentNode.valueLoading=value loading
AbstractFsContentNode.noDesc.text=no description
+AnalysisResults_name=Analysis Results
ArtifactStringContent.attrsTableHeader.sources=Source(s)
ArtifactStringContent.attrsTableHeader.type=Type
ArtifactStringContent.attrsTableHeader.value=Value
@@ -93,6 +94,7 @@ ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash
ContentTagNode.createSheet.artifactMD5.name=MD5 Hash
ContentTagNode.createSheet.origFileName=Original Name
ContentTagNode.createSheet.userName.text=User Name
+DataArtifacts_name=Data Artifacts
DataSourcesHostsNode_name=Data Sources
DeletedContent.allDelFilter.text=All
DeletedContent.createSheet.filterType.desc=no description
@@ -358,10 +360,6 @@ ReportNode.reportNameProperty.name=Report Name
ReportNode.reportNameProperty.displayName=Report Name
ReportNode.reportNameProperty.desc=Name of the report
ReportsListNode.displayName=Reports
-ResultsNode.createSheet.name.desc=no description
-ResultsNode.createSheet.name.displayName=Name
-ResultsNode.createSheet.name.name=Name
-ResultsNode.name.text=Results
SlackFileNode.getActions.viewInNewWin.text=View in New Window
SlackFileNode.getActions.viewFileInDir.text=View File in Directory
SpecialDirectoryNode.getActions.viewInNewWin.text=View in New Window
@@ -380,7 +378,7 @@ UnsupportedContentNode.createSheet.name.desc=no description
UnsupportedContentNode.createSheet.name.displayName=Name
UnsupportedContentNode.createSheet.name.name=Name
UnsupportedContentNode.displayName=Unsupported Content
-ViewsNode.name.text=Views
+ViewsNode.name.text=File Views
ViewsNode.createSheet.name.name=Name
ViewsNode.createSheet.name.displayName=Name
ViewsNode.createSheet.name.desc=no description
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java
new file mode 100644
index 0000000000..54e9e5da0a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java
@@ -0,0 +1,95 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 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.datamodel;
+
+import org.openide.nodes.Children;
+import org.openide.util.NbBundle;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+
+/**
+ * Analysis Results node support.
+ */
+@NbBundle.Messages({
+ "DataArtifacts_name=Data Artifacts",})
+public class DataArtifacts implements AutopsyVisitableItem {
+
+ /**
+ * Returns the name of this node that is the key in the children object.
+ *
+ * @return The name of this node that is the key in the children object.
+ */
+ public static String getName() {
+ return Bundle.DataArtifacts_name();
+ }
+
+ /**
+ * Parent node of all data artifacts.
+ */
+ static class RootNode extends Artifacts.BaseArtifactNode {
+
+ /**
+ * Main constructor.
+ *
+ * @param filteringDSObjId The data source object id for which results
+ * should be filtered. If no filtering should
+ * occur, this number should be less than or
+ * equal to 0.
+ */
+ RootNode(long filteringDSObjId) {
+ super(Children.create(new Artifacts.TypeFactory(BlackboardArtifact.Category.DATA_ARTIFACT, filteringDSObjId), true),
+ "org/sleuthkit/autopsy/images/extracted_content.png",
+ DataArtifacts.getName(),
+ DataArtifacts.getName());
+ }
+ }
+
+ private final long datasourceObjId;
+
+ /**
+ * Main constructor.
+ */
+ public DataArtifacts() {
+ this(0);
+ }
+
+ /**
+ * Main constructor.
+ *
+ * @param dsObjId The data source object id.
+ */
+ public DataArtifacts(long dsObjId) {
+ this.datasourceObjId = dsObjId;
+ }
+
+ @Override
+ public T accept(AutopsyItemVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ /**
+ * Returns whether or not there is a data source object for which results
+ * should be filtered.
+ *
+ * @return Whether or not there is a data source object for which results
+ * should be filtered.
+ */
+ Long getFilteringDataSourceObjId() {
+ return datasourceObjId;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java
index 214ab3ead9..28ad25fe59 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java
@@ -30,24 +30,23 @@ import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.LocalFilesDataSource;
-
/**
- * Data source grouping node - an optional grouping node in the data tree view
- *
+ * Data source grouping node - an optional grouping node in the data tree view
+ *
*/
class DataSourceGroupingNode extends DisplayableItemNode {
private static final Logger logger = Logger.getLogger(DataSourceGroupingNode.class.getName());
/**
- * Creates a data source grouping node for the given data source.
- *
- * @param dataSource specifies the data source
+ * Creates a data source grouping node for the given data source.
+ *
+ * @param dataSource specifies the data source
*/
DataSourceGroupingNode(DataSource dataSource) {
- super (Optional.ofNullable(createDSGroupingNodeChildren(dataSource))
- .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST))),
+ super(Optional.ofNullable(createDSGroupingNodeChildren(dataSource))
+ .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST))),
Lookups.singleton(dataSource));
if (dataSource instanceof Image) {
@@ -70,7 +69,7 @@ class DataSourceGroupingNode extends DisplayableItemNode {
public boolean isLeafTypeNode() {
return false;
}
-
+
private static RootContentChildren createDSGroupingNodeChildren(DataSource dataSource) {
long dsObjId = dataSource.getId();
@@ -78,11 +77,13 @@ class DataSourceGroupingNode extends DisplayableItemNode {
return new RootContentChildren(Arrays.asList(
new DataSources(dsObjId),
new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId),
- new Results(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId),
- new Tags(dsObjId) )
-
- );
-
+ new DataArtifacts(dsObjId),
+ new AnalysisResults(dsObjId),
+ new OsAccounts(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId),
+ new Tags(dsObjId),
+ new Reports()
+ ));
+
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS
return null;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
index 61bc401ee5..715f9aecd9 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
@@ -88,16 +88,11 @@ public interface DisplayableItemNodeVisitor {
T visit(RecentFilesFilterNode rffn);
- /*
- * Extracted Results Area
- */
- T visit(ResultsNode rn);
-
T visit(BlackboardArtifactNode ban);
- T visit(ExtractedContent.TypeNode atn);
+ T visit(Artifacts.TypeNode atn);
- T visit(ExtractedContent.RootNode ecn);
+ T visit(Artifacts.BaseArtifactNode ecn);
T visit(KeywordHits.RootNode khrn);
@@ -301,12 +296,12 @@ public interface DisplayableItemNodeVisitor {
}
@Override
- public T visit(ExtractedContent.TypeNode atn) {
+ public T visit(Artifacts.TypeNode atn) {
return defaultVisit(atn);
}
@Override
- public T visit(ExtractedContent.RootNode ecn) {
+ public T visit(Artifacts.BaseArtifactNode ecn) {
return defaultVisit(ecn);
}
@@ -405,11 +400,6 @@ public interface DisplayableItemNodeVisitor {
return defaultVisit(dataSourceGroupingNode);
}
- @Override
- public T visit(ResultsNode rn) {
- return defaultVisit(rn);
- }
-
@Override
public T visit(FileTypesNode ft) {
return defaultVisit(ft);
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
index 58becb183c..357f6ec153 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
@@ -32,7 +32,6 @@ import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.logging.Level;
-import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
@@ -45,10 +44,12 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_EMAIL_MSG;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
/**
* Support for TSK_EMAIL_MSG nodes and displaying emails in the directory tree.
@@ -58,8 +59,7 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
public class EmailExtracted implements AutopsyVisitableItem {
- private static final String LABEL_NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getLabel();
- private static final String DISPLAY_NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getDisplayName();
+ private static final String LABEL_NAME = BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeName();
private static final Logger logger = Logger.getLogger(EmailExtracted.class.getName());
private static final String MAIL_ACCOUNT = NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.mailAccount.text");
private static final String MAIL_FOLDER = NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.mailFolder.text");
@@ -147,7 +147,7 @@ public class EmailExtracted implements AutopsyVisitableItem {
}
@SuppressWarnings("deprecation")
- public void update() {
+ public void update() {
// clear cache if no case
if (skCase == null) {
synchronized (accounts) {
@@ -157,22 +157,22 @@ public class EmailExtracted implements AutopsyVisitableItem {
}
// get artifact id and path (if present) of all email artifacts
- int emailArtifactId = BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID();
+ int emailArtifactId = BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID();
int pathAttrId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID();
-
- String query = "SELECT \n" +
- " art.artifact_id AS artifact_id,\n" +
- " (SELECT value_text FROM blackboard_attributes attr\n" +
- " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + pathAttrId + "\n" +
- " LIMIT 1) AS value_text\n" +
- "FROM \n" +
- " blackboard_artifacts art\n" +
- " WHERE art.artifact_type_id = " + emailArtifactId + "\n" +
- ((filteringDSObjId > 0) ? " AND art.data_source_obj_id = " + filteringDSObjId : "");
-
+
+ String query = "SELECT \n"
+ + " art.artifact_id AS artifact_id,\n"
+ + " (SELECT value_text FROM blackboard_attributes attr\n"
+ + " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + pathAttrId + "\n"
+ + " LIMIT 1) AS value_text\n"
+ + "FROM \n"
+ + " blackboard_artifacts art\n"
+ + " WHERE art.artifact_type_id = " + emailArtifactId + "\n"
+ + ((filteringDSObjId > 0) ? " AND art.data_source_obj_id = " + filteringDSObjId : "");
+
// form hierarchy of account -> folder -> account id
Map>> newMapping = new HashMap<>();
-
+
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
ResultSet resultSet = dbQuery.getResultSet();
while (resultSet.next()) {
@@ -180,7 +180,7 @@ public class EmailExtracted implements AutopsyVisitableItem {
Map accountFolderMap = parsePath(resultSet.getString("value_text"));
String account = accountFolderMap.get(MAIL_ACCOUNT);
String folder = accountFolderMap.get(MAIL_FOLDER);
-
+
Map> folders = newMapping.computeIfAbsent(account, (str) -> new LinkedHashMap<>());
List messages = folders.computeIfAbsent(folder, (str) -> new ArrayList<>());
messages.add(artifactId);
@@ -188,29 +188,31 @@ public class EmailExtracted implements AutopsyVisitableItem {
} catch (TskCoreException | SQLException ex) {
logger.log(Level.WARNING, "Cannot initialize email extraction: ", ex); //NON-NLS
}
-
-
+
synchronized (accounts) {
accounts.clear();
accounts.putAll(newMapping);
}
-
+
setChanged();
notifyObservers();
}
}
-
/**
* Mail root node grouping all mail accounts, supports account-> folder
* structure
*/
- public class RootNode extends DisplayableItemNode {
+ public class RootNode extends UpdatableCountTypeNode {
public RootNode() {
- super(Children.create(new AccountFactory(), true), Lookups.singleton(DISPLAY_NAME));
+ super(Children.create(new AccountFactory(), true),
+ Lookups.singleton(TSK_EMAIL_MSG.getDisplayName()),
+ TSK_EMAIL_MSG.getDisplayName(),
+ filteringDSObjId,
+ TSK_EMAIL_MSG);
+ //super(Children.create(new AccountFactory(), true), Lookups.singleton(DISPLAY_NAME));
super.setName(LABEL_NAME);
- super.setDisplayName(DISPLAY_NAME);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
emailResults.update();
}
@@ -277,7 +279,7 @@ public class EmailExtracted implements AutopsyVisitableItem {
* for the event to have a null oldValue.
*/
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
- if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) {
+ if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) {
emailResults.update();
}
} catch (NoCurrentCaseException notUsed) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
deleted file mode 100644
index 8408c0c74a..0000000000
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2011-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.datamodel;
-
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
-import java.util.logging.Level;
-import org.openide.nodes.ChildFactory;
-import org.openide.nodes.Children;
-import org.openide.nodes.Node;
-import org.openide.nodes.Sheet;
-import org.openide.util.NbBundle;
-import org.openide.util.lookup.Lookups;
-import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
-import org.sleuthkit.autopsy.ingest.IngestManager;
-import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
-import org.sleuthkit.datamodel.Blackboard;
-import org.sleuthkit.datamodel.BlackboardArtifact;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_TL_EVENT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE;
-import org.sleuthkit.datamodel.SleuthkitCase;
-import org.sleuthkit.datamodel.TskCoreException;
-import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
-
-/**
- * Parent of the "extracted content" artifacts to be displayed in the tree.
- * Other artifacts are displayed under other more specific parents.
- */
-public class ExtractedContent implements AutopsyVisitableItem {
-
- private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
- public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text");
- private final long filteringDSObjId; // 0 if not filtering/grouping by data source
- private SleuthkitCase skCase; // set to null after case has been closed
- private Blackboard blackboard;
-
- /**
- * Constructs extracted content object
- *
- * @param skCase Case DB
- */
- public ExtractedContent(SleuthkitCase skCase) {
- this(skCase, 0);
- }
-
- /**
- * Constructs extracted content object
- *
- * @param skCase Case DB
- * @param objId Object id of the parent datasource
- */
- public ExtractedContent(SleuthkitCase skCase, long objId) {
- this.skCase = skCase;
- this.filteringDSObjId = objId;
- this.blackboard = skCase.getBlackboard();
- }
-
- @Override
- public T accept(AutopsyItemVisitor visitor) {
- return visitor.visit(this);
- }
-
- public SleuthkitCase getSleuthkitCase() {
- return skCase;
- }
-
- public class RootNode extends DisplayableItemNode {
-
- public RootNode(SleuthkitCase skCase) {
- super(Children.create(new TypeFactory(), true), Lookups.singleton(NAME));
- super.setName(NAME);
- super.setDisplayName(NAME);
- this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/extracted_content.png"); //NON-NLS
- }
-
- @Override
- public boolean isLeafTypeNode() {
- return false;
- }
-
- @Override
- public T accept(DisplayableItemNodeVisitor visitor) {
- return visitor.visit(this);
- }
-
- @Override
- protected Sheet createSheet() {
- Sheet sheet = super.createSheet();
- Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
- if (sheetSet == null) {
- sheetSet = Sheet.createPropertiesSet();
- sheet.put(sheetSet);
- }
-
- sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.name"),
- NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.displayName"),
- NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.desc"),
- NAME));
- return sheet;
- }
-
- @Override
- public String getItemType() {
- return getClass().getName();
- }
- }
-
- /**
- * Creates the children for the ExtractedContent area of the results tree.
- * This area has all of the blackboard artifacts that are not displayed in a
- * more specific form elsewhere in the tree.
- */
- private class TypeFactory extends ChildFactory.Detachable implements RefreshThrottler.Refresher {
-
- private final ArrayList doNotShow = new ArrayList<>();
- // maps the artifact type to its child node
- private final HashMap typeNodeList = new HashMap<>();
-
- /**
- * RefreshThrottler is used to limit the number of refreshes performed
- * when CONTENT_CHANGED and DATA_ADDED ingest module events are
- * received.
- */
- private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
-
- @SuppressWarnings("deprecation")
- TypeFactory() {
- super();
-
- // these are shown in other parts of the UI
- doNotShow.add(new BlackboardArtifact.Type(TSK_GEN_INFO));
- doNotShow.add(new BlackboardArtifact.Type(TSK_EMAIL_MSG));
- doNotShow.add(new BlackboardArtifact.Type(TSK_HASHSET_HIT));
- doNotShow.add(new BlackboardArtifact.Type(TSK_KEYWORD_HIT));
- doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_FILE_HIT));
- doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT));
- doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT));
- doNotShow.add(new BlackboardArtifact.Type(TSK_DATA_SOURCE_USAGE));
- doNotShow.add(new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE));
- doNotShow.add(new BlackboardArtifact.Type(TSK_TL_EVENT));
-
- //This is not meant to be shown in the UI at all. It is more of a meta artifact.
- doNotShow.add(new BlackboardArtifact.Type(TSK_ASSOCIATED_OBJECT));
- }
-
- private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
- String eventType = evt.getPropertyName();
- if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
- // case was closed. Remove listeners so that we don't get called with a stale case handle
- if (evt.getNewValue() == null) {
- removeNotify();
- skCase = null;
- }
- } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
- || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
- /**
- * This is a stop gap measure until a different way of handling
- * the closing of cases is worked out. Currently, remote events
- * may be received for a case that is already closed.
- */
- try {
- Case.getCurrentCaseThrows();
- refresh(false);
- } catch (NoCurrentCaseException notUsed) {
- /**
- * Case is closed, do nothing.
- */
- }
- }
- };
-
- @Override
- protected void addNotify() {
- refreshThrottler.registerForIngestModuleEvents();
- IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
- Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
- }
-
- @Override
- protected void removeNotify() {
- refreshThrottler.unregisterEventListener();
- IngestManager.getInstance().removeIngestJobEventListener(pcl);
- Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
- typeNodeList.clear();
- }
-
- @Override
- protected boolean createKeys(List list) {
- if (skCase != null) {
- try {
- List types = (filteringDSObjId > 0)
- ? blackboard.getArtifactTypesInUse(filteringDSObjId)
- : skCase.getArtifactTypesInUse();
-
- types.removeAll(doNotShow);
- Collections.sort(types,
- new Comparator() {
- @Override
- public int compare(BlackboardArtifact.Type a, BlackboardArtifact.Type b) {
- return a.getDisplayName().compareTo(b.getDisplayName());
- }
- });
- list.addAll(types);
-
- // the create node method will get called only for new types
- // refresh the counts if we already created them from a previous update
- for (BlackboardArtifact.Type art : types) {
- TypeNode node = typeNodeList.get(art);
- if (node != null) {
- node.updateDisplayName();
- }
- }
- } catch (TskCoreException ex) {
- Logger.getLogger(TypeFactory.class.getName()).log(Level.SEVERE, "Error getting list of artifacts in use: " + ex.getLocalizedMessage()); //NON-NLS
- }
- }
- return true;
- }
-
- @Override
- protected Node createNodeForKey(BlackboardArtifact.Type key) {
- TypeNode node = new TypeNode(key);
- typeNodeList.put(key, node);
- return node;
- }
-
- @Override
- public void refresh() {
- refresh(false);
- }
-
- @Override
- public boolean isRefreshRequired(PropertyChangeEvent evt) {
- String eventType = evt.getPropertyName();
- if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
- /**
- * This is a stop gap measure until a different way of handling
- * the closing of cases is worked out. Currently, remote events
- * may be received for a case that is already closed.
- */
- try {
- Case.getCurrentCaseThrows();
- /**
- * Due to some unresolved issues with how cases are closed,
- * it is possible for the event to have a null oldValue if
- * the event is a remote event.
- */
- final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
- if (null != event && !(this.doNotShow.contains(event.getBlackboardArtifactType()))) {
- return true;
- }
- } catch (NoCurrentCaseException notUsed) {
- /**
- * Case is closed, do nothing.
- */
- }
- }
- return false;
- }
- }
-
- /**
- * Node encapsulating blackboard artifact type. This is used on the
- * left-hand navigation side of the Autopsy UI as the parent node for all of
- * the artifacts of a given type. Its children will be
- * BlackboardArtifactNode objects.
- */
- public class TypeNode extends DisplayableItemNode {
-
- private final BlackboardArtifact.Type type;
- private long childCount = 0;
-
- TypeNode(BlackboardArtifact.Type type) {
- super(Children.create(new ArtifactFactory(type), true), Lookups.singleton(type.getDisplayName()));
- super.setName(type.getTypeName());
- this.type = type;
- String iconPath = IconsUtil.getIconFilePath(type.getTypeID());
- setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath);
- updateDisplayName();
- }
-
- final void updateDisplayName() {
- if (skCase == null) {
- return;
- }
-
- // NOTE: This completely destroys our lazy-loading ideal
- // a performance increase might be had by adding a
- // "getBlackboardArtifactCount()" method to skCase
- try {
- this.childCount = (filteringDSObjId > 0)
- ? blackboard.getArtifactsCount(type.getTypeID(), filteringDSObjId)
- : skCase.getBlackboardArtifactsTypeCount(type.getTypeID());
- } catch (TskCoreException ex) {
- Logger.getLogger(TypeNode.class.getName())
- .log(Level.WARNING, "Error getting child count", ex); //NON-NLS
- }
- super.setDisplayName(type.getDisplayName() + " \u200E(\u200E" + childCount + ")\u200E");
- }
-
- @Override
- protected Sheet createSheet() {
- Sheet sheet = super.createSheet();
- Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
- if (sheetSet == null) {
- sheetSet = Sheet.createPropertiesSet();
- sheet.put(sheetSet);
- }
-
- sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.name"),
- NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.displayName"),
- NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.desc"),
- type.getDisplayName()));
-
- sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.name"),
- NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.displayName"),
- NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.desc"),
- childCount));
-
- return sheet;
- }
-
- @Override
- public T accept(DisplayableItemNodeVisitor visitor) {
- return visitor.visit(this);
- }
-
- @Override
- public boolean isLeafTypeNode() {
- return true;
- }
-
- @Override
- public String getItemType() {
- return getClass().getName() + type.getDisplayName();
- }
- }
-
- /**
- * Creates children for a given artifact type
- */
- private class ArtifactFactory extends BaseChildFactory implements RefreshThrottler.Refresher {
-
- private final BlackboardArtifact.Type type;
-
- /**
- * RefreshThrottler is used to limit the number of refreshes performed
- * when CONTENT_CHANGED and DATA_ADDED ingest module events are
- * received.
- */
- private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
-
- ArtifactFactory(BlackboardArtifact.Type type) {
- super(type.getTypeName());
- this.type = type;
- }
-
- private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
- String eventType = evt.getPropertyName();
- if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
- || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
- /**
- * Checking for a current case is a stop gap measure until a
- * different way of handling the closing of cases is worked out.
- * Currently, remote events may be received for a case that is
- * already closed.
- */
- try {
- Case.getCurrentCaseThrows();
- refresh(false);
- } catch (NoCurrentCaseException notUsed) {
- /**
- * Case is closed, do nothing.
- */
- }
- }
- };
-
- @Override
- protected void onAdd() {
- refreshThrottler.registerForIngestModuleEvents();
- IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
- }
-
- @Override
- protected void onRemove() {
- refreshThrottler.unregisterEventListener();
- IngestManager.getInstance().removeIngestJobEventListener(pcl);
- }
-
- @Override
- protected Node createNodeForKey(BlackboardArtifact key) {
- return new BlackboardArtifactNode(key);
- }
-
- @Override
- protected List makeKeys() {
- if (skCase != null) {
- try {
- List arts;
- if (filteringDSObjId > 0) {
- arts = blackboard.getArtifacts(type.getTypeID(), filteringDSObjId);
- } else {
- arts = skCase.getBlackboardArtifacts(type.getTypeID());
- }
- for (BlackboardArtifact art : arts) {
- //Cache attributes while we are off the EDT.
- //See JIRA-5969
- art.getAttributes();
- }
- return arts;
- } catch (TskCoreException ex) {
- Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
- }
- }
- return Collections.emptyList();
- }
-
- @Override
- public void refresh() {
- refresh(false);
- }
-
- @Override
- public boolean isRefreshRequired(PropertyChangeEvent evt) {
- String eventType = evt.getPropertyName();
- if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
-
- /**
- * Checking for a current case is a stop gap measure until a
- * different way of handling the closing of cases is worked out.
- * Currently, remote events may be received for a case that is
- * already closed.
- */
- try {
- Case.getCurrentCaseThrows();
- /**
- * Even with the check above, it is still possible that the
- * case will be closed in a different thread before this
- * code executes. If that happens, it is possible for the
- * event to have a null oldValue.
- */
- final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
- if (null != event && event.getBlackboardArtifactType().equals(type)) {
- return true;
- }
-
- } catch (NoCurrentCaseException notUsed) {
- /**
- * Case is closed, do nothing.
- */
- }
- }
- return false;
- }
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
index a939d977d0..5318a99a00 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
@@ -46,19 +46,20 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
-import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_HASHSET_HIT;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
/**
* Hash set hits node support. Inner classes have all of the nodes in the tree.
*/
public class HashsetHits implements AutopsyVisitableItem {
- private static final String HASHSET_HITS = BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getLabel();
- private static final String DISPLAY_NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName();
+ private static final String HASHSET_HITS = BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeName();
+ private static final String DISPLAY_NAME = BlackboardArtifact.Type.TSK_HASHSET_HIT.getDisplayName();
private static final Logger logger = Logger.getLogger(HashsetHits.class.getName());
private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
@@ -134,7 +135,7 @@ public class HashsetHits implements AutopsyVisitableItem {
}
int setNameId = ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID();
- int artId = ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID();
+ int artId = TSK_HASHSET_HIT.getTypeID();
String query = "SELECT value_text,blackboard_attributes.artifact_id,attribute_type_id " //NON-NLS
+ "FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS
+ "attribute_type_id=" + setNameId //NON-NLS
@@ -168,10 +169,15 @@ public class HashsetHits implements AutopsyVisitableItem {
/**
* Top-level node for all hash sets
*/
- public class RootNode extends DisplayableItemNode {
+ public class RootNode extends UpdatableCountTypeNode {
public RootNode() {
- super(Children.create(new HashsetNameFactory(), true), Lookups.singleton(DISPLAY_NAME));
+ super(Children.create(new HashsetNameFactory(), true),
+ Lookups.singleton(DISPLAY_NAME),
+ DISPLAY_NAME,
+ filteringDSObjId,
+ TSK_HASHSET_HIT);
+
super.setName(HASHSET_HITS);
super.setDisplayName(DISPLAY_NAME);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
@@ -239,7 +245,7 @@ public class HashsetHits implements AutopsyVisitableItem {
* oldValue if the event is a remote event.
*/
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
- if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
+ if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_HASHSET_HIT.getTypeID()) {
hashsetResults.update();
}
} catch (NoCurrentCaseException notUsed) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
index 677aef8df3..22525854ca 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
@@ -50,6 +50,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
public class InterestingHits implements AutopsyVisitableItem {
@@ -110,8 +111,8 @@ public class InterestingHits implements AutopsyVisitableItem {
synchronized (interestingItemsMap) {
interestingItemsMap.clear();
}
- loadArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
- loadArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
+ loadArtifacts(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT);
+ loadArtifacts(BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT);
setChanged();
notifyObservers();
}
@@ -121,7 +122,7 @@ public class InterestingHits implements AutopsyVisitableItem {
* the interestingItemsMap
*/
@SuppressWarnings("deprecation")
- private void loadArtifacts(BlackboardArtifact.ARTIFACT_TYPE artType) {
+ private void loadArtifacts(BlackboardArtifact.Type artType) {
if (skCase == null) {
return;
}
@@ -145,8 +146,8 @@ public class InterestingHits implements AutopsyVisitableItem {
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
if (!interestingItemsMap.containsKey(value)) {
interestingItemsMap.put(value, new LinkedHashMap<>());
- interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getDisplayName(), new HashSet<>());
- interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName(), new HashSet<>());
+ interestingItemsMap.get(value).put(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getDisplayName(), new HashSet<>());
+ interestingItemsMap.get(value).put(BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName(), new HashSet<>());
}
interestingItemsMap.get(value).get(artType.getDisplayName()).add(artifactId);
}
@@ -165,12 +166,16 @@ public class InterestingHits implements AutopsyVisitableItem {
/**
* Node for the interesting items
*/
- public class RootNode extends DisplayableItemNode {
+ public class RootNode extends UpdatableCountTypeNode {
public RootNode() {
- super(Children.create(new SetNameFactory(), true), Lookups.singleton(DISPLAY_NAME));
+ super(Children.create(new SetNameFactory(), true),
+ Lookups.singleton(DISPLAY_NAME),
+ DISPLAY_NAME,
+ filteringDSObjId,
+ BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT,
+ BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT);
super.setName(INTERESTING_ITEMS);
- super.setDisplayName(DISPLAY_NAME);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
}
@@ -232,8 +237,8 @@ public class InterestingHits implements AutopsyVisitableItem {
* event to have a null oldValue.
*/
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
- if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()
- || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())) {
+ if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()
+ || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID())) {
interestingResults.update();
}
} catch (NoCurrentCaseException notUsed) {
@@ -314,8 +319,8 @@ public class InterestingHits implements AutopsyVisitableItem {
}
private void updateDisplayName() {
- int sizeOfSet = interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName()).size()
- + interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getDisplayName()).size();
+ int sizeOfSet = interestingResults.getArtifactIds(setName, BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName()).size()
+ + interestingResults.getArtifactIds(setName, BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getDisplayName()).size();
super.setDisplayName(setName + " (" + sizeOfSet + ")");
}
@@ -374,8 +379,8 @@ public class InterestingHits implements AutopsyVisitableItem {
@Override
protected boolean createKeys(List list) {
- list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getDisplayName());
- list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName());
+ list.add(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getDisplayName());
+ list.add(BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName());
return true;
}
@@ -400,8 +405,8 @@ public class InterestingHits implements AutopsyVisitableItem {
this.typeName = typeName;
this.setName = setName;
/**
- * We use the combination of setName and typeName as the name of
- * the node to ensure that nodes have a unique name. This comes into
+ * We use the combination of setName and typeName as the name of the
+ * node to ensure that nodes have a unique name. This comes into
* play when associating paging state with the node.
*/
super.setName(setName + "_" + typeName);
@@ -462,9 +467,9 @@ public class InterestingHits implements AutopsyVisitableItem {
private HitFactory(String setName, String typeName) {
/**
- * The node name passed to the parent constructor must be the
- * same as the name set in the InterestingItemTypeNode constructor,
- * i.e. setName underscore typeName
+ * The node name passed to the parent constructor must be the same
+ * as the name set in the InterestingItemTypeNode constructor, i.e.
+ * setName underscore typeName
*/
super(setName + "_" + typeName);
this.setName = setName;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
index 79c6b9a32e..eab8a07d3a 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
@@ -55,6 +55,8 @@ import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_KEYWORD_HIT;
+import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
/**
* Keyword hits node support
@@ -71,7 +73,7 @@ public class KeywordHits implements AutopsyVisitableItem {
@NbBundle.Messages("KeywordHits.singleRegexSearch.text=Single Regular Expression Search")
private static final String SIMPLE_REGEX_SEARCH = KeywordHits_singleRegexSearch_text();
- public static final String NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getLabel();
+ public static final String NAME = BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName();
private SleuthkitCase skCase;
private final KeywordResults keywordResults;
@@ -93,7 +95,7 @@ public class KeywordHits implements AutopsyVisitableItem {
+ "blackboard_attributes.attribute_type_id "//NON-NLS
+ "FROM blackboard_attributes, blackboard_artifacts "//NON-NLS
+ "WHERE blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id "//NON-NLS
- + " AND blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() //NON-NLS
+ + " AND blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() //NON-NLS
+ " AND (attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()//NON-NLS
+ " OR attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()//NON-NLS
+ " OR attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID()//NON-NLS
@@ -375,12 +377,16 @@ public class KeywordHits implements AutopsyVisitableItem {
}
// Created by CreateAutopsyNodeVisitor
- public class RootNode extends DisplayableItemNode {
+ public class RootNode extends UpdatableCountTypeNode {
public RootNode() {
- super(Children.create(new ListFactory(), true), Lookups.singleton(KEYWORD_HITS));
+ super(Children.create(new ListFactory(), true),
+ Lookups.singleton(KEYWORD_HITS),
+ KEYWORD_HITS,
+ filteringDSObjId,
+ TSK_KEYWORD_HIT);
+
super.setName(NAME);
- super.setDisplayName(KEYWORD_HITS);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
}
@@ -464,7 +470,7 @@ public class KeywordHits implements AutopsyVisitableItem {
* for the event to have a null oldValue.
*/
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
- if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
+ if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) {
keywordResults.update();
}
} catch (NoCurrentCaseException notUsed) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java
deleted file mode 100644
index b2d9f4799b..0000000000
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2011 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.datamodel;
-
-import org.sleuthkit.datamodel.SleuthkitCase;
-
-/**
- * Results node support
- */
-public class Results implements AutopsyVisitableItem {
-
- private SleuthkitCase skCase;
- private final long datasourceObjId;
-
- public Results(SleuthkitCase skCase) {
- this(skCase, 0);
- }
-
- public Results(SleuthkitCase skCase, long dsObjId) {
- this.skCase = skCase;
- this.datasourceObjId = dsObjId;
- }
-
- @Override
- public T accept(AutopsyItemVisitor visitor) {
- return visitor.visit(this);
- }
-
- public SleuthkitCase getSleuthkitCase() {
- return skCase;
- }
-
- long filteringDataSourceObjId() {
- return datasourceObjId;
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java
deleted file mode 100644
index e9b2eba696..0000000000
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2011-2018 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.datamodel;
-
-import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
-import java.util.Arrays;
-import org.openide.nodes.Sheet;
-import org.openide.util.NbBundle;
-import org.openide.util.lookup.Lookups;
-import org.sleuthkit.datamodel.SleuthkitCase;
-
-/**
- * Node for the results section of the tree.
- */
-public class ResultsNode extends DisplayableItemNode {
-
- @NbBundle.Messages("ResultsNode.name.text=Results")
- private static final String NAME = Bundle.ResultsNode_name_text();
-
- public static String getNameIdentifier() {
- return NAME;
- }
-
- public ResultsNode(SleuthkitCase sleuthkitCase) {
- this(sleuthkitCase, 0);
- }
-
- public ResultsNode(SleuthkitCase sleuthkitCase, long dsObjId) {
- super(
-
- new RootContentChildren(Arrays.asList(
- new ExtractedContent(sleuthkitCase, dsObjId ),
- new KeywordHits(sleuthkitCase, dsObjId),
- new HashsetHits(sleuthkitCase, dsObjId),
- new EmailExtracted(sleuthkitCase, dsObjId),
- new InterestingHits(sleuthkitCase, dsObjId ),
- new Accounts(sleuthkitCase, dsObjId),
- new OsAccounts(sleuthkitCase, dsObjId))
- ),
- Lookups.singleton(NAME));
- setName(NAME);
- setDisplayName(NAME);
- this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/results.png"); //NON-NLS
- }
-
- @Override
- public boolean isLeafTypeNode() {
- return false;
- }
-
- @Override
- public T accept(DisplayableItemNodeVisitor visitor) {
- return visitor.visit(this);
- }
-
- @Override
- @NbBundle.Messages({
- "ResultsNode.createSheet.name.name=Name",
- "ResultsNode.createSheet.name.displayName=Name",
- "ResultsNode.createSheet.name.desc=no description"})
- protected Sheet createSheet() {
- Sheet sheet = super.createSheet();
- Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
- if (sheetSet == null) {
- sheetSet = Sheet.createPropertiesSet();
- sheet.put(sheetSet);
- }
-
- sheetSet.put(new NodeProperty<>(Bundle.ResultsNode_createSheet_name_name(),
- Bundle.ResultsNode_createSheet_name_displayName(),
- Bundle.ResultsNode_createSheet_name_desc(),
- NAME
- ));
- return sheet;
- }
-
- @Override
- public String getItemType() {
- return getClass().getName();
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java
index 07ea924d82..24d077b5fa 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java
@@ -83,11 +83,6 @@ public class RootContentChildren extends Children.Keys