diff --git a/Core/build.xml b/Core/build.xml index 609fb1367b..009e2012cf 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -84,17 +84,17 @@ - - - - - - - - - - - + + + + + + + + + + + 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/centralrepository/AddEditCentralRepoCommentAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java index 8c03883523..dfeb4fbdcd 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java @@ -83,8 +83,13 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { * comment. The comment will be updated in the database if the file instance * exists there, or a new file instance will be added to the database with * the comment attached otherwise. + * + * The current comment for this instance is returned in case it is needed to + * update the display. + * + * @return the current comment for this instance */ - public void addEditCentralRepoComment() { + public String addEditCentralRepoComment() { CentralRepoCommentDialog centralRepoCommentDialog = new CentralRepoCommentDialog(correlationAttribute, title); centralRepoCommentDialog.display(); @@ -103,6 +108,7 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); } } + return centralRepoCommentDialog.getComment(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java index 4e42bfe9d7..c95b0bfde7 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java @@ -31,6 +31,7 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { private final CorrelationAttribute correlationAttribute; private boolean commentUpdated = false; + private String currentComment = ""; /** * Create an instance. @@ -44,6 +45,11 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { initComponents(); CorrelationAttributeInstance instance = correlationAttribute.getInstances().get(0); + + // Store the original comment + if (instance.getComment() != null) { + currentComment = instance.getComment(); + } pathLabel.setText(instance.getFilePath()); commentTextArea.setText(instance.getComment()); @@ -71,6 +77,16 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { boolean isCommentUpdated() { return commentUpdated; } + + /** + * Get the current comment. + * If the user hit OK, this will be the new comment. + * If the user canceled, this will be the original comment. + * @return the comment + */ + String getComment() { + return currentComment; + } /** * This method is called from within the constructor to initialize the form. @@ -168,8 +184,8 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { }//GEN-LAST:event_cancelButtonActionPerformed private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - String comment = commentTextArea.getText(); - correlationAttribute.getInstances().get(0).setComment(comment); + currentComment = commentTextArea.getText(); + correlationAttribute.getInstances().get(0).setComment(currentComment); commentUpdated = true; dispose(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java new file mode 100755 index 0000000000..5a6e8fa652 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java @@ -0,0 +1,56 @@ +/* + * Central Repository + * + * 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.centralrepository; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.swing.Action; +import org.openide.util.Utilities; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; +import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * This creates a single context menu item for adding or editing a Central + * Repository comment. + */ +@ServiceProvider(service = ContextMenuActionsProvider.class) +public class CentralRepoContextMenuActionsProvider implements ContextMenuActionsProvider { + + @Override + public List getActions() { + ArrayList actions = new ArrayList<>(); + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + + if (selectedFiles.size() != 1) { + return actions; + } + + for (AbstractFile file : selectedFiles) { + if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { + actions.add(AddEditCentralRepoCommentAction.createAddEditCentralRepoCommentAction(file)); + } + } + + return actions; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index bf7fef5fa8..15ed21d753 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -56,7 +56,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -69,7 +68,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskDataException; /** * View correlation results from other cases @@ -119,10 +117,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } else if (jmi.equals(showCommonalityMenuItem)) { showCommonalityDetails(); } else if (jmi.equals(addCommentMenuItem)) { - CorrelationAttribute selectedAttribute = (CorrelationAttribute) tableModel.getRow(otherCasesTable.getSelectedRow()); - AddEditCentralRepoCommentAction action = AddEditCentralRepoCommentAction.createAddEditCommentAction(selectedAttribute); - action.addEditCentralRepoComment(); - otherCasesTable.repaint(); + try { + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(otherCasesTable.getSelectedRow()); + AddEditCentralRepoCommentAction action = AddEditCentralRepoCommentAction.createAddEditCommentAction(selectedNode.createCorrelationAttribute()); + String currentComment = action.addEditCentralRepoComment(); + selectedNode.updateComment(currentComment); + otherCasesTable.repaint(); + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error performing Add/Edit Comment action", ex); + } } } }; @@ -202,8 +205,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi if (-1 != selectedRowViewIdx) { EamDb dbManager = EamDb.getInstance(); int selectedRowModelIdx = otherCasesTable.convertRowIndexToModel(selectedRowViewIdx); - CorrelationAttribute eamArtifact = (CorrelationAttribute) tableModel.getRow(selectedRowModelIdx); - CorrelationCase eamCasePartial = eamArtifact.getInstances().get(0).getCorrelationCase(); + OtherOccurrenceNodeData nodeData = (OtherOccurrenceNodeData) tableModel.getRow(selectedRowModelIdx); + CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase(); if (eamCasePartial == null) { JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(), @@ -454,9 +457,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Query the database for artifact instances from other cases correlated to - * the given central repository artifact. Instances from the same datasource - * / device will also be included. + * Query the central repo database (if enabled) and the case database to find all + * artifact instances correlated to the given central repository artifact. If the + * central repo is not enabled, this will only return files from the current case + * with matching MD5 hashes. * * @param corAttr CorrelationAttribute to query for * @param dataSourceName Data source to filter results @@ -464,33 +468,46 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi * * @return A collection of correlated artifact instances */ - private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { + private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { // @@@ Check exception try { final Case openCase = Case.getCurrentCase(); String caseUUID = openCase.getName(); - String filePath = (file.getParentPath() + file.getName()).toLowerCase(); - HashMap artifactInstances = new HashMap<>(); + + HashMap nodeDataMap = new HashMap<>(); if (EamDb.isEnabled()) { - EamDb dbManager = EamDb.getInstance(); - artifactInstances.putAll(dbManager.getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).stream() - .filter(artifactInstance -> !artifactInstance.getFilePath().equals(filePath) - || !artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) - || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) - || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId)) - .collect(Collectors.toMap(correlationAttr -> new UniquePathKey(correlationAttr.getCorrelationDataSource().getDeviceID(), correlationAttr.getFilePath()), - correlationAttr -> correlationAttr))); - } + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()); - if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { - List caseDbFiles = addCaseDbMatches(corAttr, openCase); - for (AbstractFile caseDbFile : caseDbFiles) { - addOrUpdateAttributeInstance(openCase, artifactInstances, caseDbFile); + for (CorrelationAttributeInstance artifactInstance:instances) { + + // Only add the attribute if it isn't the object the user selected. + // We consider it to be a different object if at least one of the following is true: + // - the case UUID is different + // - the data source name is different + // - the data source device ID is different + // - the file path is different + if (!artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) + || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) + || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId) + || !artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName())) { + + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + nodeDataMap.put(uniquePathKey, newNode); + } } } - return artifactInstances; + if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { + List caseDbFiles = getCaseDbMatches(corAttr, openCase); + + for (AbstractFile caseDbFile : caseDbFiles) { + addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile); + } + } + + return nodeDataMap; } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { @@ -504,7 +521,16 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return new HashMap<>(0); } - private List addCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { + /** + * Get all other abstract files in the current case with the same MD5 as the selected node. + * @param corAttr The CorrelationAttribute containing the MD5 to search for + * @param openCase The current case + * @return List of matching AbstractFile objects + * @throws NoCurrentCaseException + * @throws TskCoreException + * @throws EamDbException + */ + private List getCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { String md5 = corAttr.getCorrelationValue(); SleuthkitCase tsk = openCase.getSleuthkitCase(); @@ -522,75 +548,64 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Adds the file to the artifactInstances map if it does not already exist - * - * @param autopsyCase - * @param artifactInstances + * Adds the file to the nodeDataMap map if it does not already exist + * + * @param autopsyCase + * @param nodeDataMap * @param newFile * * @throws TskCoreException * @throws EamDbException */ - private void addOrUpdateAttributeInstance(final Case autopsyCase, Map artifactInstances, AbstractFile newFile) throws TskCoreException, EamDbException { - - // figure out if the casedb file is known via either hash or tags - TskData.FileKnown localKnown = newFile.getKnown(); - - if (localKnown != TskData.FileKnown.BAD) { + private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, EamDbException { + + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(newFile, autopsyCase); + + // If the caseDB object has a notable tag associated with it, update + // the known status to BAD + if (newNode.getKnown() != TskData.FileKnown.BAD) { List fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile); for (ContentTag tag : fileMatchTags) { TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus(); if (tagKnownStatus.equals(TskData.FileKnown.BAD)) { - localKnown = TskData.FileKnown.BAD; + newNode.updateKnown(TskData.FileKnown.BAD); break; } } } - // make a key to see if the file is already in the map - String filePath = newFile.getParentPath() + newFile.getName(); - String deviceId; - try { - deviceId = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()).getDeviceId(); - } catch (TskDataException | TskCoreException ex) { - logger.log(Level.WARNING, "Error getting data source info: {0}", ex); - return; - } - UniquePathKey uniquePathKey = new UniquePathKey(deviceId, filePath); - - // double check that the CR version is BAD if the caseDB version is BAD. - if (artifactInstances.containsKey(uniquePathKey)) { - if (localKnown == TskData.FileKnown.BAD) { - CorrelationAttributeInstance prevInstance = artifactInstances.get(uniquePathKey); - prevInstance.setKnownStatus(localKnown); + // Make a key to see if the file is already in the map + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + + // If this node is already in the list, the only thing we need to do is + // update the known status to BAD if the caseDB version had known status BAD. + // Otherwise this is a new node so add the new node to the map. + if (nodeDataMap.containsKey(uniquePathKey)) { + if (newNode.getKnown() == TskData.FileKnown.BAD) { + OtherOccurrenceNodeData prevInstance = nodeDataMap.get(uniquePathKey); + prevInstance.updateKnown(newNode.getKnown()); } - } // add the data from the case DB by pushing data into CorrelationAttributeInstance class - else { - // NOTE: If we are in here, it is likely because CR is not enabled. So, we cannot rely - // on any of the methods that query the DB. - CorrelationCase correlationCase = new CorrelationCase(autopsyCase.getName(), autopsyCase.getDisplayName()); - - CorrelationDataSource correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, newFile.getDataSource()); - - CorrelationAttributeInstance caseDbInstance = new CorrelationAttributeInstance(correlationCase, correlationDataSource, filePath, "", localKnown); - artifactInstances.put(uniquePathKey, caseDbInstance); + } else { + nodeDataMap.put(uniquePathKey, newNode); } } @Override public boolean isSupported(Node node) { - this.file = this.getAbstractFileFromNode(node); - // Is supported if this node - // has correlatable content (File, BlackboardArtifact) OR - // other common files across datasources. + // Is supported if one of the following is true: + // - The central repo is enabled and the node has correlatable content + // (either through the MD5 hash of the associated file or through a BlackboardArtifact) + // - The central repo is disabled and the backing file has a valid MD5 hash + this.file = this.getAbstractFileFromNode(node); if (EamDb.isEnabled()) { return this.file != null && this.file.getSize() > 0 && !getCorrelationAttributesFromNode(node).isEmpty(); } else { return this.file != null - && this.file.getSize() > 0; + && this.file.getSize() > 0 + && ((this.file.getMd5Hash() != null) && ( ! this.file.getMd5Hash().isEmpty())); } } @@ -632,22 +647,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // get the attributes we can correlate on correlationAttributes.addAll(getCorrelationAttributesFromNode(node)); for (CorrelationAttribute corAttr : correlationAttributes) { - Map corAttrInstances = new HashMap<>(0); + Map correlatedNodeDataMap = new HashMap<>(0); // get correlation and reference set instances from DB - corAttrInstances.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + + correlatedNodeDataMap.values().forEach((nodeData) -> { + tableModel.addNodeData(nodeData); - corAttrInstances.values().forEach((corAttrInstance) -> { - try { - CorrelationAttribute newCeArtifact = new CorrelationAttribute( - corAttr.getCorrelationType(), - corAttr.getCorrelationValue() - ); - newCeArtifact.addInstance(corAttrInstance); - tableModel.addEamArtifact(newCeArtifact); - } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error creating correlation attribute", ex); - } }); } @@ -816,18 +823,19 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi }// //GEN-END:initComponents private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible - boolean addCommentMenuItemVisible = false; + boolean enableCentralRepoActions = false; if (EamDbUtil.useCentralRepo() && otherCasesTable.getSelectedRowCount() == 1) { int rowIndex = otherCasesTable.getSelectedRow(); - CorrelationAttribute selectedAttribute = (CorrelationAttribute) tableModel.getRow(rowIndex); - if (selectedAttribute.getInstances().get(0).isDatabaseInstance() - && selectedAttribute.getCorrelationType().getId() == CorrelationAttribute.FILES_TYPE_ID) { - addCommentMenuItemVisible = true; + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex); + if (selectedNode.isCentralRepoNode()) { + enableCentralRepoActions = true; } } - addCommentMenuItem.setVisible(addCommentMenuItemVisible); + addCommentMenuItem.setVisible(enableCentralRepoActions); + showCaseDetailsMenuItem.setVisible(enableCentralRepoActions); + showCommonalityMenuItem.setVisible(enableCentralRepoActions); }//GEN-LAST:event_rightClickPopupMenuPopupMenuWillBecomeVisible // Variables declaration - do not modify//GEN-BEGIN:variables @@ -854,33 +862,26 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private final String dataSourceID; private final String filePath; + private final String type; - UniquePathKey(String theDataSource, String theFilePath) { + UniquePathKey(OtherOccurrenceNodeData nodeData) { super(); - dataSourceID = theDataSource; - filePath = theFilePath.toLowerCase(); - } - - /** - * - * @return the dataSourceID device ID - */ - String getDataSourceID() { - return dataSourceID; - } - - /** - * - * @return the filPath including the filename and extension. - */ - String getFilePath() { - return filePath; + dataSourceID = nodeData.getDeviceID(); + if (nodeData.getFilePath() != null) { + filePath = nodeData.getFilePath().toLowerCase(); + } else { + filePath = null; + } + type = nodeData.getType(); } @Override public boolean equals(Object other) { if (other instanceof UniquePathKey) { - return ((UniquePathKey) other).getDataSourceID().equals(dataSourceID) && ((UniquePathKey) other).getFilePath().equals(filePath); + UniquePathKey otherKey = (UniquePathKey)(other); + return ( Objects.equals(otherKey.dataSourceID, this.dataSourceID) + && Objects.equals(otherKey.filePath, this.filePath) + && Objects.equals(otherKey.type, this.type)); } return false; } @@ -890,7 +891,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi //int hash = 7; //hash = 67 * hash + this.dataSourceID.hashCode(); //hash = 67 * hash + this.filePath.hashCode(); - return Objects.hash(dataSourceID, filePath); + return Objects.hash(dataSourceID, filePath, type); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java index f0ecb31e72..5febf88dc3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java @@ -68,10 +68,10 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } }; - List eamArtifacts; + List nodeDataList; DataContentViewerOtherCasesTableModel() { - eamArtifacts = new ArrayList<>(); + nodeDataList = new ArrayList<>(); } @Override @@ -95,7 +95,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public int getRowCount() { - return eamArtifacts.size(); + return nodeDataList.size(); } @Override @@ -105,15 +105,15 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public Object getValueAt(int rowIdx, int colIdx) { - if (0 == eamArtifacts.size()) { + if (0 == nodeDataList.size()) { return Bundle.DataContentViewerOtherCasesTableModel_noData(); } return mapValueById(rowIdx, TableColumns.values()[colIdx]); } - public Object getRow(int rowIdx) { - return eamArtifacts.get(rowIdx); + Object getRow(int rowIdx) { + return nodeDataList.get(rowIdx); } /** @@ -125,40 +125,39 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { * @return value in the cell */ private Object mapValueById(int rowIdx, TableColumns colId) { - CorrelationAttribute eamArtifact = eamArtifacts.get(rowIdx); - CorrelationAttributeInstance eamArtifactInstance = eamArtifact.getInstances().get(0); + OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx); String value = Bundle.DataContentViewerOtherCasesTableModel_noData(); switch (colId) { case CASE_NAME: - if (null != eamArtifactInstance.getCorrelationCase()) { - value = eamArtifactInstance.getCorrelationCase().getDisplayName(); + if (null != nodeData.getCaseName()) { + value = nodeData.getCaseName(); } break; case DEVICE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getDeviceID(); + if (null != nodeData.getDeviceID()) { + value = nodeData.getDeviceID(); } break; case DATA_SOURCE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getName(); + if (null != nodeData.getDataSourceName()) { + value = nodeData.getDataSourceName(); } break; case FILE_PATH: - value = eamArtifactInstance.getFilePath(); + value = nodeData.getFilePath(); break; case TYPE: - value = eamArtifact.getCorrelationType().getDisplayName(); + value = nodeData.getType(); break; case VALUE: - value = eamArtifact.getCorrelationValue(); + value = nodeData.getValue(); break; case KNOWN: - value = eamArtifactInstance.getKnownStatus().getName(); + value = nodeData.getKnown().getName(); break; case COMMENT: - value = eamArtifactInstance.getComment(); + value = nodeData.getComment(); break; } return value; @@ -170,18 +169,17 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } /** - * Add one local central repository artifact to the table. + * Add one correlated instance object to the table * - * @param eamArtifact central repository artifact to add to the - * table + * @param newNodeData data to add to the table */ - public void addEamArtifact(CorrelationAttribute eamArtifact) { - eamArtifacts.add(eamArtifact); + void addNodeData(OtherOccurrenceNodeData newNodeData) { + nodeDataList.add(newNodeData); fireTableDataChanged(); } - public void clearTable() { - eamArtifacts.clear(); + void clearTable() { + nodeDataList.clear(); fireTableDataChanged(); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java new file mode 100644 index 0000000000..958068fb14 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java @@ -0,0 +1,233 @@ +/* + * Central Repository + * + * 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.centralrepository.contentviewer; + +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; + +/** + * Class for populating the Other Occurrences tab + */ +class OtherOccurrenceNodeData { + + // For now hard code the string for the central repo files type, since + // getting it dynamically can fail. + private static final String FILE_TYPE_STR = "Files"; + + private final String caseName; + private String deviceID; + private String dataSourceName; + private final String filePath; + private final String typeStr; + private final CorrelationAttribute.Type type; + private final String value; + private TskData.FileKnown known; + private String comment; + + private AbstractFile originalAbstractFile = null; + private CorrelationAttributeInstance originalCorrelationInstance = null; + + /** + * Create a node from a central repo instance. + * @param instance The central repo instance + * @param type The type of the instance + * @param value The value of the instance + */ + OtherOccurrenceNodeData(CorrelationAttributeInstance instance, CorrelationAttribute.Type type, String value) { + caseName = instance.getCorrelationCase().getDisplayName(); + deviceID = instance.getCorrelationDataSource().getDeviceID(); + dataSourceName = instance.getCorrelationDataSource().getName(); + filePath = instance.getFilePath(); + this.typeStr = type.getDisplayName(); + this.type = type; + this.value = value; + known = instance.getKnownStatus(); + comment = instance.getComment(); + + originalCorrelationInstance = instance; + } + + /** + * Create a node from an abstract file. + * @param newFile The abstract file + * @param autopsyCase The current case + * @throws EamDbException + */ + OtherOccurrenceNodeData(AbstractFile newFile, Case autopsyCase) throws EamDbException { + caseName = autopsyCase.getDisplayName(); + try { + DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()); + deviceID = dataSource.getDeviceId(); + dataSourceName = dataSource.getName(); + } catch (TskDataException | TskCoreException ex) { + throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex); + } + + filePath = newFile.getParentPath() + newFile.getName(); + typeStr = FILE_TYPE_STR; + this.type = null; + value = newFile.getMd5Hash(); + known = newFile.getKnown(); + comment = ""; + + originalAbstractFile = newFile; + } + + /** + * Check if this node is a "file" type + * @return true if it is a file type + */ + boolean isFileType() { + return FILE_TYPE_STR.equals(typeStr); + } + + /** + * Update the known status for this node + * @param newKnownStatus The new known status + */ + void updateKnown(TskData.FileKnown newKnownStatus) { + known = newKnownStatus; + } + + /** + * Update the comment for this node + * @param newComment The new comment + */ + void updateComment(String newComment) { + comment = newComment; + } + + /** + * Check if this is a central repo node. + * @return true if this node was created from a central repo instance, false otherwise + */ + boolean isCentralRepoNode() { + return (originalCorrelationInstance != null); + } + + /** + * Uses the saved instance plus type and value to make a new CorrelationAttribute. + * Should only be called if isCentralRepoNode() is true. + * @return the newly created CorrelationAttribute + */ + CorrelationAttribute createCorrelationAttribute() throws EamDbException { + if (! isCentralRepoNode() ) { + throw new EamDbException("Can not create CorrelationAttribute for non central repo node"); + } + CorrelationAttribute attr = new CorrelationAttribute(type, value); + attr.addInstance(originalCorrelationInstance); + return attr; + } + + /** + * Get the case name + * @return the case name + */ + String getCaseName() { + return caseName; + } + + /** + * Get the device ID + * @return the device ID + */ + String getDeviceID() { + return deviceID; + } + + /** + * Get the data source name + * @return the data source name + */ + String getDataSourceName() { + return dataSourceName; + } + + /** + * Get the file path + * @return the file path + */ + String getFilePath() { + return filePath; + } + + /** + * Get the type (as a string) + * @return the type + */ + String getType() { + return typeStr; + } + + /** + * Get the value (MD5 hash for files) + * @return the value + */ + String getValue() { + return value; + } + + /** + * Get the known status + * @return the known status + */ + TskData.FileKnown getKnown() { + return known; + } + + /** + * Get the comment + * @return the comment + */ + String getComment() { + return comment; + } + + /** + * Get the backing abstract file. + * Should only be called if isCentralRepoNode() is false + * @return the original abstract file + */ + AbstractFile getAbstractFile() throws EamDbException { + if (originalCorrelationInstance == null) { + throw new EamDbException("AbstractFile is null"); + } + return originalAbstractFile; + } + + /** + * Get the backing CorrelationAttributeInstance. + * Should only be called if isCentralRepoNode() is true + * @return the original CorrelationAttributeInstance + * @throws EamDbException + */ + CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException { + if (originalCorrelationInstance == null) { + throw new EamDbException("CorrelationAttributeInstance is null"); + } + return originalCorrelationInstance; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 45bc197283..59df6b7bbc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1772,7 +1772,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all artifact instances from instances table", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java index 1154da273d..32de4e7646 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java @@ -221,7 +221,7 @@ public final class PostgresEamDbSettings { LOGGER.log(Level.SEVERE, "Failed to execute database existance query.", ex); // NON-NLS return false; } finally { - EamDbUtil.closePreparedStatement(ps); + EamDbUtil.closeStatement(ps); EamDbUtil.closeResultSet(rs); EamDbUtil.closeConnection(conn); } 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/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index bae8218148..bb98cc90f6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -26,7 +26,7 @@ LBL_Description=
Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2018.
URL_ON_IMG=http://www.sleuthkit.org/ -URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.7.0/ +URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.8.0/ FILE_FOR_LOCAL_HELP=file:/// INDEX_FOR_LOCAL_HELP=/docs/index.html LBL_Close=Close diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index c9b271a68e..28329f17c5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -258,21 +258,6 @@ public abstract class AbstractAbstractFileNode extends A map.put(EXTENSION.toString(), content.getNameExtension()); } - @Override - public Action[] getActions(boolean context) { - List actionsList = new ArrayList<>(); - - actionsList.addAll(Arrays.asList(super.getActions(true))); - - // Create the "Add/Edit Central Repository Comment" menu item if the enabled. - AbstractFile file = content; - if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { - actionsList.add(AddEditCentralRepoCommentAction.createAddEditCentralRepoCommentAction(file)); - } - - return actionsList.toArray(new Action[actionsList.size()]); - } - /** * Used by subclasses of AbstractAbstractFileNode to add the tags property * to their sheets. 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/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 9e003c5be4..235f00cb1b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -221,11 +221,6 @@ public class BlackboardArtifactNode extends AbstractContentNode 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 diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index d47efa3995..ce96fc0efe 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -46,7 +46,7 @@ import static junit.framework.Assert.assertTrue; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; /** - * + * Functional tests for the Central Repository data model. */ public class CentralRepoDatamodelTest extends TestCase { @@ -115,7 +115,7 @@ public class CentralRepoDatamodelTest extends TestCase { EamDbUtil.setUseCentralRepo(true); EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.SQLITE.name()); EamDbPlatformEnum.saveSelectedPlatform(); - } catch (Exception ex) { + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -2554,7 +2554,7 @@ public class CentralRepoDatamodelTest extends TestCase { // This seems to help in allowing the Autopsy case to be deleted try { Thread.sleep(2000); - } catch (Exception ex) { + } catch (InterruptedException ex) { } } catch (CaseActionException ex) { diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java index 66a68f3a38..39f18e0696 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java @@ -1,5 +1,4 @@ /* - * * Autopsy Forensic Browser * * Copyright 2018 Basis Technology Corp. @@ -84,20 +83,20 @@ class IntraCaseUtils { static final String PDF = "adsf.pdf"; //not a typo - it appears this way in the test image static final String EMPTY = "file.dat"; - static final String SET1 = "commonfiles_image1_v1.vhd"; - static final String SET2 = "commonfiles_image2_v1.vhd"; - static final String SET3 = "commonfiles_image3_v1.vhd"; - static final String SET4 = "commonfiles_image4_v1.vhd"; + static final String SET1 = "CommonFiles_img1_v1.vhd"; + static final String SET2 = "CommonFiles_img2_v1.vhd"; + static final String SET3 = "CommonFiles_img3_v1.vhd"; + static final String SET4 = "CommonFiles_img4_v1.vhd"; private final DataSourceLoader dataSourceLoader; private final String caseName; IntraCaseUtils(NbTestCase nbTestCase, String caseName){ - imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image1_v1.vhd"); - imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image2_v1.vhd"); - imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image3_v1.vhd"); - imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image4_v1.vhd"); + imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), SET1); + imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), SET2); + imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), SET3); + imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), SET4); this.dataSourceLoader = new DataSourceLoader(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 813c09334a..8f562d698b 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -39,11 +39,13 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; +/** + * Functional tests for embedded files. + */ public class EmbeddedFileTest extends NbTestCase { private static final String CASE_NAME = "EmbeddedFileTest"; - private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "EmbeddedIM_img1_v1.vhd"); public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6"; private static final int DEEP_FOLDER_COUNT = 25; private Case openCase; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index fcb2b8a4ca..16b0f2f4a4 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -50,10 +50,13 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; +/** + * Functional tests for ingest file filters. + */ public class IngestFileFiltersTest extends NbTestCase { - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"filter_test1.img"); - private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "local_files_test.zip"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"IngestFilters_img1_v1.img"); + private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "IngestFilters_local1_v1.zip"); private boolean testSucceeded; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index ef639bec5f..dc1f85afcd 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -47,6 +47,9 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Volume; import org.sleuthkit.datamodel.VolumeSystem; +/** + * Functional tests for Encryption Detection. + */ public class EncryptionDetectionTest extends NbTestCase { private static final String BITLOCKER_DETECTION_CASE_NAME = "testBitlockerEncryption"; @@ -54,10 +57,10 @@ public class EncryptionDetectionTest extends NbTestCase { private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; private static final String SQLCIPHER_DETECTION_CASE_NAME = "SQLCipherDetectionTest"; - private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); - private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); - private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "veracrypt_detection_test.vhd"); - private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_sqlcipher_test.vhd"); + private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "BitlockerDetection_img1_v1.vhd"); + private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "PasswordDetection_img1_v1.img"); + private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "VeracryptDetection_img1_v1.vhd"); + private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "SqlCipherDetection_img1_v1.vhd"); private boolean testSucceeded; diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile index 84605ac34f..ee0a85b2dd 100755 --- a/docs/doxygen-user/Doxyfile +++ b/docs/doxygen-user/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.7.0 +HTML_OUTPUT = 4.8.0 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 5dbdbf1479..7facd03b19 100755 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -38,10 +38,10 @@ PROJECT_NAME = "Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears a the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Graphical digital forensics platform for The Sleuth Kit and other tools." @@ -1063,7 +1063,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api-docs/4.7.0/ +HTML_OUTPUT = api-docs/4.8.0/ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/nbproject/project.properties b/nbproject/project.properties index 3cbde84d3e..1ce749b501 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,10 +4,10 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.7.0 +app.version=4.8.0 ### build.type must be one of: DEVELOPMENT, RELEASE -#build.type=RELEASE -build.type=DEVELOPMENT +build.type=RELEASE +#build.type=DEVELOPMENT project.org.netbeans.progress=org-netbeans-api-progress project.org.sleuthkit.autopsy.experimental=Experimental