diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 522c439d26..d80feed87a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -166,6 +166,21 @@ public class TagsManager implements Closeable { return caseDb.getTagNamesInUse(); } + /** + * Selects all of the rows from the tag_names table in the case database for + * which there is at least one matching row in the content_tags or + * blackboard_artifact_tags tables, for the given data source object id. + * + * @param dsObjId data source object id + * + * @return A list, possibly empty, of TagName data transfer objects (DTOs) + * for the rows. + * + * @throws TskCoreException + */ + public List getTagNamesInUse(long dsObjId) throws TskCoreException { + return caseDb.getTagNamesInUse(dsObjId); + } /** * Gets a map of tag display names to tag name entries in the case database. * It has keys for the display names of the standard tag types, the current @@ -401,6 +416,24 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsCountByTagName(tagName); } + /** + * Gets content tags count by tag name, for the given data source + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames and/or addTagName. + * + * @param dsObjId data source object id + * + * @return A count of the content tags with the specified tag name, and for + * the given data source + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getContentTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getContentTagsCountByTagName(tagName, dsObjId); + } + /** * Gets a content tag by tag id. * @@ -430,6 +463,23 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsByTagName(tagName); } + /** + * Gets content tags by tag name, for the given data source. + * + * @param tagName The tag name of interest. + * + * @param dsObjId data source object id + * + * @return A list, possibly empty, of the content tags with the specified + * tag name, and for the given data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + public List getContentTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getContentTagsByTagName(tagName, dsObjId); + } + /** * Gets content tags count by content. * @@ -531,6 +581,24 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } + /** + * Gets an artifact tags count by tag name, for the given data source. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param dsObjId data source object id + * + * @return A count of the artifact tags with the specified tag name, + * for the given data source. + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getBlackboardArtifactTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getBlackboardArtifactTagsCountByTagName(tagName, dsObjId); + } + /** * Gets an artifact tag by tag id. * @@ -562,6 +630,24 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsByTagName(tagName); } + /** + * Gets artifact tags by tag name, for specified data source. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param dsObjId data source object id + * + * @return A list, possibly empty, of the artifact tags with the specified + * tag name, for the specified data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + public List getBlackboardArtifactTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getBlackboardArtifactTagsByTagName(tagName, dsObjId); + } + /** * Gets artifact tags for a particular artifact. * diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 904ab285c8..22fa2b7135 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -69,6 +69,7 @@ public final class UserPreferences { private static final String MODE = "AutopsyMode"; // NON-NLS private static final String MAX_NUM_OF_LOG_FILE = "MaximumNumberOfLogFiles"; private static final int LOG_FILE_NUM_INT = 10; + public static final String GROUP_ITEMS_IN_TREE_BY_DATASOURCE = "GroupItemsInTreeByDataSource"; //NON-NLS // Prevent instantiation. private UserPreferences() { @@ -187,6 +188,14 @@ public final class UserPreferences { preferences.putInt(NUMBER_OF_FILE_INGEST_THREADS, value); } + public static boolean groupItemsInTreeByDatasource() { + return preferences.getBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, false); + } + + public static void setGroupItemsInTreeByDatasource(boolean value) { + preferences.putBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, value); + } + /** * Reads persisted case database connection info. * diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index bf139f9a47..71705d2725 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -162,12 +162,12 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(DeletedContent dc) { - return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase()); + return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override public AbstractNode visit(FileSize dc) { - return new FileSize.FileSizeRootNode(dc.getSleuthkitCase()); + return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override @@ -192,22 +192,27 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(Tags tagsNodeKey) { - return tagsNodeKey.new RootNode(); + return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId()); } @Override public AbstractNode visit(DataSources i) { - return new DataSourcesNode(); + return new DataSourcesNode(i.filteringDataSourceObjId()); } + @Override + public AbstractNode visit(DataSourceGrouping datasourceGrouping) { + return new DataSourceGroupingNode(datasourceGrouping.getDataSource()); + } + @Override public AbstractNode visit(Views v) { - return new ViewsNode(v.getSleuthkitCase()); + return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId()); } @Override - public AbstractNode visit(Results r) { - return new ResultsNode(r.getSleuthkitCase()); + public AbstractNode visit(Results results) { + return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() ); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java index 2dcc5942db..29843bccb7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -28,6 +28,8 @@ import org.sleuthkit.autopsy.datamodel.accounts.Accounts; public interface AutopsyItemVisitor { T visit(DataSources i); + + T visit(DataSourceGrouping datasourceGrouping); T visit(Views v); @@ -173,6 +175,11 @@ public interface AutopsyItemVisitor { return defaultVisit(v); } + @Override + public T visit(DataSourceGrouping datasourceGrouping) { + return defaultVisit(datasourceGrouping); + } + @Override public T visit(Results r) { return defaultVisit(r); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java new file mode 100644 index 0000000000..cc3b8cb8ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java @@ -0,0 +1,146 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 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 java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.TskCoreException; + + +/** + * Child factory to create the top level children of the autopsy tree + * + */ +public class AutopsyTreeChildrenFactory extends ChildFactory.Detachable { + + private static final Logger logger = Logger.getLogger(AutopsyTreeChildrenFactory.class.getName()); + private final SleuthkitCase tskCase; + + /** + * Constructs the child factory + * @param tskCase + */ + public AutopsyTreeChildrenFactory(SleuthkitCase tskCase) { + this.tskCase = tskCase; + + } + + /** + * Listener for handling DATA_SOURCE_ADDED events. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + refreshChildren(); + } + } + }; + + @Override + protected void addNotify() { + super.addNotify(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + @Override + protected void removeNotify() { + super.removeNotify(); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + /** + * Creates keys for the top level children. + * + * @param list list of keys created + * @return true, indicating that the key list is complete + */ + @Override + protected boolean createKeys(List list) { + + try { + if (UserPreferences.groupItemsInTreeByDatasource()) { + List dataSources = tskCase.getDataSources(); + List keys = new ArrayList<>(); + dataSources.forEach((datasource) -> { + keys.add(new DataSourceGrouping(datasource)); + }); + list.addAll(keys); + + list.add(new Reports()); + } else { + + List keys = new ArrayList<>(Arrays.asList( + new DataSources(), + new Views(tskCase), + new Results(tskCase), + new Tags(), + new Reports())); + + list.addAll(keys); + } + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error getting datas sources list from the database.", tskCoreException); + } + return true; + } + + /** + * Creates nodes for the top level Key + * + * @param key + * + * @return Node for the key, null if key is unknown. + */ + @Override + protected Node createNodeForKey(Object key) { + + if (key instanceof SleuthkitVisitableItem) { + return ((SleuthkitVisitableItem) key).accept(new RootContentChildren.CreateSleuthkitNodeVisitor()); + } else if (key instanceof AutopsyVisitableItem) { + return ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor()); + } + else { + logger.log(Level.SEVERE, "Unknown key type ", key.getClass().getName()); + return null; + } + } + + /** + * Refresh the children + */ + public void refreshChildren() { + refresh(true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 733c49e8b7..374c96069b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -56,6 +56,7 @@ DataModelActionsFactory.viewNewWin.text=View in New Window DataModelActionsFactory.openExtViewer.text=Open in External Viewer DataModelActionsFactory.srfFileSameMD5.text=Search for files with the same MD5 hash DataSourcesNode.name=Data Sources +DataSourcesNode.group_by_datasource.name=Data Source Files DataSourcesNode.createSheet.name.name=Name DataSourcesNode.createSheet.name.displayName=Name DataSourcesNode.createSheet.name.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties index 3d20fded35..979c4d50cb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties @@ -57,6 +57,7 @@ DataModelActionsFactory.viewNewWin.text=\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30 DataModelActionsFactory.openExtViewer.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u306b\u8868\u793a DataModelActionsFactory.srfFileSameMD5.text=\u540c\u3058MD5\u30cf\u30c3\u30b7\u30e5\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22 DataSourcesNode.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 +DataSourcesNode.group_by_datasource.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb DataSourcesNode.createSheet.name.name=\u540d\u524d DataSourcesNode.createSheet.name.displayName=\u540d\u524d DataSourcesNode.createSheet.name.desc=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093 diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java new file mode 100644 index 0000000000..91ae285c81 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java @@ -0,0 +1,45 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 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.datamodel.DataSource; + +/** + * A top level UI grouping of Files, Views, Results, Tags + * for 'Group by Data Source' view of the tree. + * + */ +public class DataSourceGrouping implements AutopsyVisitableItem { + + private final DataSource dataSource; + + public DataSourceGrouping(DataSource dataSource) { + this.dataSource = dataSource; + } + + DataSource getDataSource() { + return this.dataSource; + } + + @Override + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java new file mode 100644 index 0000000000..4c8ee90e7c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 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 java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +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 + * + */ +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 + */ + DataSourceGroupingNode(DataSource dataSource) { + + super (Optional.ofNullable(createDSGroupingNodeChildren(dataSource)) + .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST)))); + + if (dataSource instanceof Image) { + Image image = (Image) dataSource; + + super.setName(image.getName()); + super.setDisplayName(image.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); + } else if (dataSource instanceof LocalFilesDataSource) { + LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource; + + super.setName(localFilesDataSource.getName()); + super.setDisplayName(localFilesDataSource.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); + } + + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + private static RootContentChildren createDSGroupingNodeChildren(DataSource dataSource) { + + long dsObjId = dataSource.getId(); + try { + return new RootContentChildren(Arrays.asList( + new DataSources(dsObjId), + new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), + new Results(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), + new Tags(dsObjId) ) + + ); + + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS + return null; + } + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index 5f8cbfff54..52ca52e89f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -23,9 +23,20 @@ package org.sleuthkit.autopsy.datamodel; */ public class DataSources implements AutopsyVisitableItem { + private final long datasourceObjId; + public DataSources() { + this(0); } + public DataSources(long datasourceObjId) { + this.datasourceObjId = datasourceObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 6e8374252d..d4e0ebf261 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -33,6 +34,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; /** * Nodes for the images @@ -40,22 +42,27 @@ import org.sleuthkit.datamodel.TskCoreException; public class DataSourcesNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name"); + private final String displayName; // NOTE: The images passed in via argument will be ignored. @Deprecated public DataSourcesNode(List images) { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } public DataSourcesNode() { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } + public DataSourcesNode(long dsObjId) { + super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME)); + displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME; + init(); + } + private void init() { setName(NAME); - setDisplayName(NAME); + setDisplayName(displayName); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); //NON-NLS } @@ -70,14 +77,20 @@ public class DataSourcesNode extends DisplayableItemNode { public static class DataSourcesNodeChildren extends AbstractContentChildren { private static final Logger logger = Logger.getLogger(DataSourcesNodeChildren.class.getName()); - + private final long datasourceObjId; + List currentKeys; public DataSourcesNodeChildren() { - super(); - this.currentKeys = new ArrayList<>(); + this(0); } + public DataSourcesNodeChildren(long dsObjId) { + super(); + this.currentKeys = new ArrayList<>(); + this.datasourceObjId = dsObjId; + } + private final PropertyChangeListener pcl = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -103,9 +116,15 @@ public class DataSourcesNode extends DisplayableItemNode { private void reloadKeys() { try { - currentKeys = Case.getCurrentCaseThrows().getDataSources(); + if (datasourceObjId == 0) { + currentKeys = Case.getCurrentCaseThrows().getDataSources(); + } + else { + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(datasourceObjId); + currentKeys = new ArrayList<>(Arrays.asList(content)); + } setKeys(currentKeys); - } catch (TskCoreException | NoCurrentCaseException ex) { + } catch (TskCoreException | NoCurrentCaseException | TskDataException ex) { logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS setKeys(Collections.emptySet()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 812eaa065b..370cc5cec7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.TskData; public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; @NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System", "DeletedContent.allDelFilter.text=All"}) @@ -101,9 +102,18 @@ public class DeletedContent implements AutopsyVisitableItem { } public DeletedContent(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public DeletedContent(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -118,8 +128,8 @@ public class DeletedContent implements AutopsyVisitableItem { @NbBundle.Messages("DeletedContent.deletedContentsNode.name=Deleted Files") private static final String NAME = Bundle.DeletedContent_deletedContentsNode_name(); - DeletedContentsNode(SleuthkitCase skCase) { - super(Children.create(new DeletedContentsChildren(skCase), true), Lookups.singleton(NAME)); + DeletedContentsNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new DeletedContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS @@ -164,11 +174,13 @@ public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; private Observable notifier; + private final long datasourceObjId; // true if we have already told user that not all files will be shown private static volatile boolean maxFilesDialogShown = false; - public DeletedContentsChildren(SleuthkitCase skCase) { + public DeletedContentsChildren(SleuthkitCase skCase, long dsObjId) { this.skCase = skCase; + this.datasourceObjId = dsObjId; this.notifier = new DeletedContentsChildrenObservable(); } @@ -257,24 +269,27 @@ public class DeletedContent implements AutopsyVisitableItem { @Override protected Node createNodeForKey(DeletedContent.DeletedContentFilter key) { - return new DeletedContentNode(skCase, key, notifier); + return new DeletedContentNode(skCase, key, notifier, datasourceObjId); } public class DeletedContentNode extends DisplayableItemNode { private final DeletedContent.DeletedContentFilter filter; + private final long datasourceObjId; // Use version that has observer for updates @Deprecated - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter) { - super(Children.create(new DeletedContentChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, null, dsObjId ), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); } - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o) { - super(Children.create(new DeletedContentChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, o, dsObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); o.addObserver(new DeletedContentNodeObserver()); } @@ -299,7 +314,7 @@ public class DeletedContent implements AutopsyVisitableItem { private void updateDisplayName() { //get count of children without preloading all children nodes - final long count = DeletedContentChildren.calculateItems(skCase, filter); + final long count = DeletedContentChildren.calculateItems(skCase, filter, datasourceObjId); //final long count = getChildren().getNodesCount(true); super.setDisplayName(filter.getDisplayName() + " (" + count + ")"); } @@ -351,11 +366,13 @@ public class DeletedContent implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName()); private static final int MAX_OBJECTS = 10001; private final Observable notifier; + private final long datasourceObjId; - DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o) { + DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = datasourceObjId; } private final Observer observer = new DeletedContentChildrenObserver(); @@ -366,7 +383,7 @@ public class DeletedContent implements AutopsyVisitableItem { @Override public void update(Observable o, Object arg) { refresh(true); - } + } } @Override @@ -405,7 +422,7 @@ public class DeletedContent implements AutopsyVisitableItem { return true; } - static private String makeQuery(DeletedContent.DeletedContentFilter filter) { + static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) { String query = ""; switch (filter) { case FS_DELETED_FILTER: @@ -443,6 +460,10 @@ public class DeletedContent implements AutopsyVisitableItem { + " OR known IS NULL)"; //NON-NLS } + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + query += " LIMIT " + MAX_OBJECTS; //NON-NLS return query; } @@ -450,7 +471,7 @@ public class DeletedContent implements AutopsyVisitableItem { private List runFsQuery() { List ret = new ArrayList<>(); - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); try { ret = skCase.findAllFilesWhere(query); } catch (TskCoreException e) { @@ -469,9 +490,9 @@ public class DeletedContent implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting deleted files search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 6d90aa9b0e..0392614fe3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -61,6 +61,8 @@ public interface DisplayableItemNodeVisitor { */ T visit(ViewsNode vn); + T visit(DataSourceGroupingNode dataSourceGroupingNode); + T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileExtensionNode fsfn); T visit(DeletedContentNode dcn); @@ -336,6 +338,11 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(vn); } + @Override + public T visit(DataSourceGroupingNode dataSourceGroupingNode) { + return defaultVisit(dataSourceGroupingNode); + } + @Override public T visit(ResultsNode rn) { return defaultVisit(rn); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 409c3165e8..d9df849d86 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -40,6 +40,7 @@ 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.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -86,10 +87,29 @@ public class EmailExtracted implements AutopsyVisitableItem { } private SleuthkitCase skCase; private final EmailResults emailResults; + private final long datasourceObjId; + + /** + * Constructor + * + * @param skCase Case DB + */ public EmailExtracted(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public EmailExtracted(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; emailResults = new EmailResults(); } @@ -141,6 +161,9 @@ public class EmailExtracted implements AutopsyVisitableItem { + "attribute_type_id=" + pathAttrId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 304073c9b6..366aa12979 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -35,9 +35,11 @@ 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.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; 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_EMAIL_MSG; @@ -57,12 +59,31 @@ import org.sleuthkit.datamodel.TskException; public class ExtractedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; // set to null after case has been closed + private Blackboard blackboard; public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text"); + private final long datasourceObjId; + /** + * Constructs extracted content object + * + * @param skCase Case DB + */ public ExtractedContent(SleuthkitCase skCase) { - this.skCase = 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.datasourceObjId = objId; + this.blackboard = skCase.getBlackboard(); + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -270,7 +291,10 @@ public class ExtractedContent implements AutopsyVisitableItem { //TEST COMMENT if (skCase != null) { try { - List types = skCase.getArtifactTypesInUse(); + List types = (UserPreferences.groupItemsInTreeByDatasource()) ? + blackboard.getArtifactTypesInUse(datasourceObjId) : + skCase.getArtifactTypesInUse() ; + types.removeAll(doNotShow); Collections.sort(types, new Comparator() { @@ -332,7 +356,9 @@ public class ExtractedContent implements AutopsyVisitableItem { // a performance increase might be had by adding a // "getBlackboardArtifactCount()" method to skCase try { - this.childCount = skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); + this.childCount = UserPreferences.groupItemsInTreeByDatasource() ? + blackboard.getArtifactsCount(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) .log(Level.WARNING, "Error getting child count", ex); //NON-NLS @@ -454,7 +480,10 @@ public class ExtractedContent implements AutopsyVisitableItem { protected boolean createKeys(List list) { if (skCase != null) { try { - List arts = skCase.getBlackboardArtifacts(type.getTypeID()); + List arts = + UserPreferences.groupItemsInTreeByDatasource() ? + blackboard.getArtifacts(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 4bf994fb0c..51ece423da 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.VirtualDirectory; public class FileSize implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public enum FileSizeFilter implements AutopsyVisitableItem { @@ -97,9 +98,14 @@ public class FileSize implements AutopsyVisitableItem { } public FileSize(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public FileSize(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -109,6 +115,9 @@ public class FileSize implements AutopsyVisitableItem { return this.skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /* * Root node. Children are nodes for specific sizes. */ @@ -116,8 +125,8 @@ public class FileSize implements AutopsyVisitableItem { private static final String NAME = NbBundle.getMessage(FileSize.class, "FileSize.fileSizeRootNode.name"); - FileSizeRootNode(SleuthkitCase skCase) { - super(Children.create(new FileSizeRootChildren(skCase), true), Lookups.singleton(NAME)); + FileSizeRootNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new FileSizeRootChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-size-16.png"); //NON-NLS @@ -161,10 +170,12 @@ public class FileSize implements AutopsyVisitableItem { public static class FileSizeRootChildren extends ChildFactory { private SleuthkitCase skCase; + private final long datasourceObjId; private Observable notifier; - public FileSizeRootChildren(SleuthkitCase skCase) { + public FileSizeRootChildren(SleuthkitCase skCase, long datasourceObjId) { this.skCase = skCase; + this.datasourceObjId = datasourceObjId; notifier = new FileSizeRootChildrenObservable(); } @@ -248,7 +259,7 @@ public class FileSize implements AutopsyVisitableItem { @Override protected Node createNodeForKey(FileSizeFilter key) { - return new FileSizeNode(skCase, key, notifier); + return new FileSizeNode(skCase, key, notifier, datasourceObjId); } /* @@ -257,12 +268,14 @@ public class FileSize implements AutopsyVisitableItem { public class FileSizeNode extends DisplayableItemNode { private FileSizeFilter filter; + private final long datasourceObjId; // use version with observer instead so that it updates @Deprecated - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter) { - super(Children.create(new FileSizeChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, null, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); } @@ -272,10 +285,12 @@ public class FileSize implements AutopsyVisitableItem { * @param filter * @param o Observable that provides updates when events are * fired + * @param datasourceObjId filter by data source, if configured in user preferences */ - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o) { - super(Children.create(new FileSizeChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, o, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); o.addObserver(new FileSizeNodeObserver()); } @@ -309,7 +324,7 @@ public class FileSize implements AutopsyVisitableItem { } private void updateDisplayName() { - final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter); + final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter, datasourceObjId); super.setDisplayName(filter.getDisplayName() + " (" + numVisibleChildren + ")"); } @@ -349,6 +364,7 @@ public class FileSize implements AutopsyVisitableItem { private final SleuthkitCase skCase; private final FileSizeFilter filter; private final Observable notifier; + private final long datasourceObjId; private static final Logger logger = Logger.getLogger(FileSizeChildren.class.getName()); /** @@ -358,10 +374,12 @@ public class FileSize implements AutopsyVisitableItem { * @param o Observable that provides updates when new files are * added to case */ - FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o) { + FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = dsObjId; + } @Override @@ -395,7 +413,7 @@ public class FileSize implements AutopsyVisitableItem { return true; } - private static String makeQuery(FileSizeFilter filter) { + private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) { String query; switch (filter) { case SIZE_50_200: @@ -427,6 +445,11 @@ public class FileSize implements AutopsyVisitableItem { query += " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + ")"; //NON-NLS } + // filter by datasource if indicated in user preferences + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + return query; } @@ -434,7 +457,7 @@ public class FileSize implements AutopsyVisitableItem { List ret = new ArrayList<>(); try { - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); ret = skCase.findAllFilesWhere(query); } catch (Exception e) { @@ -449,9 +472,9 @@ public class FileSize implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting files by size search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java index e4dca77d01..6a304d7b86 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -72,11 +72,18 @@ public final class FileTypes implements AutopsyVisitableItem { private final SleuthkitCase skCase; + private final long datasourceObjId; + FileTypes(SleuthkitCase skCase) { - this.skCase = skCase; - updateShowCounts(); + this(skCase, 0); } + FileTypes(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + updateShowCounts(); + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -86,6 +93,9 @@ public final class FileTypes implements AutopsyVisitableItem { return skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /** * Check the db to determine if the nodes should show child counts. */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 2d04fe9733..1d24f081ab 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -69,6 +69,10 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { return visitor.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Listens for case and ingest invest. Updates observers when events are * fired. FileType and FileTypes nodes are all listening to this. @@ -359,6 +363,9 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { + (UserPreferences.hideKnownFilesInViewsTree() ? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")" : " ") + + (UserPreferences.groupItemsInTreeByDatasource() + ? " AND data_source_obj_id = " + filteringDataSourceObjId() + : " ") + " AND (extension IN (" + filter.getFilter().stream() .map(String::toLowerCase) .map(s -> "'"+StringUtils.substringAfter(s, ".")+"'") diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index e41477d96e..90cb90ae77 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -42,6 +42,7 @@ 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.core.UserPreferences; import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; import org.sleuthkit.autopsy.coreutils.Logger; @@ -91,15 +92,16 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi * @return The base expression to be used in the where clause of queries for * files by mime type. */ - static private String createBaseWhereExpr() { + private String createBaseWhereExpr() { return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" + " AND (type IN (" + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() - + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + "))" + + ( UserPreferences.groupItemsInTreeByDatasource() ? " AND data_source_obj_id = " + this.filteringDataSourceObjId() : " ") + (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : ""); } @@ -188,6 +190,10 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi return visitor.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Method to check if the node in question is a ByMimeTypeNode which is * empty. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index a720409489..24bae3d368 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -42,6 +42,7 @@ 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.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -63,9 +64,29 @@ public class HashsetHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(HashsetHits.class.getName()); private SleuthkitCase skCase; private final HashsetResults hashsetResults; - + private final long datasourceObjId; + + + /** + * Constructor + * + * @param skCase Case DB + * + */ public HashsetHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public HashsetHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; hashsetResults = new HashsetResults(); } @@ -120,7 +141,10 @@ public class HashsetHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS - + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); synchronized (hashSetHitsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 7c3249f990..67622d180c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -42,6 +42,7 @@ 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.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -59,9 +60,28 @@ public class InterestingHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(InterestingHits.class.getName()); private SleuthkitCase skCase; private final InterestingResults interestingResults = new InterestingResults(); + private final long datasourceObjId; + /** + * Constructor + * + * @param skCase Case DB + * + */ public InterestingHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public InterestingHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; interestingResults.update(); } @@ -112,6 +132,9 @@ public class InterestingHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { synchronized (interestingItemsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c35d9bdda0..51367297b8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -44,6 +44,7 @@ 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.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; import org.sleuthkit.autopsy.ingest.IngestManager; @@ -73,6 +74,7 @@ public class KeywordHits implements AutopsyVisitableItem { private SleuthkitCase skCase; private final KeywordResults keywordResults; + private final long datasourceObjId; /** * String used in the instance MAP so that exact matches and substring can @@ -81,6 +83,7 @@ public class KeywordHits implements AutopsyVisitableItem { */ private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME"; + /** * query attributes table for the ones that we need for the tree */ @@ -101,8 +104,25 @@ public class KeywordHits implements AutopsyVisitableItem { return (instances.size() == 1) && (instances.get(0).equals(DEFAULT_INSTANCE_NAME)); } - public KeywordHits(SleuthkitCase skCase) { + /** + * Constructor + * + * @param skCase Case DB + */ + KeywordHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public KeywordHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; keywordResults = new KeywordResults(); } @@ -300,7 +320,12 @@ public class KeywordHits implements AutopsyVisitableItem { return; } - try (CaseDbQuery dbQuery = skCase.executeQuery(KEYWORD_HIT_ATTRIBUTES_QUERY)) { + String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY; + if (UserPreferences.groupItemsInTreeByDatasource()) { + queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { long artifactId = resultSet.getLong("artifact_id"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java index 7f1cd1cea7..b2d9f4799b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java @@ -26,11 +26,17 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Results implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Results(SleuthkitCase skCase) { - this.skCase = 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); @@ -39,4 +45,8 @@ public class Results implements AutopsyVisitableItem { 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 index 87a3d5f903..91d18bbcc6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,17 +33,22 @@ public class ResultsNode extends DisplayableItemNode { @NbBundle.Messages("ResultsNode.name.text=Results") public static final String NAME = Bundle.ResultsNode_name_text(); - - public ResultsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new ExtractedContent(sleuthkitCase), - new KeywordHits(sleuthkitCase), - new HashsetHits(sleuthkitCase), - new EmailExtracted(sleuthkitCase), - new InterestingHits(sleuthkitCase), - new Accounts(sleuthkitCase) - )), Lookups.singleton(NAME)); + 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) ) + ), + Lookups.singleton(NAME)); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/results.png"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 9a01fa608f..c41a750f7e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -36,6 +36,7 @@ import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -57,6 +58,20 @@ public class Tags implements AutopsyVisitableItem { private final String DISPLAY_NAME = NbBundle.getMessage(RootNode.class, "TagsNode.displayName.text"); private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final long datasourceObjId; + + Tags() { + this(0); + } + + Tags(long dsObjId) { + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -83,11 +98,13 @@ public class Tags implements AutopsyVisitableItem { */ public class RootNode extends DisplayableItemNode { - public RootNode() { - super(Children.create(new TagNameNodeFactory(), true), Lookups.singleton(DISPLAY_NAME)); + + public RootNode(long objId) { + super(Children.create(new TagNameNodeFactory(objId), true), Lookups.singleton(DISPLAY_NAME)); super.setName(DISPLAY_NAME); super.setDisplayName(DISPLAY_NAME); this.setIconBaseWithExtension(ICON_PATH); + } @Override @@ -121,6 +138,8 @@ public class Tags implements AutopsyVisitableItem { private class TagNameNodeFactory extends ChildFactory.Detachable implements Observer { + private final long datasourceObjId; + private final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.CONTENT_TAG_ADDED, @@ -176,6 +195,15 @@ public class Tags implements AutopsyVisitableItem { } }; + /** + * Constructor + * @param objId data source object id + */ + TagNameNodeFactory(long objId) { + this.datasourceObjId = objId; + + } + @Override protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); @@ -196,7 +224,11 @@ public class Tags implements AutopsyVisitableItem { @Override protected boolean createKeys(List keys) { try { - List tagNamesInUse = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); + + List tagNamesInUse = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse() + ; Collections.sort(tagNamesInUse); keys.addAll(tagNamesInUse); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -244,8 +276,15 @@ public class Tags implements AutopsyVisitableItem { long tagsCount = 0; try { TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagsCount = tm.getContentTagsCountByTagName(tagName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + if (UserPreferences.groupItemsInTreeByDatasource()) { + tagsCount = tm.getContentTagsCountByTagName(tagName, datasourceObjId); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId); + } + else { + tagsCount = tm.getContentTagsCountByTagName(tagName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + } + } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -348,7 +387,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -403,7 +444,11 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { // Use the content tags bearing the specified tag name as the keys. try { - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName)); + List contentTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName); + + keys.addAll(contentTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } @@ -447,7 +492,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -502,7 +549,10 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { try { // Use the blackboard artifact tags bearing the specified tag name as the keys. - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName)); + List artifactTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName); + keys.addAll(artifactTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java index b31cfda543..d2a8671678 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -26,11 +26,21 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Views implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Views(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public Views(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 08f2369a61..a1114761bb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,19 +34,28 @@ public class ViewsNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(ViewsNode.class, "ViewsNode.name.text"); public ViewsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new FileTypes(sleuthkitCase), - // June '15: Recent Files was removed because it was not useful w/out filtering - // add it back in if we can filter the results to a more managable size. - // new RecentFiles(sleuthkitCase), - new DeletedContent(sleuthkitCase), - new FileSize(sleuthkitCase))), - Lookups.singleton(NAME)); + this(sleuthkitCase, 0); + } + + public ViewsNode(SleuthkitCase sleuthkitCase, long dsObjId) { + + super( + new RootContentChildren(Arrays.asList( + new FileTypes(sleuthkitCase, dsObjId), + // June '15: Recent Files was removed because it was not useful w/out filtering + // add it back in if we can filter the results to a more managable size. + // new RecentFiles(sleuthkitCase), + new DeletedContent(sleuthkitCase, dsObjId), + new FileSize(sleuthkitCase, dsObjId)) + ), + Lookups.singleton(NAME) + ); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); //NON-NLS } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 8301928528..557e6582ca 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -58,6 +58,7 @@ import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor; @@ -94,6 +95,8 @@ final public class Accounts implements AutopsyVisitableItem { final public static String NAME = Bundle.AccountsRootNode_name(); private SleuthkitCase skCase; + private final long datasourceObjId; + private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus"); /* Should rejected accounts be shown in the accounts section of the tree. */ @@ -108,12 +111,24 @@ final public class Accounts implements AutopsyVisitableItem { * @param skCase The SleuthkitCase object to use for db queries. */ public Accounts(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + * @param objId Object id of the data source + */ + public Accounts(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; this.rejectActionInstance = new RejectAccounts(); this.approveActionInstance = new ApproveAccounts(); } - + + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -130,6 +145,18 @@ final public class Accounts implements AutopsyVisitableItem { return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS } + /** + * Returns the clause to filter artifacts by data source. + * + * @return A clause that will or will not filter artifacts by datasource + * based on the UserPreferences groupItemsInTreeByDatasource setting + */ + private String getFilterByDataSourceClause() { + return (UserPreferences.groupItemsInTreeByDatasource()) ? + " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId + " " + : " "; + } + /** * Gets a new Action that when invoked toggles showing rejected artifacts on * or off. @@ -291,10 +318,14 @@ final public class Accounts implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery( - "SELECT DISTINCT blackboard_attributes.value_text as account_type " - + " FROM blackboard_attributes " - + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()); + String accountTypesInUseQuery = + "SELECT DISTINCT blackboard_attributes.value_text as account_type " + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() + + getFilterByDataSourceClause(); + + try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery ); ResultSet resultSet = executeQuery.getResultSet()) { while (resultSet.next()) { String accountType = resultSet.getString("account_type"); @@ -429,6 +460,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet rs = results.getResultSet();) { @@ -739,6 +771,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS + " ORDER BY hits DESC "; //NON-NLS @@ -807,6 +840,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo"; try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -943,6 +977,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY BIN " //NON-NLS + " ORDER BY BIN "; //NON-NLS @@ -1009,6 +1044,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { @@ -1304,6 +1340,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " ORDER BY blackboard_attributes.value_text"; //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -1375,6 +1412,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index 647c6f70d3..0e7f3cf315 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -119,3 +119,4 @@ AddExternalViewerRulePanel.browseButton.text=Browse AddExternalViewerRulePanel.exePathTextField.text= AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension AddExternalViewerRulePanel.extRadioButton.text=Extension +DirectoryTreeTopComponent.groupByDatasourceCheckBox.text=Group by Data Source diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index 90c6ac00a6..48dfad0bb6 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -16,14 +16,17 @@ - - - + + + - - + + + + + @@ -31,14 +34,23 @@ - - - - - + + + + + + + + + + + + + + - - + + @@ -130,5 +142,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 2612c75675..33977d5904 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -30,6 +30,7 @@ import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.prefs.PreferenceChangeEvent; @@ -64,8 +65,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.CreditCards; -import org.sleuthkit.autopsy.datamodel.DataSources; -import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.EmailExtracted; import org.sleuthkit.autopsy.datamodel.EmptyNode; @@ -73,12 +72,8 @@ import org.sleuthkit.autopsy.datamodel.ExtractedContent; import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.InterestingHits; import org.sleuthkit.autopsy.datamodel.KeywordHits; -import org.sleuthkit.autopsy.datamodel.Reports; -import org.sleuthkit.autopsy.datamodel.Results; import org.sleuthkit.autopsy.datamodel.ResultsNode; -import org.sleuthkit.autopsy.datamodel.RootContentChildren; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Views; +import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildrenFactory; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; @@ -107,7 +102,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final LinkedList forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); - private RootContentChildren contentChildren; + private AutopsyTreeChildrenFactory autopsyTreeChildrenFactory; + private Children autopsyTreeChildren; /** * the constructor @@ -130,6 +126,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.forwardList = new LinkedList<>(); backButton.setEnabled(false); forwardButton.setEnabled(false); + + groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource()); } /** @@ -142,6 +140,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat switch (evt.getKey()) { case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE: case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE: + case UserPreferences.GROUP_ITEMS_IN_TREE_BY_DATASOURCE: refreshContentTreeSafe(); break; case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE: @@ -182,6 +181,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat backButton = new javax.swing.JButton(); forwardButton = new javax.swing.JButton(); showRejectedCheckBox = new javax.swing.JCheckBox(); + groupByDatasourceCheckBox = new javax.swing.JCheckBox(); treeView.setBorder(null); @@ -219,30 +219,45 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat org.openide.awt.Mnemonics.setLocalizedText(showRejectedCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.showRejectedCheckBox.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(groupByDatasourceCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.groupByDatasourceCheckBox.text")); // NOI18N + groupByDatasourceCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + groupByDatasourceCheckBoxActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) + .addComponent(treeView) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) + .addContainerGap() .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE) - .addComponent(showRejectedCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 65, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showRejectedCheckBox) + .addComponent(groupByDatasourceCheckBox)) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(showRejectedCheckBox)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(showRejectedCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(groupByDatasourceCheckBox)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 854, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents @@ -296,9 +311,14 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.setCursor(null); }//GEN-LAST:event_forwardButtonActionPerformed + private void groupByDatasourceCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_groupByDatasourceCheckBoxActionPerformed + UserPreferences.setGroupItemsInTreeByDatasource(this.groupByDatasourceCheckBox.isSelected()); + }//GEN-LAST:event_groupByDatasourceCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton backButton; private javax.swing.JButton forwardButton; + private javax.swing.JCheckBox groupByDatasourceCheckBox; private javax.swing.JCheckBox showRejectedCheckBox; private javax.swing.JScrollPane treeView; // End of variables declaration//GEN-END:variables @@ -376,13 +396,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } else { // if there's at least one image, load the image and open the top component final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); - contentChildren = new RootContentChildren(Arrays.asList( - new DataSources(), - new Views(tskCase), - new Results(tskCase), - new Tags(), - new Reports())); - Node root = new AbstractNode(contentChildren) { + + autopsyTreeChildrenFactory = new AutopsyTreeChildrenFactory(tskCase); + autopsyTreeChildren = Children.create(autopsyTreeChildrenFactory, true); + Node root = new AbstractNode(autopsyTreeChildren) { //JIRA-2807: What is the point of these overrides? /** * to override the right click action in the white blank space @@ -422,17 +439,23 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat TreeView tree = getTree(); Node results = rootChildren.findChild(ResultsNode.NAME); - tree.expandNode(results); - Children resultsChildren = results.getChildren(); - Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); + if (!Objects.isNull(results)) { + tree.expandNode(results); + Children resultsChildren = results.getChildren(); + Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); - Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); - showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); - showRejectedCheckBox.setSelected(false); + Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); + if (!Objects.isNull(accounts)) { + showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); + showRejectedCheckBox.setSelected(false); + } + } Node views = rootChildren.findChild(ViewsNode.NAME); - Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); - tree.collapseNode(views); + if (!Objects.isNull(views)) { + Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); + tree.collapseNode(views); + } /* * JIRA-2806: What is this supposed to do? Right now it selects * the data sources node, but the comment seems to indicate @@ -464,7 +487,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // of changing the selected node fires a handler that tries to make // dataResult active) try { - em.setSelectedNodes(get()); + Node[] selections = get(); + if (selections != null && selections.length > 0){ + em.setSelectedNodes(selections); + } } catch (PropertyVetoException ex) { LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS } catch (InterruptedException | ExecutionException ex) { @@ -486,7 +512,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat @Override public void componentClosed() { //@@@ push the selection node to null? - contentChildren = null; + autopsyTreeChildren = null; } void writeProperties(java.util.Properties p) { @@ -729,7 +755,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat */ String[] currentLast = backList.peekLast(); String lastNodeName = null; - if (currentLast != null) { + if (currentLast != null && currentLast.length > 0) { lastNodeName = currentLast[currentLast.length - 1]; } @@ -773,27 +799,56 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Refresh the content node part of the dir tree safely in the EDT thread */ public void refreshContentTreeSafe() { - SwingUtilities.invokeLater(this::refreshDataSourceTree); + SwingUtilities.invokeLater(this::rebuildTree); } /** - * Refreshes changed content nodes + * Rebuilds the directory tree */ - private void refreshDataSourceTree() { - Node selectedNode = getSelectedNode(); - final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); - Children rootChildren = em.getRootContext().getChildren(); - Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME); - if (dataSourcesFilterNode == null) { - LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS - return; - } - Node dataSourcesNode = ((DirectoryTreeFilterNode) dataSourcesFilterNode).getOriginal(); - DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) dataSourcesNode.getChildren(); - contentRootChildren.refreshContentKeys(); - setSelectedNode(selectedPath, DataSourcesNode.NAME); - } + private void rebuildTree() { + // refresh all children of the root. + autopsyTreeChildrenFactory.refreshChildren(); + + // Select the first node and reset the selection history + // This should happen on the EDT once the tree has been rebuilt. + // hence the SwingWorker that does this in the done() method + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + selectFirstChildNode(); + resetHistory(); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Error selecting tree node.", ex); //NON-NLS + } //NON-NLS + } + }.execute(); + } + + /** + * Selects the first node in the tree. + * + */ + private void selectFirstChildNode () { + Children rootChildren = em.getRootContext().getChildren(); + + if (rootChildren.getNodesCount() > 0) { + Node firstNode = rootChildren.getNodeAt(0); + if (firstNode != null) { + final String[] selectedPath = NodeOp.createPath(firstNode, em.getRootContext()); + setSelectedNode(selectedPath, null); + } + } + } /** * Set the selected node using a path to a previously selected node. * diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 792252605a..f4da3a3edf 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -24,6 +24,7 @@ import java.beans.PropertyVetoException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; @@ -33,6 +34,8 @@ import org.openide.explorer.view.TreeView; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; @@ -44,9 +47,12 @@ import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.VolumeSystem; /** @@ -122,7 +128,9 @@ public class ViewContextAction extends AbstractAction { @Override @Messages({ "ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory.", - "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.",}) + "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.", + "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree." + }) public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { /* @@ -130,7 +138,40 @@ public class ViewContextAction extends AbstractAction { */ DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); - Node parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + + Node parentTreeViewNode; + if (UserPreferences.groupItemsInTreeByDatasource()) { // 'Group by Data Source' view + + SleuthkitCase skCase; + String dsname; + try { + // get the objid/name of the datasource of the selected content. + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + long contentDSObjid = content.getDataSource().getId(); + DataSource datasource = skCase.getDataSource(contentDSObjid); + dsname = datasource.getName(); + + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + Node datasourceGroupingNode = rootChildren.findChild(dsname); + if (! Objects.isNull(datasourceGroupingNode) ) { + Children dsChildren = datasourceGroupingNode.getChildren(); + parentTreeViewNode = dsChildren.findChild(DataSourcesNode.NAME); + } + else { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS + return; + } + } catch (NoCurrentCaseException| TskDataException | TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS + return; + } + } else { // Classic view + // Start the search at the DataSourcesNode + parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + } + /* * Get the parent content for the content to be selected in the * results view. If the parent content is null, then the specified