diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java index a014257daf..17fbb40e1b 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java @@ -64,13 +64,14 @@ public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction() { @Override @@ -90,7 +91,7 @@ public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction "# {1} - content obj id", "ReplaceContentTagAction.replaceTag.alert=Unable to replace tag {0} for {1}."}) @Override - protected void replaceTag(ContentTag oldTag, TagName newTagName) { + protected void replaceTag(ContentTag oldTag, TagName newTagName, String comment) { new SwingWorker() { @Override @@ -84,7 +84,7 @@ public final class ReplaceContentTagAction extends ReplaceTagAction logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS tagsManager.deleteContentTag(oldTag); - tagsManager.addContentTag(oldTag.getContent(), newTagName); + tagsManager.addContentTag(oldTag.getContent(), newTagName, comment); } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java index a5a0843965..03d48f4012 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -77,10 +77,11 @@ abstract class ReplaceTagAction extends AbstractAction implements /** * Method to actually replace the selected tag with the given new tag * - * @param oldTag - * @param newTagName + * @param oldTag - the TagName which is being removed from the item + * @param newTagName - the TagName which is being added to the itme + * @param comment the comment associated with the tag, empty string for no comment */ - abstract protected void replaceTag(T oldTag, TagName newTagName); + abstract protected void replaceTag(T oldTag, TagName newTagName, String comment); /** * Returns elected tags which are to be replaced @@ -140,7 +141,7 @@ abstract class ReplaceTagAction extends AbstractAction implements // Add action to replace the tag tagNameItem.addActionListener((ActionEvent event) -> { selectedTags.forEach((oldtag) -> { - replaceTag(oldtag, entry.getValue()); + replaceTag(oldtag, entry.getValue(), ""); }); }); @@ -177,12 +178,24 @@ abstract class ReplaceTagAction extends AbstractAction implements TagName newTagName = GetTagNameDialog.doDialog(); if (null != newTagName) { selectedTags.forEach((oldtag) -> { - replaceTag(oldtag, newTagName); + replaceTag(oldtag, newTagName, ""); }); } }); add(newTagMenuItem); - + // Create a "Choose Tag and Comment..." menu item. Selecting this item initiates + // a dialog that can be used to create or select a tag name with an + // optional comment and adds a tag with the resulting name. + JMenuItem tagAndCommentItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.tagAndComment")); + tagAndCommentItem.addActionListener((ActionEvent event) -> { + GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(); + if (null != tagNameAndComment) { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, tagNameAndComment.getTagName(), tagNameAndComment.getComment()); + }); + } + }); + add(tagAndCommentItem); } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index ee14581bc7..056ff4e3ec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -36,6 +36,7 @@ import javax.swing.Box.Filler; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.JToggleButton; +import javax.swing.SwingUtilities; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; @@ -67,7 +68,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); } - + //add actionlistner to listen for change } @@ -132,7 +133,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { //Add the button JToggleButton dspButton = createDspButton(dspType); dspButton.addActionListener(cbActionListener); - if ((Case.getCurrentCaseThrows().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())){ + if ((Case.getCurrentCaseThrows().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())) { dspButton.setEnabled(false); //disable the button for local disk DSP when this is a multi user case dspButton.setSelected(false); shouldAddMultiUserWarning = true; @@ -172,6 +173,9 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.weighty = 1; gridBagLayout.setConstraints(vertGlue, constraints); jPanel1.setLayout(gridBagLayout); + SwingUtilities.invokeLater(() -> { + jScrollPane1.getVerticalScrollBar().setValue(0); + }); } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 37a8ad9394..767e86ebcc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -38,7 +38,7 @@ import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil.updateSchemaVersion; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.TskData; @@ -1004,8 +1004,8 @@ abstract class AbstractSqlEamDb implements EamDb { bulkArtifacts.get(type.getDbTableName()).clear(); } - TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); - EnterpriseHealthMonitor.submitTimingMetric(timingMetric); + TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); + HealthMonitor.submitTimingMetric(timingMetric); // Reset state bulkArtifactsCount = 0; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java index 33f790d72e..9c30cf05c5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java @@ -49,7 +49,7 @@ import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.eventlisteners.IngestEventsListener; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; /** @@ -135,9 +135,9 @@ final class IngestModule implements FileIngestModule { */ if (abstractFile.getKnown() != TskData.FileKnown.KNOWN && flagTaggedNotableItems) { try { - TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Notable artifact query"); + TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Notable artifact query"); List caseDisplayNamesList = dbManager.getListCasesHavingArtifactInstancesKnownBad(filesType, md5); - EnterpriseHealthMonitor.submitTimingMetric(timingMetric); + HealthMonitor.submitTimingMetric(timingMetric); if (!caseDisplayNamesList.isEmpty()) { postCorrelatedBadFileToBlackboard(abstractFile, caseDisplayNamesList); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties index 72ddffb748..a2b42155d7 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -7,7 +7,7 @@ CommonFilesPanel.selectedFileCategoriesButton.text=Match on the following file c CommonFilesPanel.selectedFileCategoriesButton.toolTipText=Select from the options below... CommonFilesPanel.pictureVideoCheckbox.text=Pictures and Videos CommonFilesPanel.documentsCheckbox.text=Documents -CommonFilesPanel.commonFilesSearchLabel.text=Find duplicate files in the current case. +CommonFilesPanel.commonFilesSearchLabel.text=Find files in multiple data sources in the current case. CommonFilesPanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results... CommonFilesPanel.allFileCategoriesRadioButton.text=Match on all file types CommonFilesPanel.text=Indicate which data sources to consider while searching for duplicates: diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java index e816391870..356056a1bc 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java @@ -104,12 +104,15 @@ public final class CommonFilesPanel extends javax.swing.JPanel { CommonFilesPanel.this.selectDataSourceComboBox.setModel(CommonFilesPanel.this.dataSourcesList); boolean multipleDataSources = this.caseHasMultipleSources(); - CommonFilesPanel.this.allDataSourcesRadioButton.setEnabled(multipleDataSources); - CommonFilesPanel.this.allDataSourcesRadioButton.setSelected(multipleDataSources); - + + CommonFilesPanel.this.allDataSourcesRadioButton.setEnabled(true); + CommonFilesPanel.this.allDataSourcesRadioButton.setSelected(true); + if (!multipleDataSources) { - CommonFilesPanel.this.withinDataSourceRadioButton.setSelected(true); - withinDataSourceSelected(true); + CommonFilesPanel.this.withinDataSourceRadioButton.setEnabled(false); + CommonFilesPanel.this.withinDataSourceRadioButton.setSelected(false); + withinDataSourceSelected(false); + CommonFilesPanel.this.selectDataSourceComboBox.setEnabled(false); } CommonFilesPanel.this.searchButton.setEnabled(true); @@ -120,7 +123,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel { } private boolean caseHasMultipleSources() { - return CommonFilesPanel.this.dataSourceMap.size() >= 2; + return CommonFilesPanel.this.dataSourceMap.size() >= 3; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java index e470fd16bc..1727c9322b 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java @@ -19,11 +19,14 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.awt.event.ActionEvent; +import java.util.logging.Level; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.Installer; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Encapsulates a menu action which triggers the common files search dialog. @@ -32,15 +35,22 @@ final public class CommonFilesSearchAction extends CallableSystemAction { private static CommonFilesSearchAction instance = null; private static final long serialVersionUID = 1L; - + private static final Logger logger = Logger.getLogger(CommonFilesSearchAction.class.getName()); + CommonFilesSearchAction() { super(); this.setEnabled(false); } - + @Override public boolean isEnabled(){ - return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited(); + boolean shouldBeEnabled = false; + try { + shouldBeEnabled = Case.isCaseOpen() && Case.getCurrentCase().getDataSources().size() > 1; + } catch(TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting data sources for action enabled check", ex); + } + return super.isEnabled() && shouldBeEnabled; } public static synchronized CommonFilesSearchAction getDefault() { diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java index f699900e8e..71ffe1a21e 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java @@ -38,24 +38,27 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { private static final Map COLUMN_WIDTHS; + private static final long serialVersionUID = 1L; static { Map map = new HashMap<>(); - map.put(Bundle.CommonFilesSearchResultsViewerTable_matchColLbl(), 235); + map.put(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), 260); + map.put(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), 65); map.put(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), 300); map.put(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), 200); map.put(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), 100); - map.put(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), 150); + map.put(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), 130); map.put(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1(), 300); COLUMN_WIDTHS = Collections.unmodifiableMap(map); } @NbBundle.Messages({ - "CommonFilesSearchResultsViewerTable.matchColLbl=Match", + "CommonFilesSearchResultsViewerTable.filesColLbl=Files", + "CommonFilesSearchResultsViewerTable.instancesColLbl=Instances", "CommonFilesSearchResultsViewerTable.pathColLbl=Parent Path", "CommonFilesSearchResultsViewerTable.hashsetHitsColLbl=Hash Set Hits", - "CommonFilesSearchResultsViewerTable.dataSourceColLbl=Data Source", + "CommonFilesSearchResultsViewerTable.dataSourceColLbl=Data Source(s)", "CommonFilesSearchResultsViewerTable.mimeTypeColLbl=MIME Type", "CommonFilesSearchResultsViewerTable.tagsColLbl1=Tags" }) @@ -68,7 +71,7 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { TableColumn column = columnsEnumerator.nextElement(); - final Object headerValue = column.getHeaderValue(); + final String headerValue = column.getHeaderValue().toString(); final Integer get = COLUMN_WIDTHS.get(headerValue); column.setPreferredWidth(get); diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java index e25f4a8637..07c87035e1 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.commonfilesearch; -import java.util.LinkedHashMap; -import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; @@ -30,7 +28,7 @@ import org.sleuthkit.datamodel.AbstractFile; /** * Used by the Common Files search feature to encapsulate instances of a given - * MD5s matched in the search. These nodes will be children of Md5Nodes. + MD5s matched in the search. These nodes will be children of Md5Nodes. */ public class FileInstanceNode extends FileNode { @@ -46,6 +44,8 @@ public class FileInstanceNode extends FileNode { public FileInstanceNode(AbstractFile fsContent, String dataSource) { super(fsContent); this.dataSource = dataSource; + + this.setDisplayName(fsContent.getName()); } @Override @@ -73,64 +73,16 @@ public class FileInstanceNode extends FileNode { sheet.put(sheetSet); } - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); - final String NO_DESCR = Bundle.FileInstanceNode_createSheet_noDescription(); - for (CommonFilePropertyType propType : CommonFilePropertyType.values()) { - final String propString = propType.toString(); - final Object property = map.get(propString); - final NodeProperty nodeProperty = new NodeProperty<>(propString, propString, NO_DESCR, property); - sheetSet.put(nodeProperty); - } + + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName())); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, this.getContent().getParentPath())); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, getHashSetHitsForFile(this.getContent()))); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSource())); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, StringUtils.defaultString(this.getContent().getMIMEType()))); this.addTagProperty(sheetSet); return sheet; } - - /** - * Fill map with AbstractFile properties - * - * @param map map with preserved ordering, where property names/values are - * put - * @param node The item to get properties for. - */ - static private void fillPropertyMap(Map map, FileInstanceNode node) { - - map.put(CommonFilePropertyType.ParentPath.toString(), node.getContent().getParentPath()); - map.put(CommonFilePropertyType.HashsetHits.toString(), getHashSetHitsForFile(node.getContent())); - map.put(CommonFilePropertyType.DataSource.toString(), node.getDataSource()); - map.put(CommonFilePropertyType.MimeType.toString(), StringUtils.defaultString(node.getContent().getMIMEType())); - } - - /** - * Encapsulates the columns to be displayed for reach row represented by an - * instance of this object. - */ - @NbBundle.Messages({ - "CommonFilePropertyType.pathColLbl=Parent Path", - "CommonFilePropertyType.hashsetHitsColLbl=Hash Set Hits", - "CommonFilePropertyType.dataSourceColLbl=Data Source", - "CommonFilePropertyType.caseColLbl=Case", - "CommonFilePropertyType.mimeTypeColLbl=MIME Type" - }) - public enum CommonFilePropertyType { - - ParentPath(Bundle.CommonFilePropertyType_pathColLbl()), - HashsetHits(Bundle.CommonFilePropertyType_hashsetHitsColLbl()), - DataSource(Bundle.CommonFilePropertyType_dataSourceColLbl()), - MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl()); - - final private String displayString; - - private CommonFilePropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return displayString; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index de22cd6590..987e6faa08 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -22,7 +22,6 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.openide.nodes.ChildFactory; @@ -50,7 +49,7 @@ final public class InstanceCountNode extends DisplayableItemNode { * @param md5Metadata */ @NbBundle.Messages({ - "InstanceCountNode.displayName=Matches with %s instances" + "InstanceCountNode.displayName=Files with %s instances (%s)" }) public InstanceCountNode(int instanceCount, List md5Metadata) { super(Children.create(new Md5NodeFactory(md5Metadata), true)); @@ -58,7 +57,8 @@ final public class InstanceCountNode extends DisplayableItemNode { this.instanceCount = instanceCount; this.metadataList = md5Metadata; - this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount))); + this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), md5Metadata.size())); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } /** @@ -101,51 +101,13 @@ final public class InstanceCountNode extends DisplayableItemNode { sheetSet = Sheet.createPropertiesSet(); sheet.put(sheetSet); } - - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); - + final String NO_DESCR = Bundle.InstanceCountNode_createSheet_noDescription(); - for (InstanceCountNode.InstanceCountNodePropertyType propType : InstanceCountNode.InstanceCountNodePropertyType.values()) { - final String propString = propType.toString(); - sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); - } - + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, this.getInstanceCount())); return sheet; } - /** - * Fill map with AbstractFile properties - * - * @param map map with preserved ordering, where property names/values are - * put - * @param node The item to get properties for. - */ - static private void fillPropertyMap(Map map, InstanceCountNode node) { - map.put(InstanceCountNodePropertyType.Match.toString(), node.getInstanceCount()); - } - - /** - * Fields which will appear in the tree table. - */ - @NbBundle.Messages({ - "InstanceCountNodePropertyType.matchCountColLbl1=Match" - }) - public enum InstanceCountNodePropertyType{ - - Match(Bundle.InstanceCountNodePropertyType_matchCountColLbl1()); - - final private String displayString; - - private InstanceCountNodePropertyType(String displayName){ - this.displayString = displayName; - } - - @Override - public String toString(){ - return this.displayString; - } - } /** * ChildFactory which builds CommonFileParentNodes from the diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java index 0705636ef6..0b10d73e30 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -19,9 +19,7 @@ */ package org.sleuthkit.autopsy.commonfilesearch; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -52,6 +50,9 @@ public class Md5Node extends DisplayableItemNode { private final int commonFileCount; private final String dataSources; + @NbBundle.Messages({ + "Md5Node.Md5Node.format=MD5: %s" + }) /** * Create a Match node whose children will all have this object in common. * @param data the common feature, and the children @@ -64,7 +65,8 @@ public class Md5Node extends DisplayableItemNode { this.dataSources = String.join(", ", data.getDataSources()); this.md5Hash = data.getMd5(); - this.setDisplayName(this.md5Hash); + this.setDisplayName(String.format(Bundle.Md5Node_Md5Node_format(), this.md5Hash)); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } /** @@ -101,29 +103,15 @@ public class Md5Node extends DisplayableItemNode { sheet.put(sheetSet); } - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); - final String NO_DESCR = Bundle.Md5Node_createSheet_noDescription(); - for (Md5Node.CommonFileParentPropertyType propType : Md5Node.CommonFileParentPropertyType.values()) { - final String propString = propType.toString(); - sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); - } + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSources())); return sheet; } - /** - * Fill map with AbstractFile properties - * - * @param map map with preserved ordering, where property names/values are - * put - * @param node The item to get properties for. - */ - static private void fillPropertyMap(Map map, Md5Node node) { - //map.put(CommonFileParentPropertyType.Case.toString(), ""); - map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); - } @Override public T accept(DisplayableItemNodeVisitor visitor) { @@ -172,24 +160,4 @@ public class Md5Node extends DisplayableItemNode { } } - @NbBundle.Messages({ - "CommonFileParentPropertyType.fileColLbl=File", - "CommonFileParentPropertyType.instanceColLbl=Instance Count", - "CommonFileParentPropertyType.caseColLbl=Case", - "CommonFileParentPropertyType.dataSourceColLbl=Data Source"}) - public enum CommonFileParentPropertyType { - - DataSource(Bundle.CommonFileParentPropertyType_dataSourceColLbl()); - - final private String displayString; - - private CommonFileParentPropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return this.displayString; - } - } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java index 53491b407e..24f54fa7ac 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java @@ -28,7 +28,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; * but the initial method was needed only be viewers in * corecomponents and therefore can stay out of public API. */ -class DataContentViewerUtility { +public class DataContentViewerUtility { /** * Returns the first non-Blackboard Artifact from a Node. * Needed for (at least) Hex and Strings that want to view @@ -39,7 +39,7 @@ class DataContentViewerUtility { * @param node Node passed into content viewer * @return highest priority content or null if there is no content */ - static Content getDefaultContent(Node node) { + public static Content getDefaultContent(Node node) { Content bbContentSeen = null; for (Content content : (node).getLookup().lookupAll(Content.class)) { if (content instanceof BlackboardArtifact) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 374c96069b..f45a333ec1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -54,7 +54,6 @@ DataModelActionsFactory.srcFileInDir.text=View Source File in Directory DataModelActionsFactory.fileInDir.text=View File in Directory DataModelActionsFactory.viewNewWin.text=View in New Window DataModelActionsFactory.openExtViewer.text=Open in External Viewer -DataModelActionsFactory.srfFileSameMD5.text=Search for files with the same MD5 hash DataSourcesNode.name=Data Sources DataSourcesNode.group_by_datasource.name=Data Source Files DataSourcesNode.createSheet.name.name=Name diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties index 979c4d50cb..e2c331b390 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties @@ -55,7 +55,6 @@ DataModelActionsFactory.srcFileInDir.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u DataModelActionsFactory.fileInDir.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u5185\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u8868\u793a DataModelActionsFactory.viewNewWin.text=\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30a6\u306b\u8868\u793a DataModelActionsFactory.openExtViewer.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u306b\u8868\u793a -DataModelActionsFactory.srfFileSameMD5.text=\u540c\u3058MD5\u30cf\u30c3\u30b7\u30e5\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22 DataSourcesNode.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 DataSourcesNode.group_by_datasource.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb DataSourcesNode.createSheet.name.name=\u540d\u524d diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index 6b4b1c8faf..88e90f054d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -38,7 +38,6 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; @@ -76,8 +75,6 @@ public class DataModelActionsFactory { .getMessage(DataModelActionsFactory.class, "DataModelActionsFactory.viewNewWin.text"); public static final String OPEN_IN_EXTERNAL_VIEWER = NbBundle .getMessage(DataModelActionsFactory.class, "DataModelActionsFactory.openExtViewer.text"); - public static final String SEARCH_FOR_FILES_SAME_MD5 = NbBundle - .getMessage(DataModelActionsFactory.class, "DataModelActionsFactory.srfFileSameMD5.text"); public static List getActions(File file, boolean isArtifactSource) { List actionsList = new ArrayList<>(); @@ -88,7 +85,6 @@ public class DataModelActionsFactory { actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, fileNode)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, fileNode)); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -367,7 +363,6 @@ public class DataModelActionsFactory { actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, tagNode)); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -404,7 +399,6 @@ public class DataModelActionsFactory { actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, tagNode)); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 4bb8ced8bd..13e7ef34a5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -34,7 +34,6 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; @@ -166,7 +165,6 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(null); // Creates an item separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction(Bundle.FileNode_getActions_searchFilesSameMD5_text(), this)); actionsList.add(null); // Creates an item separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 133f90c291..d5064312b3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -36,7 +36,6 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; @@ -107,8 +106,6 @@ public class LocalFileNode extends AbstractAbstractFileNode { actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction( - NbBundle.getMessage(this.getClass(), "LocalFileNode.getActions.searchFilesSameMd5.text"), this)); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 2b58040088..fd13e30d62 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -401,10 +401,8 @@ public class DataResultFilterNode extends FilterNode { } Content c = ban.getLookup().lookup(File.class); Node n = null; - boolean md5Action = false; if (c != null) { n = new FileNode((AbstractFile) c); - md5Action = true; } else if ((c = ban.getLookup().lookup(Directory.class)) != null) { n = new DirectoryNode((Directory) c); } else if ((c = ban.getLookup().lookup(VirtualDirectory.class)) != null) { @@ -438,10 +436,6 @@ public class DataResultFilterNode extends FilterNode { NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.openInExtViewer.text"), n)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - if (md5Action) { - actionsList.add(new HashSearchAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.searchFilesSameMd5.text"), n)); - } actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); actionsList.add(AddBlackboardArtifactTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index 48dfad0bb6..732e34c20b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -18,11 +18,10 @@ + - - - - + + @@ -43,14 +42,14 @@ - - - + + + - + @@ -72,7 +71,7 @@ - + @@ -80,7 +79,7 @@ - + @@ -92,10 +91,10 @@ - + - + @@ -105,7 +104,7 @@ - + @@ -113,7 +112,7 @@ - + @@ -125,10 +124,10 @@ - + - + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 7e1ba36b67..7db201f00b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -184,32 +184,32 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat treeView.setBorder(null); - backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N + backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.backButton.text")); // NOI18N backButton.setBorderPainted(false); backButton.setContentAreaFilled(false); - backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N + backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png"))); // NOI18N backButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); backButton.setMaximumSize(new java.awt.Dimension(55, 100)); backButton.setMinimumSize(new java.awt.Dimension(5, 5)); - backButton.setPreferredSize(new java.awt.Dimension(23, 23)); - backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N + backButton.setPreferredSize(new java.awt.Dimension(32, 32)); + backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png"))); // NOI18N backButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { backButtonActionPerformed(evt); } }); - forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N + forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(forwardButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.forwardButton.text")); // NOI18N forwardButton.setBorderPainted(false); forwardButton.setContentAreaFilled(false); - forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N + forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png"))); // NOI18N forwardButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); forwardButton.setMaximumSize(new java.awt.Dimension(55, 100)); forwardButton.setMinimumSize(new java.awt.Dimension(5, 5)); - forwardButton.setPreferredSize(new java.awt.Dimension(23, 23)); - forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N + forwardButton.setPreferredSize(new java.awt.Dimension(32, 32)); + forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png"))); // NOI18N forwardButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { forwardButtonActionPerformed(evt); @@ -231,11 +231,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(treeView) .addGroup(layout.createSequentialGroup() - .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, 65, Short.MAX_VALUE) + .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 51, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(showRejectedCheckBox) .addComponent(groupByDatasourceCheckBox)) @@ -252,11 +251,11 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat .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)))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(forwardButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 843, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents @@ -800,10 +799,23 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } /** - * Rebuilds the directory tree + * Rebuilds the autopsy tree. + * + * Does nothing if there is no open case. */ private void rebuildTree() { + // if no open case or has no data then there is no tree to rebuild + Case currentCase; + try { + currentCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + return; + } + if (null == currentCase || currentCase.hasData() == false) { + return; + } + // refresh all children of the root. autopsyTreeChildrenFactory.refreshChildren(); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/HashSearchAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/HashSearchAction.java deleted file mode 100644 index b7cd2eab12..0000000000 --- a/Core/src/org/sleuthkit/autopsy/directorytree/HashSearchAction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.directorytree; - -import java.awt.event.ActionEvent; -import javax.annotation.concurrent.Immutable; -import javax.swing.AbstractAction; -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.modules.hashdatabase.HashDbSearchAction; - -/** - * Action to lookup the interface and call the real action in HashDatabase. The - * real action, HashDbSearchAction, implements HashSearchProvider, and should be - * the only instance of it. - * - * //TODO: HashDBSearchAction needs a public constructor and a service - * registration annotation for the lookup technique to work - */ -@Immutable -public class HashSearchAction extends AbstractAction { - - private final Node contentNode; - - public HashSearchAction(String title, Node contentNode) { - super(title); - this.contentNode = contentNode; - } - - @Override - public void actionPerformed(ActionEvent e) { - //HashSearchProvider searcher = Lookup.getDefault().lookup(HashSearchProvider.class); - //TODO: HashDBSearchAction needs a public constructor and a service registration annotation for the above technique to work - HashDbSearchAction searcher = HashDbSearchAction.getDefault(); - searcher.search(contentNode); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png new file mode 100644 index 0000000000..53e6bb25d4 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png new file mode 100644 index 0000000000..f012692c6f Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png new file mode 100644 index 0000000000..56810faa2f Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png new file mode 100644 index 0000000000..b1d97b6897 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png new file mode 100644 index 0000000000..04b4471bb9 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png new file mode 100644 index 0000000000..4566b3dbba Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java similarity index 78% rename from Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java rename to Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java index 57a5ae452d..251fe35572 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java @@ -52,44 +52,42 @@ import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; - /** * Class for recording data on the health of the system. - * - * For timing data: - * Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object - * Modules will call submitTimingMetric() with the obtained TimingMetric object to log it + * + * For timing data: Modules will call getTimingMetric() before the code to be + * timed to get a TimingMetric object Modules will call submitTimingMetric() + * with the obtained TimingMetric object to log it */ -public final class EnterpriseHealthMonitor implements PropertyChangeListener { - - private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName()); - private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; +public final class HealthMonitor implements PropertyChangeListener { + + private final static Logger logger = Logger.getLogger(HealthMonitor.class.getName()); + private final static String DATABASE_NAME = "HealthMonitor"; private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes - public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION - = new CaseDbSchemaVersionNumber(1, 1); - - private static final AtomicBoolean isEnabled = new AtomicBoolean(false); - private static EnterpriseHealthMonitor instance; - + private final static CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 1); + + private final static AtomicBoolean isEnabled = new AtomicBoolean(false); + private static HealthMonitor instance; + private ScheduledThreadPoolExecutor healthMonitorOutputTimer; private final Map timingInfoMap; private final List userInfoList; - private static final int CONN_POOL_SIZE = 10; + private final static int CONN_POOL_SIZE = 10; private BasicDataSource connectionPool = null; private CaseDbConnectionInfo connectionSettingsInUse = null; private String hostName; - - private EnterpriseHealthMonitor() throws HealthMonitorException { - + + private HealthMonitor() throws HealthMonitorException { + // Create the map to collect timing metrics. The map will exist regardless // of whether the monitor is enabled. timingInfoMap = new HashMap<>(); - + // Create the list to hold user information. The list will exist regardless // of whether the monitor is enabled. userInfoList = new ArrayList<>(); - + // Get the host name try { hostName = java.net.InetAddress.getLocalHost().getHostName(); @@ -98,84 +96,87 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { hostName = UUID.randomUUID().toString(); logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex); } - + // Read from the database to determine if the module is enabled updateFromGlobalEnabledStatus(); - + // Start the timer for database checks and writes startTimer(); } - + /** - * Get the instance of the EnterpriseHealthMonitor + * Get the instance of the HealthMonitor + * * @return the instance - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException { + synchronized static HealthMonitor getInstance() throws HealthMonitorException { if (instance == null) { - instance = new EnterpriseHealthMonitor(); + instance = new HealthMonitor(); Case.addPropertyChangeListener(instance); } return instance; } - + /** - * Activate the health monitor. - * Creates/initialized the database (if needed), clears any existing metrics - * out of the maps, and sets up the timer for writing to the database. - * @throws HealthMonitorException + * Activate the health monitor. Creates/initialized the database (if + * needed), clears any existing metrics out of the maps, and sets up the + * timer for writing to the database. + * + * @throws HealthMonitorException */ private synchronized void activateMonitorLocally() throws HealthMonitorException { - + logger.log(Level.INFO, "Activating Servies Health Monitor"); - + // Make sure there are no left over connections to an old database shutdownConnections(); - + if (!UserPreferences.getIsMultiUserModeEnabled()) { throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor"); } - + // Set up database (if needed) try (CoordinationService.Lock lock = getExclusiveDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + // Check if the database exists - if (! databaseExists()) { - + if (!databaseExists()) { + // If not, create a new one createDatabase(); } - - if( ! databaseIsInitialized()) { + + if (!databaseIsInitialized()) { initializeDatabaseSchema(); } - - if( ! CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) { + + if (!CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) { upgradeDatabaseSchema(); } - + } catch (CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error releasing database lock", ex); } - + // Clear out any old data timingInfoMap.clear(); userInfoList.clear(); } - + /** * Upgrade an older database */ private void upgradeDatabaseSchema() throws HealthMonitorException { - + logger.log(Level.INFO, "Upgrading Health Monitor database"); CaseDbSchemaVersionNumber currentSchema = getVersion(); - + Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } @@ -184,23 +185,23 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { // Upgrade from 1.0 to 1.1 // Changes: user_data table added - if(currentSchema.compareTo(new CaseDbSchemaVersionNumber(1,1)) < 0) { - + if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) { + // Add the user_data table - statement.execute("CREATE TABLE IF NOT EXISTS user_data ("+ - "id SERIAL PRIMARY KEY," + - "host text NOT NULL," + - "timestamp bigint NOT NULL," + - "event_type int NOT NULL," + - "is_examiner boolean NOT NULL," + - "case_name text NOT NULL" + - ")"); + statement.execute("CREATE TABLE IF NOT EXISTS user_data (" + + "id SERIAL PRIMARY KEY," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "event_type int NOT NULL," + + "is_examiner boolean NOT NULL," + + "case_name text NOT NULL" + + ")"); } - + // Update the schema version statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "' WHERE name='SCHEMA_VERSION'"); statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "' WHERE name='SCHEMA_MINOR_VERSION'"); - + conn.commit(); logger.log(Level.INFO, "Health Monitor database upgraded to version {0}", CURRENT_DB_SCHEMA_VERSION.toString()); } catch (SQLException ex) { @@ -218,82 +219,89 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** - * Deactivate the health monitor. - * This should only be used when disabling the monitor, not when Autopsy is closing. - * Clears out any metrics that haven't been written, stops the database write timer, - * and shuts down the connection pool. - * @throws HealthMonitorException + * Deactivate the health monitor. This should only be used when disabling + * the monitor, not when Autopsy is closing. Clears out any metrics that + * haven't been written, stops the database write timer, and shuts down the + * connection pool. + * + * @throws HealthMonitorException */ private synchronized void deactivateMonitorLocally() throws HealthMonitorException { - + logger.log(Level.INFO, "Deactivating Servies Health Monitor"); - + // Clear out the collected data timingInfoMap.clear(); - + // Shut down the connection pool shutdownConnections(); } - + /** - * Start the ScheduledThreadPoolExecutor that will handle the database writes. + * Start the ScheduledThreadPoolExecutor that will handle the database + * writes. */ private synchronized void startTimer() { // Make sure the previous executor (if it exists) has been stopped stopTimer(); - + healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); healthMonitorOutputTimer.scheduleWithFixedDelay(new PeriodicHealthMonitorTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); } - + /** * Stop the ScheduledThreadPoolExecutor to prevent further database writes. */ private synchronized void stopTimer() { - if(healthMonitorOutputTimer != null) { + if (healthMonitorOutputTimer != null) { ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer); } } /** - * Called from the installer to set up the Health Monitor instance at startup. - * @throws HealthMonitorException + * Called from the installer to set up the Health Monitor instance at + * startup. + * + * @throws HealthMonitorException */ static synchronized void startUpIfEnabled() throws HealthMonitorException { getInstance().addUserEvent(UserEvent.LOG_ON); } - + /** - * Called when the application is closing. - * Create a log off event and write all existing metrics to the database - * @throws HealthMonitorException + * Called when the application is closing. Create a log off event and write + * all existing metrics to the database + * + * @throws HealthMonitorException */ static synchronized void shutdown() throws HealthMonitorException { getInstance().addUserEvent(UserEvent.LOG_OFF); recordMetrics(); } - + /** * Enabled/disable the health monitor. + * * @param enabled true to enable the monitor, false to disable it - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ static synchronized void setEnabled(boolean enabled) throws HealthMonitorException { - if(enabled == isEnabled.get()) { + if (enabled == isEnabled.get()) { // The setting has not changed, so do nothing return; } - - if(enabled) { + + if (enabled) { getInstance().activateMonitorLocally(); - + // If activateMonitor fails, we won't update this getInstance().setGlobalEnabledStatusInDB(true); isEnabled.set(true); } else { - if(isEnabled.get()) { + if (isEnabled.get()) { // If we were enabled before, set the global state to disabled getInstance().setGlobalEnabledStatusInDB(false); } @@ -301,33 +309,34 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { getInstance().deactivateMonitorLocally(); } } - + /** * Get a metric that will measure the time to execute a section of code. - * Call this before the section of code to be timed and then - * submit it afterward using submitTimingMetric(). - * This method is safe to call regardless of whether the Enterprise Health - * Monitor is enabled. + * Call this before the section of code to be timed and then submit it + * afterward using submitTimingMetric(). This method is safe to call + * regardless of whether the health monitor is enabled. + * * @param name A short but descriptive name describing the code being timed. * This name will appear in the UI. + * * @return The TimingMetric object */ public static TimingMetric getTimingMetric(String name) { - if(isEnabled.get()) { + if (isEnabled.get()) { return new TimingMetric(name); } return null; } - + /** * Submit the metric that was previously obtained through getTimingMetric(). - * Call this immediately after the section of code being timed. - * This method is safe to call regardless of whether the Enterprise Health - * Monitor is enabled. + * Call this immediately after the section of code being timed. This method + * is safe to call regardless of whether the health monitor is enabled. + * * @param metric The TimingMetric object obtained from getTimingMetric() */ public static void submitTimingMetric(TimingMetric metric) { - if(isEnabled.get() && (metric != null)) { + if (isEnabled.get() && (metric != null)) { metric.stopTiming(); try { getInstance().addTimingMetric(metric); @@ -337,18 +346,20 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Submit the metric that was previously obtained through getTimingMetric(), - * incorporating a count that the time should be divided by. - * Call this immediately after the section of code being timed. - * This method is safe to call regardless of whether the Enterprise Health - * Monitor is enabled. - * @param metric The TimingMetric object obtained from getTimingMetric() - * @param normalization The number to divide the time by (a zero here will be treated as a one) + * incorporating a count that the time should be divided by. Call this + * immediately after the section of code being timed. This method is safe to + * call regardless of whether the health monitor is enabled. + * + * @param metric The TimingMetric object obtained from + * getTimingMetric() + * @param normalization The number to divide the time by (a zero here will + * be treated as a one) */ public static void submitNormalizedTimingMetric(TimingMetric metric, long normalization) { - if(isEnabled.get() && (metric != null)) { + if (isEnabled.get() && (metric != null)) { metric.stopTiming(); try { metric.normalize(normalization); @@ -359,125 +370,130 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Add the timing metric data to the map. - * @param metric The metric to add. stopTiming() should already have been called. + * + * @param metric The metric to add. stopTiming() should already have been + * called. */ private void addTimingMetric(TimingMetric metric) throws HealthMonitorException { - + // Do as little as possible within the synchronized block to minimize // blocking with multiple threads. - synchronized(this) { + synchronized (this) { // There's a small check-then-act situation here where isEnabled // may have changed before reaching this code. This is fine - // the map still exists and any extra data added after the monitor // is disabled will be deleted if the monitor is re-enabled. This // seems preferable to doing another check on isEnabled within // the synchronized block. - if(timingInfoMap.containsKey(metric.getName())) { + if (timingInfoMap.containsKey(metric.getName())) { timingInfoMap.get(metric.getName()).addMetric(metric); } else { timingInfoMap.put(metric.getName(), new TimingInfo(metric)); } } } - + /** * Add a user event to the list. - * @param eventType + * + * @param eventType */ private void addUserEvent(UserEvent eventType) { UserData userInfo = new UserData(eventType); - synchronized(this) { + synchronized (this) { userInfoList.add(userInfo); } } - + /** - * Time a database query. - * Database queries are hard to test in normal processing because the time - * is so dependent on the size of the tables being queried. We use getImages here - * because every table it queries is the same size (one entry for each image) so - * we a) know the size of the tables and b) can use that table size to do - * normalization. - * @throws HealthMonitorException + * Time a database query. Database queries are hard to test in normal + * processing because the time is so dependent on the size of the tables + * being queried. We use getImages here because every table it queries is + * the same size (one entry for each image) so we a) know the size of the + * tables and b) can use that table size to do normalization. + * + * @throws HealthMonitorException */ private void performDatabaseQuery() throws HealthMonitorException { try { SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Database: getImages query"); + TimingMetric metric = HealthMonitor.getTimingMetric("Database: getImages query"); List images = skCase.getImages(); - + // Through testing we found that this normalization gives us fairly // consistent results for different numbers of data sources. long normalization = images.size(); if (images.isEmpty()) { normalization += 2; - } else if (images.size() == 1){ + } else if (images.size() == 1) { normalization += 3; } else if (images.size() < 10) { normalization += 5; } else { normalization += 7; } - - EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, normalization); + + HealthMonitor.submitNormalizedTimingMetric(metric, normalization); } catch (NoCurrentCaseException ex) { // If there's no case open, we just can't do the metrics. } catch (TskCoreException ex) { throw new HealthMonitorException("Error running getImages()", ex); } } - + /** * Collect metrics at a scheduled time. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void gatherTimerBasedMetrics() throws HealthMonitorException { performDatabaseQuery(); } - + /** * Write the collected metrics to the database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void writeCurrentStateToDatabase() throws HealthMonitorException { - + Map timingMapCopy; List userDataCopy; - + // Do as little as possible within the synchronized block since it will // block threads attempting to record metrics. - synchronized(this) { - if(! isEnabled.get()) { + synchronized (this) { + if (!isEnabled.get()) { return; } - + // Make a shallow copy of the timing map. The map should be small - one entry // per metric name. timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); - + userDataCopy = new ArrayList<>(userInfoList); userInfoList.clear(); } - + // Check if there's anything to report - if(timingMapCopy.keySet().isEmpty() && userDataCopy.isEmpty()) { + if (timingMapCopy.keySet().isEmpty() && userDataCopy.isEmpty()) { return; } - + logger.log(Level.INFO, "Writing health monitor metrics to database"); - + // Write to the database try (CoordinationService.Lock lock = getSharedDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } @@ -485,9 +501,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; String addUserInfoSql = "INSERT INTO user_data (host, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?)"; try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql); - PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) { + PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) { - for(String name:timingMapCopy.keySet()) { + for (String name : timingMapCopy.keySet()) { TimingInfo info = timingMapCopy.get(name); timingStatement.setString(1, name); @@ -500,8 +516,8 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { timingStatement.execute(); } - - for(UserData userInfo:userDataCopy) { + + for (UserData userInfo : userDataCopy) { userStatement.setString(1, hostName); userStatement.setLong(2, userInfo.getTimestamp()); userStatement.setInt(3, userInfo.getEventType().getEventValue()); @@ -523,14 +539,16 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error releasing database lock", ex); } } - + /** - * Check whether the health monitor database exists. - * Does not check the schema. + * Check whether the health monitor database exists. Does not check the + * schema. + * * @return true if the database exists, false otherwise - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - private boolean databaseExists() throws HealthMonitorException { + private boolean databaseExists() throws HealthMonitorException { try { // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); @@ -538,13 +556,13 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { ResultSet rs = null; try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS Statement statement = connection.createStatement();) { - String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; + String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; rs = statement.executeQuery(createCommand); - if(rs.next()) { + if (rs.next()) { return true; } } finally { - if(rs != null) { + if (rs != null) { rs.close(); } } @@ -553,10 +571,11 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } return false; } - + /** * Create a new health monitor database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void createDatabase() throws HealthMonitorException { try { @@ -576,13 +595,14 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { /** * Setup a connection pool for db connections. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void setupConnectionPool() throws HealthMonitorException { try { CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); connectionSettingsInUse = db; - + connectionPool = new BasicDataSource(); connectionPool.setDriverClassName("org.postgresql.Driver"); @@ -606,15 +626,16 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error loading database configuration", ex); } } - + /** * Shut down the connection pool - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void shutdownConnections() throws HealthMonitorException { try { - synchronized(this) { - if(connectionPool != null){ + synchronized (this) { + if (connectionPool != null) { connectionPool.close(); connectionPool = null; // force it to be re-created on next connect() } @@ -623,12 +644,13 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Failed to close existing database connections.", ex); // NON-NLS } } - + /** - * Get a database connection. - * Sets up the connection pool if needed. + * Get a database connection. Sets up the connection pool if needed. + * * @return The Connection object - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private Connection connect() throws HealthMonitorException { synchronized (this) { @@ -643,20 +665,22 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error getting connection from connection pool.", ex); // NON-NLS } } - + /** - * Test whether the database schema has been initialized. - * We do this by looking for the version number. + * Test whether the database schema has been initialized. We do this by + * looking for the version number. + * * @return True if it has been initialized, false otherwise. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private boolean databaseIsInitialized() throws HealthMonitorException { Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); - } + } ResultSet resultSet = null; - + try (Statement statement = conn.createStatement()) { resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); return resultSet.next(); @@ -664,7 +688,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { // This likely just means that the db_info table does not exist return false; } finally { - if(resultSet != null) { + if (resultSet != null) { try { resultSet.close(); } catch (SQLException ex) { @@ -676,67 +700,69 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } catch (SQLException ex) { logger.log(Level.SEVERE, "Error closing Connection.", ex); } - } + } } - + /** - * Return whether the health monitor is locally enabled. - * This does not query the database. + * Return whether the health monitor is locally enabled. This does not query + * the database. + * * @return true if it is enabled, false otherwise */ static boolean monitorIsEnabled() { return isEnabled.get(); } - + /** - * Check whether monitoring should be enabled from the monitor database - * and enable/disable as needed. - * @throws HealthMonitorException + * Check whether monitoring should be enabled from the monitor database and + * enable/disable as needed. + * + * @throws HealthMonitorException */ synchronized void updateFromGlobalEnabledStatus() throws HealthMonitorException { - + boolean previouslyEnabled = monitorIsEnabled(); - + // We can't even check the database if multi user settings aren't enabled. if (!UserPreferences.getIsMultiUserModeEnabled()) { isEnabled.set(false); - if(previouslyEnabled) { + if (previouslyEnabled) { deactivateMonitorLocally(); } return; } - + // If the health monitor database doesn't exist or if it is not initialized, // then monitoring isn't enabled - if ((! databaseExists()) || (! databaseIsInitialized())) { + if ((!databaseExists()) || (!databaseIsInitialized())) { isEnabled.set(false); - - if(previouslyEnabled) { + + if (previouslyEnabled) { deactivateMonitorLocally(); } return; } - + // If we're currently enabled, check whether the multiuser settings have changed. // If they have, force a reset on the connection pool. - if(previouslyEnabled && (connectionSettingsInUse != null)) { + if (previouslyEnabled && (connectionSettingsInUse != null)) { try { CaseDbConnectionInfo currentSettings = UserPreferences.getDatabaseConnectionInfo(); - if(! (connectionSettingsInUse.getUserName().equals(currentSettings.getUserName()) + if (!(connectionSettingsInUse.getUserName().equals(currentSettings.getUserName()) && connectionSettingsInUse.getPassword().equals(currentSettings.getPassword()) && connectionSettingsInUse.getPort().equals(currentSettings.getPort()) - && connectionSettingsInUse.getHost().equals(currentSettings.getHost()) )) { + && connectionSettingsInUse.getHost().equals(currentSettings.getHost()))) { shutdownConnections(); } } catch (UserPreferencesException ex) { throw new HealthMonitorException("Error reading database connection info", ex); } } - + boolean currentlyEnabled = getGlobalEnabledStatusFromDB(); - if( currentlyEnabled != previouslyEnabled) { - if( ! currentlyEnabled ) { + if (currentlyEnabled != previouslyEnabled) { + if (!currentlyEnabled) { isEnabled.set(false); deactivateMonitorLocally(); } else { @@ -745,54 +771,59 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** - * Read the enabled status from the database. - * Check that the health monitor database exists before calling this. + * Read the enabled status from the database. Check that the health monitor + * database exists before calling this. + * * @return true if the database is enabled, false otherwise - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private boolean getGlobalEnabledStatusFromDB() throws HealthMonitorException { - + try (Connection conn = connect(); - Statement statement = conn.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='MONITOR_ENABLED'")) { + Statement statement = conn.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='MONITOR_ENABLED'")) { if (resultSet.next()) { - return(resultSet.getBoolean("value")); + return (resultSet.getBoolean("value")); } throw new HealthMonitorException("No enabled status found in database"); } catch (SQLException ex) { throw new HealthMonitorException("Error initializing database", ex); } } - + /** * Set the global enabled status in the database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void setGlobalEnabledStatusInDB(boolean status) throws HealthMonitorException { - + try (Connection conn = connect(); - Statement statement = conn.createStatement();) { - statement.execute("UPDATE db_info SET value='" + status + "' WHERE name='MONITOR_ENABLED'"); + Statement statement = conn.createStatement();) { + statement.execute("UPDATE db_info SET value='" + status + "' WHERE name='MONITOR_ENABLED'"); } catch (SQLException ex) { throw new HealthMonitorException("Error setting enabled status", ex); } } - + /** * Get the current schema version + * * @return the current schema version - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private CaseDbSchemaVersionNumber getVersion() throws HealthMonitorException { Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); - } + } ResultSet resultSet = null; - + try (Statement statement = conn.createStatement()) { int minorVersion = 0; int majorVersion = 0; @@ -820,7 +851,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } catch (SQLException ex) { throw new HealthMonitorException("Error initializing database", ex); } finally { - if(resultSet != null) { + if (resultSet != null) { try { resultSet.close(); } catch (SQLException ex) { @@ -834,51 +865,51 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Initialize the database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void initializeDatabaseSchema() throws HealthMonitorException { Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } try (Statement statement = conn.createStatement()) { conn.setAutoCommit(false); - statement.execute("CREATE TABLE IF NOT EXISTS timing_data (" + - "id SERIAL PRIMARY KEY," + - "name text NOT NULL," + - "host text NOT NULL," + - "timestamp bigint NOT NULL," + - "count bigint NOT NULL," + - "average double precision NOT NULL," + - "max double precision NOT NULL," + - "min double precision NOT NULL" + - ")"); - - statement.execute("CREATE TABLE IF NOT EXISTS db_info (" + - "id SERIAL PRIMARY KEY NOT NULL," + - "name text NOT NULL," + - "value text NOT NULL" + - ")"); - - statement.execute("CREATE TABLE IF NOT EXISTS user_data ("+ - "id SERIAL PRIMARY KEY," + - "host text NOT NULL," + - "timestamp bigint NOT NULL," + - "event_type int NOT NULL," + - "is_examiner BOOLEAN NOT NULL," + - "case_name text NOT NULL" + - ")"); - - + statement.execute("CREATE TABLE IF NOT EXISTS timing_data (" + + "id SERIAL PRIMARY KEY," + + "name text NOT NULL," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "count bigint NOT NULL," + + "average double precision NOT NULL," + + "max double precision NOT NULL," + + "min double precision NOT NULL" + + ")"); + + statement.execute("CREATE TABLE IF NOT EXISTS db_info (" + + "id SERIAL PRIMARY KEY NOT NULL," + + "name text NOT NULL," + + "value text NOT NULL" + + ")"); + + statement.execute("CREATE TABLE IF NOT EXISTS user_data (" + + "id SERIAL PRIMARY KEY," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "event_type int NOT NULL," + + "is_examiner BOOLEAN NOT NULL," + + "case_name text NOT NULL" + + ")"); + statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')"); statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')"); statement.execute("INSERT INTO db_info (name, value) VALUES ('MONITOR_ENABLED', 'true')"); - + conn.commit(); } catch (SQLException ex) { try { @@ -895,10 +926,10 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** - * The task called by the ScheduledThreadPoolExecutor to handle - * the periodic database update + * The task called by the ScheduledThreadPoolExecutor to handle the periodic + * database update */ static final class PeriodicHealthMonitorTask implements Runnable { @@ -907,18 +938,17 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { recordMetrics(); } } - + /** - * Perform all periodic tasks: - * - Check if monitoring has been enabled / disabled in the database - * - Gather any additional metrics - * - Write current metric data to the database - * Do not run this from a new thread if the case/application is closing. + * Perform all periodic tasks: - Check if monitoring has been enabled / + * disabled in the database - Gather any additional metrics - Write current + * metric data to the database Do not run this from a new thread if the + * case/application is closing. */ private static void recordMetrics() { try { getInstance().updateFromGlobalEnabledStatus(); - if(monitorIsEnabled()) { + if (monitorIsEnabled()) { getInstance().gatherTimerBasedMetrics(); getInstance().writeCurrentStateToDatabase(); } @@ -926,7 +956,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS } } - + @Override public void propertyChange(PropertyChangeEvent evt) { @@ -936,48 +966,48 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { // Case is closing addUserEvent(UserEvent.CASE_CLOSE); - - } else if((null == evt.getOldValue()) && (evt.getNewValue() instanceof Case)) { + + } else if ((null == evt.getOldValue()) && (evt.getNewValue() instanceof Case)) { // Case is opening addUserEvent(UserEvent.CASE_OPEN); } break; } } - + /** - * Debugging method to generate sample data for the database. - * It will delete all current timing data and replace it with randomly generated values. - * If there is more than one node, the second node's times will trend upwards. + * Debugging method to generate sample data for the database. It will delete + * all current timing data and replace it with randomly generated values. If + * there is more than one node, the second node's times will trend upwards. */ void populateDatabaseWithSampleData(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { - - if(! isEnabled.get()) { + + if (!isEnabled.get()) { throw new HealthMonitorException("Can't populate database - monitor not enabled"); } - + // Get the database lock CoordinationService.Lock lock = getSharedDbLock(); - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + String[] metricNames = {"Disk Reads: Hash calculation", "Database: getImages query", "Solr: Index chunk", "Solr: Connectivity check", "Correlation Engine: Notable artifact query", "Correlation Engine: Bulk insert"}; // NON-NLS - + Random rand = new Random(); - + long maxTimestamp = System.currentTimeMillis(); long millisPerHour = 1000 * 60 * 60; long minTimestamp = maxTimestamp - (nDays * (millisPerHour * 24)); - + Connection conn = null; try { conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } - + try (Statement statement = conn.createStatement()) { statement.execute("DELETE FROM timing_data"); // NON-NLS @@ -985,19 +1015,17 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { logger.log(Level.SEVERE, "Error clearing timing data", ex); return; } - - // Add timing metrics to the database String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { - for(String metricName:metricNames) { + for (String metricName : metricNames) { long baseIndex = rand.nextInt(900) + 100; int multiplier = rand.nextInt(5); long minIndexTimeNanos; - switch(multiplier) { + switch (multiplier) { case 0: minIndexTimeNanos = baseIndex; break; @@ -1010,16 +1038,16 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } long maxIndexTimeOverMin = minIndexTimeNanos * 3; - - for(int node = 0;node < nNodes; node++) { - + + for (int node = 0; node < nNodes; node++) { + String host = "testHost" + node; // NON-NLS - + double count = 0; double maxCount = nDays * 24 + 1; - + // Record data every hour, with a small amount of randomness about when it starts - for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += millisPerHour) { + for (long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55); timestamp < maxTimestamp; timestamp += millisPerHour) { double aveTime; @@ -1027,29 +1055,29 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { // collection count++; double slowNodeMultiplier = 1.0; - if((maxCount - count) <= 3 * 24) { + if ((maxCount - count) <= 3 * 24) { slowNodeMultiplier += (3 - (maxCount - count) / 24) * 0.33; } - if( ! createVerificationData ) { + if (!createVerificationData) { // Try to make a reasonable sample data set, with most points in a small range // but some higher and lower int outlierVal = rand.nextInt(30); long randVal = rand.nextLong(); - if(randVal < 0) { + if (randVal < 0) { randVal *= -1; } - if(outlierVal < 2){ + if (outlierVal < 2) { aveTime = minIndexTimeNanos + maxIndexTimeOverMin + randVal % maxIndexTimeOverMin; - } else if(outlierVal == 2){ + } else if (outlierVal == 2) { aveTime = (minIndexTimeNanos / 2) + randVal % (minIndexTimeNanos / 2); - } else if(outlierVal < 17) { + } else if (outlierVal < 17) { aveTime = minIndexTimeNanos + randVal % (maxIndexTimeOverMin / 2); } else { aveTime = minIndexTimeNanos + randVal % maxIndexTimeOverMin; } - - if(node == 1) { + + if (node == 1) { aveTime = aveTime * slowNodeMultiplier; } } else { @@ -1061,8 +1089,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { int day = thisDate.get(Calendar.DAY_OF_MONTH); aveTime = day * 1000000; } - - + statement.setString(1, metricName); statement.setString(2, host); statement.setLong(3, timestamp); @@ -1080,8 +1107,8 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } finally { try { - if(conn != null) { - conn.close(); + if (conn != null) { + conn.close(); } } catch (SQLException ex) { logger.log(Level.SEVERE, "Error closing Connection.", ex); @@ -1093,45 +1120,48 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Get timing metrics currently stored in the database. + * * @param timeRange Maximum age for returned metrics (in milliseconds) + * * @return A map with metric name mapped to a list of data - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ Map> getTimingMetricsFromDatabase(long timeRange) throws HealthMonitorException { - + // Make sure the monitor is enabled. It could theoretically get disabled after this // check but it doesn't seem worth holding a lock to ensure that it doesn't since that // may slow down ingest. - if(! isEnabled.get()) { + if (!isEnabled.get()) { throw new HealthMonitorException("Health Monitor is not enabled"); } - + // Calculate the smallest timestamp we should return long minimumTimestamp = System.currentTimeMillis() - timeRange; try (CoordinationService.Lock lock = getSharedDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); - } + } Map> resultMap = new HashMap<>(); try (Statement statement = conn.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM timing_data WHERE timestamp > " + minimumTimestamp)) { - + ResultSet resultSet = statement.executeQuery("SELECT * FROM timing_data WHERE timestamp > " + minimumTimestamp)) { + while (resultSet.next()) { String name = resultSet.getString("name"); DatabaseTimingResult timingResult = new DatabaseTimingResult(resultSet); - if(resultMap.containsKey(name)) { + if (resultMap.containsKey(name)) { resultMap.get(name).add(timingResult); } else { List resultList = new ArrayList<>(); @@ -1153,36 +1183,39 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error getting database lock", ex); } } - + /** * Get user metrics currently stored in the database. + * * @param timeRange Maximum age for returned metrics (in milliseconds) + * * @return A list of user metrics - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ List getUserMetricsFromDatabase(long timeRange) throws HealthMonitorException { - + // Make sure the monitor is enabled. It could theoretically get disabled after this // check but it doesn't seem worth holding a lock to ensure that it doesn't since that // may slow down ingest. - if(! isEnabled.get()) { + if (!isEnabled.get()) { throw new HealthMonitorException("Health Monitor is not enabled"); } - + // Calculate the smallest timestamp we should return long minimumTimestamp = System.currentTimeMillis() - timeRange; try (CoordinationService.Lock lock = getSharedDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } List resultList = new ArrayList<>(); try (Connection conn = connect(); - Statement statement = conn.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM user_data WHERE timestamp > " + minimumTimestamp)) { - + Statement statement = conn.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM user_data WHERE timestamp > " + minimumTimestamp)) { + while (resultSet.next()) { resultList.add(new UserData(resultSet)); } @@ -1193,47 +1226,51 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } catch (CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error getting database lock", ex); } - } - + } + /** - * Get an exclusive lock for the health monitor database. - * Acquire this before creating, initializing, or updating the database schema. + * Get an exclusive lock for the health monitor database. Acquire this + * before creating, initializing, or updating the database schema. + * * @return The lock - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ + private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException { try { CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES); - if(lock != null){ + if (lock != null) { return lock; } throw new HealthMonitorException("Error acquiring database lock"); - } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error acquiring database lock", ex); } - } - + } + /** - * Get an shared lock for the health monitor database. - * Acquire this before database reads or writes. + * Get an shared lock for the health monitor database. Acquire this before + * database reads or writes. + * * @return The lock - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException{ + private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException { try { String databaseNodeName = DATABASE_NAME; CoordinationService.Lock lock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES); - if(lock != null){ + if (lock != null) { return lock; } throw new HealthMonitorException("Error acquiring database lock"); - } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error acquiring database lock"); } - } - + } + /** * Types of user events being logged */ @@ -1242,72 +1279,79 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { LOG_OFF(1), CASE_OPEN(2), CASE_CLOSE(3); - + int value; - + UserEvent(int value) { this.value = value; } - + /** * Get the integer value of the event to store in the database. + * * @return value corresponding to the event */ int getEventValue() { return value; } - + /** * Get the UserEvent from the value stored in the database + * * @param value + * * @return the corresponding UserEvent object - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ static UserEvent valueOf(int value) throws HealthMonitorException { for (UserEvent v : UserEvent.values()) { - if (v.value == value) { - return v; - } + if (v.value == value) { + return v; + } } throw new HealthMonitorException("Can not create UserEvent from unknown value " + value); } - + /** - * Return whether a case is considered to be open given this event - * as the last recorded event. + * Return whether a case is considered to be open given this event as + * the last recorded event. + * * @return true if a case is open, false otherwise */ boolean caseIsOpen() { - return(this.equals(CASE_OPEN)); + return (this.equals(CASE_OPEN)); } - + /** * Return whether a user is considered to be logged in given this event * as the last recorded event. + * * @return true if a the user is logged in, false otherwise */ boolean userIsLoggedIn() { // LOG_ON, CASE_OPEN, and CASE_CLOSED events all imply that the user // is logged in - return( ! this.equals(LOG_OFF)); + return (!this.equals(LOG_OFF)); } } - + /** - * Class holding user metric data. - * Can be used for storing new events or retrieving - * events out of the database. + * Class holding user metric data. Can be used for storing new events or + * retrieving events out of the database. */ static class UserData { + private final UserEvent eventType; private long timestamp; private final boolean isExaminer; private final String hostname; private String caseName; - + /** - * Create a new UserData object using the given event type - * and the current settings. + * Create a new UserData object using the given event type and the + * current settings. + * * @param eventType The type of event being recorded */ private UserData(UserEvent eventType) { @@ -1315,7 +1359,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.timestamp = System.currentTimeMillis(); this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode()); this.hostname = ""; - + // If there's a case open, record the name try { this.caseName = Case.getCurrentCaseThrows().getDisplayName(); @@ -1324,12 +1368,14 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.caseName = ""; } } - + /** * Create a UserData object from a database result set. + * * @param resultSet The result set containing the data + * * @throws SQLException - * @throws HealthMonitorException + * @throws HealthMonitorException */ UserData(ResultSet resultSet) throws SQLException, HealthMonitorException { this.timestamp = resultSet.getLong("timestamp"); @@ -1338,11 +1384,13 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.isExaminer = resultSet.getBoolean("is_examiner"); this.caseName = resultSet.getString("case_name"); } - + /** * This should only be used to make a dummy object to use for timestamp * comparisons. + * * @param timestamp + * * @return A UserData object with the given timestamp */ static UserData createDummyUserData(long timestamp) { @@ -1350,131 +1398,144 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { userData.timestamp = timestamp; return userData; } - + /** * Get the timestamp for the event + * * @return Timestamp in milliseconds */ long getTimestamp() { return timestamp; } - + /** * Get the host that created the metric + * * @return the host name */ String getHostname() { return hostname; } - + /** * Get the type of event + * * @return the event type */ UserEvent getEventType() { return eventType; } - + /** * Check whether this node is an examiner node or an auto ingest node + * * @return true if it is an examiner node */ boolean isExaminerNode() { return isExaminer; } - + /** * Get the name of the case for this metric + * * @return the case name. Will be the empty string if no case was open. */ String getCaseName() { return caseName; } } - + /** - * Internal class for collecting timing metrics. - * Instead of storing each TimingMetric, we only store the min and max - * seen and the number of metrics and total duration to compute the average - * later. - * One TimingInfo instance should be created per metric name, and - * additional timing metrics will be added to it. + * Internal class for collecting timing metrics. Instead of storing each + * TimingMetric, we only store the min and max seen and the number of + * metrics and total duration to compute the average later. One TimingInfo + * instance should be created per metric name, and additional timing metrics + * will be added to it. */ private class TimingInfo { + private long count; // Number of metrics collected private double sum; // Sum of the durations collected (nanoseconds) private double max; // Maximum value found (nanoseconds) private double min; // Minimum value found (nanoseconds) - + TimingInfo(TimingMetric metric) throws HealthMonitorException { count = 1; sum = metric.getDuration(); max = metric.getDuration(); min = metric.getDuration(); } - + /** - * Add a new TimingMetric to an existing TimingInfo object. - * This is called in a synchronized block for almost all new - * TimingMetric objects, so do as little processing here as possible. + * Add a new TimingMetric to an existing TimingInfo object. This is + * called in a synchronized block for almost all new TimingMetric + * objects, so do as little processing here as possible. + * * @param metric The new metric - * @throws HealthMonitorException Will be thrown if the metric hasn't been stopped + * + * @throws HealthMonitorException Will be thrown if the metric hasn't + * been stopped */ void addMetric(TimingMetric metric) throws HealthMonitorException { - + // Keep track of needed info to calculate the average count++; sum += metric.getDuration(); - + // Check if this is the longest duration seen - if(max < metric.getDuration()) { + if (max < metric.getDuration()) { max = metric.getDuration(); } - + // Check if this is the lowest duration seen - if(min > metric.getDuration()) { + if (min > metric.getDuration()) { min = metric.getDuration(); } } - + /** * Get the average duration + * * @return average duration (milliseconds) */ double getAverage() { return sum / count; } - + /** * Get the maximum duration + * * @return maximum duration (milliseconds) */ double getMax() { return max; } - + /** * Get the minimum duration + * * @return minimum duration (milliseconds) */ double getMin() { return min; } - + /** * Get the total number of metrics collected + * * @return number of metrics collected */ long getCount() { return count; } } - + /** - * Class for retrieving timing metrics from the database to display to the user. - * All times will be in milliseconds. + * Class for retrieving timing metrics from the database to display to the + * user. All times will be in milliseconds. */ static class DatabaseTimingResult { + private final long timestamp; // Time the metric was recorded private final String hostname; // Host that recorded the metric private final long count; // Number of metrics collected @@ -1490,49 +1551,55 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.max = resultSet.getDouble("max"); this.min = resultSet.getDouble("min"); } - + /** * Get the timestamp for when the metric was recorded - * @return + * + * @return */ long getTimestamp() { return timestamp; } - + /** * Get the average duration + * * @return average duration (milliseconds) */ double getAverage() { return average; } - + /** * Get the maximum duration + * * @return maximum duration (milliseconds) */ double getMax() { return max; } - + /** * Get the minimum duration + * * @return minimum duration (milliseconds) */ double getMin() { return min; } - + /** * Get the total number of metrics collected + * * @return number of metrics collected */ long getCount() { return count; - } - + } + /** * Get the name of the host that recorded this metric + * * @return the host */ String getHostName() { diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 07f8fcced7..265cab388f 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -61,8 +61,8 @@ public class HealthMonitorDashboard { private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; // NON-NLS private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(Places.getUserDirectory().getAbsolutePath(), ADMIN_ACCESS_FILE_NAME).toString(); - Map> timingData; - List userData; + Map> timingData; + List userData; private JComboBox timingDateComboBox = null; private JComboBox timingHostComboBox = null; @@ -90,7 +90,7 @@ public class HealthMonitorDashboard { * Display the dashboard. */ @NbBundle.Messages({"HealthMonitorDashboard.display.errorCreatingDashboard=Error creating health monitor dashboard", - "HealthMonitorDashboard.display.dashboardTitle=Enterprise Health Monitor"}) + "HealthMonitorDashboard.display.dashboardTitle=Health Monitor"}) public void display() { // Update the enabled status and get the timing data, then create all @@ -153,14 +153,14 @@ public class HealthMonitorDashboard { private void updateData() throws HealthMonitorException { // Update the monitor status - EnterpriseHealthMonitor.getInstance().updateFromGlobalEnabledStatus(); + HealthMonitor.getInstance().updateFromGlobalEnabledStatus(); - if(EnterpriseHealthMonitor.monitorIsEnabled()) { + if(HealthMonitor.monitorIsEnabled()) { // Get a copy of the timing data from the database - timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(DateRange.getMaximumTimestampRange()); + timingData = HealthMonitor.getInstance().getTimingMetricsFromDatabase(DateRange.getMaximumTimestampRange()); // Get a copy of the user data from the database - userData = EnterpriseHealthMonitor.getInstance().getUserMetricsFromDatabase(DateRange.getMaximumTimestampRange()); + userData = HealthMonitor.getInstance().getUserMetricsFromDatabase(DateRange.getMaximumTimestampRange()); } } @@ -174,7 +174,7 @@ public class HealthMonitorDashboard { private JPanel createTimingPanel() throws HealthMonitorException { // If the monitor isn't enabled, just add a message - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { //timingMetricPanel.setPreferredSize(new Dimension(400,100)); JPanel emptyTimingMetricPanel = new JPanel(); emptyTimingMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle())); @@ -223,7 +223,7 @@ public class HealthMonitorDashboard { JPanel timingControlPanel = new JPanel(); // If the monitor is not enabled, don't add any components - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { return timingControlPanel; } @@ -247,7 +247,7 @@ public class HealthMonitorDashboard { // Create an array of host names Set hostNameSet = new HashSet<>(); for(String metricType:timingData.keySet()) { - for(EnterpriseHealthMonitor.DatabaseTimingResult result: timingData.get(metricType)) { + for(HealthMonitor.DatabaseTimingResult result: timingData.get(metricType)) { hostNameSet.add(result.getHostName()); } } @@ -364,7 +364,7 @@ public class HealthMonitorDashboard { for(String metricName:timingData.keySet()) { // If necessary, trim down the list of results to fit the selected time range - List intermediateTimingDataForDisplay; + List intermediateTimingDataForDisplay; if(timingDateComboBox.getSelectedItem() != null) { DateRange selectedDateRange = DateRange.fromLabel(timingDateComboBox.getSelectedItem().toString()); long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); @@ -403,7 +403,7 @@ public class HealthMonitorDashboard { "HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics"}) private JPanel createUserPanel() throws HealthMonitorException { // If the monitor isn't enabled, just add a message - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { JPanel emptyUserMetricPanel = new JPanel(); emptyUserMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle())); emptyUserMetricPanel.add(new JLabel(" ")); @@ -448,7 +448,7 @@ public class HealthMonitorDashboard { JPanel userControlPanel = new JPanel(); // If the monitor is not enabled, don't add any components - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { return userControlPanel; } @@ -535,7 +535,7 @@ public class HealthMonitorDashboard { JButton enableButton = new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_enableButton()); JButton disableButton = new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_disableButton()); - boolean isEnabled = EnterpriseHealthMonitor.monitorIsEnabled(); + boolean isEnabled = HealthMonitor.monitorIsEnabled(); enableButton.setEnabled(! isEnabled); disableButton.setEnabled(isEnabled); @@ -545,7 +545,7 @@ public class HealthMonitorDashboard { public void actionPerformed(ActionEvent arg0) { try { dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - EnterpriseHealthMonitor.setEnabled(true); + HealthMonitor.setEnabled(true); redisplay(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error enabling monitoring", ex); @@ -561,7 +561,7 @@ public class HealthMonitorDashboard { public void actionPerformed(ActionEvent arg0) { try { dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - EnterpriseHealthMonitor.setEnabled(false); + HealthMonitor.setEnabled(false); redisplay(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error disabling monitoring", ex); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index 187513a84a..4a709e79fb 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -45,7 +45,7 @@ public class Installer extends ModuleInstall { public void restored() { try { - EnterpriseHealthMonitor.startUpIfEnabled(); + HealthMonitor.startUpIfEnabled(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error starting health services monitor", ex); } @@ -54,7 +54,7 @@ public class Installer extends ModuleInstall { @Override public void close() { try { - EnterpriseHealthMonitor.shutdown(); + HealthMonitor.shutdown(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error stopping health services monitor", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index a540ea8c6b..c88ce1e627 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -39,7 +39,7 @@ import java.util.logging.Level; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor.DatabaseTimingResult; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor.DatabaseTimingResult; /** * Creates a graph of the given timing metric data diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java index d8da7afe97..b5b76b6993 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java @@ -38,7 +38,7 @@ import javax.swing.JPanel; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor.UserData; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor.UserData; /** * Creates graphs using the given user metric data diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index d907a131e9..74387840b5 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestMessage; @@ -184,7 +184,7 @@ public class HashDbIngestModule implements FileIngestModule { String md5Hash = file.getMd5Hash(); if (md5Hash == null || md5Hash.isEmpty()) { try { - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); + TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); long calcstart = System.currentTimeMillis(); md5Hash = HashUtility.calculateMd5Hash(file); if (file.getSize() > 0) { @@ -192,10 +192,10 @@ public class HashDbIngestModule implements FileIngestModule { // strongly with file size until the files get large. // Only normalize if the file size is greater than ~1MB. if (file.getSize() < 1000000) { - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } else { // In testing, this normalization gave reasonable resuls - EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); + HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); } } file.setMd5Hash(md5Hash); diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java index 7347d32c8f..0019800ee1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCond */ public final class FilesSetsManager extends Observable { - @NbBundle.Messages({"FilesSetsManager.allFilesAndDirectories=All Files and Directories", + @NbBundle.Messages({"FilesSetsManager.allFilesAndDirectories=All Files and Directories (Not Unallocated Space)", "FilesSetsManager.allFilesDirectoriesAndUnallocated=All Files, Directories, and Unallocated Space"}) private static final List ILLEGAL_FILE_NAME_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", "/", ":", "*", "?", "\"", "<", ">"))); private static final List ILLEGAL_FILE_PATH_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", ":", "*", "?", "\"", "<", ">"))); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index b5e587ec81..d9fb71f56c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -380,6 +380,11 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen throw new AutoIngestMonitorException("Error removing priority for job " + job.toString(), ex); } job.setPriority(DEFAULT_PRIORITY); + + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(job); } /* @@ -428,6 +433,11 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen throw new AutoIngestMonitorException("Error bumping priority for job " + job.toString(), ex); } job.setPriority(highestPriority); + + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(job); } /* @@ -466,7 +476,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } /* - * If the job was still in the pending jobs queue, bump its + * If the job was still in the pending jobs queue, reset its * priority. */ if (null != jobToDeprioritize) { @@ -480,6 +490,11 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } jobToDeprioritize.setPriority(DEFAULT_PRIORITY); + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(jobToDeprioritize); + /* * Publish a deprioritization event. */ @@ -538,6 +553,11 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } jobToPrioritize.setPriority(highestPriority); + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(jobToPrioritize); + /* * Publish a prioritization event. */ diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 03b06c18e0..106e521142 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -102,6 +102,7 @@ public class SharedConfiguration { private boolean hideKnownFilesInViews; private boolean hideSlackFilesInDataSource; private boolean hideSlackFilesInViews; + private boolean groupDatasources; private boolean keepPreferredViewer; /** @@ -206,6 +207,8 @@ public class SharedConfiguration { uploadHashDbSettings(remoteFolder); uploadFileExporterSettings(remoteFolder); uploadCentralRepositorySettings(remoteFolder); + uploadObjectDetectionClassifiers(remoteFolder); + uploadPythonModules(remoteFolder); try { Files.deleteIfExists(uploadInProgress.toPath()); @@ -271,6 +274,8 @@ public class SharedConfiguration { downloadAndroidTriageSettings(remoteFolder); downloadFileExporterSettings(remoteFolder); downloadCentralRepositorySettings(remoteFolder); + downloadObjectDetectionClassifiers(remoteFolder); + downloadPythonModules(remoteFolder); // Download general settings, then restore the current // values for the unshared fields @@ -348,6 +353,7 @@ public class SharedConfiguration { fileIngestThreads = UserPreferences.numberOfFileIngestThreads(); hideSlackFilesInDataSource = UserPreferences.hideSlackFilesInDataSourcesTree(); hideSlackFilesInViews = UserPreferences.hideSlackFilesInViewsTree(); + groupDatasources = UserPreferences.groupItemsInTreeByDatasource(); } /** @@ -364,6 +370,7 @@ public class SharedConfiguration { UserPreferences.setNumberOfFileIngestThreads(fileIngestThreads); UserPreferences.setHideSlackFilesInDataSourcesTree(hideSlackFilesInDataSource); UserPreferences.setHideSlackFilesInViewsTree(hideSlackFilesInViews); + UserPreferences.setGroupItemsInTreeByDatasource(groupDatasources); } /** @@ -511,6 +518,71 @@ public class SharedConfiguration { throw new SharedConfigurationException(String.format("Failed to copy %s to %s", remoteFile.getAbsolutePath(), localSettingsFolder.getAbsolutePath()), ex); } } + + /** + * Copy an entire local settings folder to the remote folder, deleting any existing files. + * + * @param localFolder The local folder to copy + * @param remoteBaseFolder The remote folder that will hold a copy of the original folder + * + * @throws SharedConfigurationException + */ + private void copyLocalFolderToRemoteFolder(File localFolder, File remoteBaseFolder) throws SharedConfigurationException { + logger.log(Level.INFO, "Uploading {0} to {1}", new Object[]{localFolder.getAbsolutePath(), remoteBaseFolder.getAbsolutePath()}); + + File newRemoteFolder = new File(remoteBaseFolder, localFolder.getName()); + + if(newRemoteFolder.exists()) { + try { + FileUtils.deleteDirectory(newRemoteFolder); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Failed to delete remote folder {0}", newRemoteFolder.getAbsolutePath()); + throw new SharedConfigurationException(String.format("Failed to delete remote folder {0}", newRemoteFolder.getAbsolutePath()), ex); + } + } + + try { + FileUtils.copyDirectoryToDirectory(localFolder, remoteBaseFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", localFolder, remoteBaseFolder.getAbsolutePath()), ex); + } + } + + /** + * Copy an entire remote settings folder to the local folder, deleting any existing files. + * No error if the remote folder does not exist. + * + * @param localFolder The local folder that will be overwritten. + * @param remoteBaseFolder The remote folder holding the folder that will be copied + * + * @throws SharedConfigurationException + */ + private void copyRemoteFolderToLocalFolder(File localFolder, File remoteBaseFolder) throws SharedConfigurationException { + logger.log(Level.INFO, "Downloading {0} from {1}", new Object[]{localFolder.getAbsolutePath(), remoteBaseFolder.getAbsolutePath()}); + + // Clean out the local folder regardless of whether the remote version exists. leave the + // folder in place since Autopsy expects it to exist. + if(localFolder.exists()) { + try { + FileUtils.cleanDirectory(localFolder); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Failed to delete files from local folder {0}", localFolder.getAbsolutePath()); + throw new SharedConfigurationException(String.format("Failed to delete files from local folder {0}", localFolder.getAbsolutePath()), ex); + } + } + + File remoteSubFolder = new File(remoteBaseFolder, localFolder.getName()); + if(! remoteSubFolder.exists()) { + logger.log(Level.INFO, "{0} does not exist", remoteSubFolder.getAbsolutePath()); + return; + } + + try { + FileUtils.copyDirectory(remoteSubFolder, localFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s from %s", localFolder, remoteBaseFolder.getAbsolutePath()), ex); + } + } /** * Upload the basic set of auto-ingest settings to the shared folder. @@ -828,6 +900,58 @@ public class SharedConfiguration { copyToLocalFolder(AUTO_INGEST_PROPERTIES, moduleDirPath, remoteFolder, false); } + /** + * Upload the object detection classifiers. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadObjectDetectionClassifiers(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading object detection classfiers"); + File classifiersFolder = new File(PlatformUtil.getObjectDetectionClassifierPath()); + copyLocalFolderToRemoteFolder(classifiersFolder, remoteFolder); + } + + /** + * Download the object detection classifiers. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadObjectDetectionClassifiers(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading object detection classfiers"); + File classifiersFolder = new File(PlatformUtil.getObjectDetectionClassifierPath()); + copyRemoteFolderToLocalFolder(classifiersFolder, remoteFolder); + } + + /** + * Upload the Python modules. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadPythonModules(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading python modules"); + File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); + copyLocalFolderToRemoteFolder(classifiersFolder, remoteFolder); + } + + /** + * Download the Python modules. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadPythonModules(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading python modules"); + File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); + copyRemoteFolderToLocalFolder(classifiersFolder, remoteFolder); + } + /** * Upload settings and hash databases to the shared folder. The general * algorithm is: - Copy the general settings in hashsets.xml - For each hash diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 51d58c7f1e..7688ccd146 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -1179,6 +1179,12 @@ public final class DrawableDB { * @return the number of files with Cat-0 */ public long getUncategorizedCount(Collection fileIDs) { + + // if the fileset is empty, return count as 0 + if (fileIDs.isEmpty()) { + return 0; + } + DrawableTagsManager tagsManager = controller.getTagsManager(); // get a comma seperated list of TagName ids for non zero categories diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 7dddfa5ca8..de9c1f6b4f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -82,6 +82,7 @@ import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData.DbType; /** * Provides an abstraction layer on top of {@link DrawableDB} ( and to some @@ -351,11 +352,22 @@ public class GroupManager { case MIME_TYPE: if (nonNull(db)) { HashSet types = new HashSet<>(); - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery("select group_concat(obj_id), mime_type from tsk_files group by mime_type "); //NON-NLS + + // Use the group_concat function to get a list of files for each mime type. + // This has different syntax on Postgres vs SQLite + String groupConcatClause; + if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { + groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; + } + else { + groupConcatClause = "select group_concat(obj_id) as object_ids"; + } + String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS ResultSet resultSet = executeQuery.getResultSet();) { while (resultSet.next()) { final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("group_concat(obj_id)"); //NON-NLS + String objIds = resultSet.getString("object_ids"); //NON-NLS Pattern.compile(",").splitAsStream(objIds) .map(Long::valueOf) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java index 3177bbbbb4..57b84b11d6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java @@ -36,7 +36,6 @@ import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -48,7 +47,6 @@ import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.VirtualDirectory; /** @@ -126,50 +124,45 @@ class AdHocSearchFilterNode extends FilterNode { @Override public List visit(File f) { - return getFileActions(true); + return getFileActions(); } @Override public List visit(DerivedFile f) { - return getFileActions(true); + return getFileActions(); } @Override public List visit(Directory d) { - return getFileActions(false); + return getFileActions(); } @Override public List visit(LayoutFile lf) { - //we want hashsearch enabled on carved files but not unallocated blocks - boolean enableHashSearch = (lf.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.CARVED); - return getFileActions(enableHashSearch); + return getFileActions(); } @Override public List visit(LocalFile lf) { - return getFileActions(true); + return getFileActions(); } @Override public List visit(SlackFile f) { - return getFileActions(false); + return getFileActions(); } @Override public List visit(VirtualDirectory dir) { - return getFileActions(false); + return getFileActions(); } - private List getFileActions(boolean enableHashSearch) { + private List getFileActions() { List actionsList = new ArrayList<>(); actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this)); actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); actionsList.add(null); actionsList.add(ExtractAction.getInstance()); - Action hashSearchAction = new HashSearchAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.searchSameMd5"), getOriginal()); - hashSearchAction.setEnabled(enableHashSearch); - actionsList.add(hashSearchAction); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); @@ -185,7 +178,7 @@ class AdHocSearchFilterNode extends FilterNode { @Override protected List defaultVisit(Content c) { - return getFileActions(false); + return getFileActions(); } } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index b9c4541c7b..fb792808ab 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -27,7 +27,7 @@ import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; @@ -237,9 +237,9 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 4d36cd1164..1253d40d95 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -70,7 +70,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.Content; @@ -710,9 +710,9 @@ public class Server { if (null == currentCore) { throw new NoOpenCoreException(); } - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Index chunk"); currentCore.addDocument(doc); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } finally { currentCoreLock.readLock().unlock(); } @@ -781,9 +781,9 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); @@ -1325,13 +1325,13 @@ public class Server { * @throws IOException */ void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException { - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check"); CoreAdminRequest statusRequest = new CoreAdminRequest(); statusRequest.setCoreName( null ); statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS ); statusRequest.setIndexInfoNeeded(false); statusRequest.process(solrServer); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } /** diff --git a/docs/doxygen-user/common_files.dox b/docs/doxygen-user/common_files.dox index 21209b8108..bdc6c0bbb9 100644 --- a/docs/doxygen-user/common_files.dox +++ b/docs/doxygen-user/common_files.dox @@ -2,7 +2,7 @@ \section common_files_overview Overview -The common files feature allows you to search for multiple copies of the same file within a case. +The common files feature allows you to search for multiple copies of the same file in different data sources within a case. \section common_files_usage Usage diff --git a/docs/doxygen-user/images/common_files_dialog.png b/docs/doxygen-user/images/common_files_dialog.png index d1d780e66b..8be9e7c6f1 100644 Binary files a/docs/doxygen-user/images/common_files_dialog.png and b/docs/doxygen-user/images/common_files_dialog.png differ diff --git a/docs/doxygen-user/images/common_files_results.png b/docs/doxygen-user/images/common_files_results.png index 415d234416..7b16ce92b7 100644 Binary files a/docs/doxygen-user/images/common_files_results.png and b/docs/doxygen-user/images/common_files_results.png differ