From 4fdfab329573a29a57a0121066c310182f831155 Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Mon, 25 Jun 2018 10:32:15 -0600 Subject: [PATCH 01/37] instances column changed from matches --- .../autopsy/commonfilesearch/InstanceCountNode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index de22cd6590..146ea930ff 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -122,18 +122,18 @@ final public class InstanceCountNode extends DisplayableItemNode { * @param node The item to get properties for. */ static private void fillPropertyMap(Map map, InstanceCountNode node) { - map.put(InstanceCountNodePropertyType.Match.toString(), node.getInstanceCount()); + map.put(InstanceCountNodePropertyType.Instances.toString(), node.getInstanceCount()); } /** * Fields which will appear in the tree table. */ @NbBundle.Messages({ - "InstanceCountNodePropertyType.matchCountColLbl1=Match" + "InstanceCountNodePropertyType.instancesCountColLbl1=Instances" }) public enum InstanceCountNodePropertyType{ - Match(Bundle.InstanceCountNodePropertyType_matchCountColLbl1()); + Instances(Bundle.InstanceCountNodePropertyType_instancesCountColLbl1()); final private String displayString; From 2e5820e313c7fab482c761413d38706da1d82a8a Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Tue, 26 Jun 2018 09:59:06 -0600 Subject: [PATCH 02/37] fiddling --- .../CommonFilesSearchResultsViewerTable.java | 6 ++-- .../commonfilesearch/FileInstanceNode.java | 17 ++++++++-- .../commonfilesearch/InstanceCountNode.java | 19 +++++++++-- .../autopsy/commonfilesearch/Md5Node.java | 33 +++++++++++++++---- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java index f699900e8e..1792df94f7 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java @@ -41,7 +41,8 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { static { Map map = new HashMap<>(); - map.put(Bundle.CommonFilesSearchResultsViewerTable_matchColLbl(), 235); + map.put(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), 235); + map.put(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), 235); map.put(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), 300); map.put(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), 200); map.put(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), 100); @@ -52,7 +53,8 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { } @NbBundle.Messages({ - "CommonFilesSearchResultsViewerTable.matchColLbl=Match", + "CommonFilesSearchResultsViewerTable.filesColLbl=Files", + "CommonFilesSearchResultsViewerTable.instancesColLbl=Instances", "CommonFilesSearchResultsViewerTable.pathColLbl=Parent Path", "CommonFilesSearchResultsViewerTable.hashsetHitsColLbl=Hash Set Hits", "CommonFilesSearchResultsViewerTable.dataSourceColLbl=Data Source", diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java index e25f4a8637..7854bdfde4 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java @@ -30,7 +30,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 +46,8 @@ public class FileInstanceNode extends FileNode { public FileInstanceNode(AbstractFile fsContent, String dataSource) { super(fsContent); this.dataSource = dataSource; + + this.setDisplayName(fsContent.getName()); } @Override @@ -98,10 +100,13 @@ public class FileInstanceNode extends FileNode { */ static private void fillPropertyMap(Map map, FileInstanceNode node) { + map.put(CommonFilePropertyType.Files.toString(), node.getContent().getName()); + map.put(CommonFilePropertyType.Instances.toString(), ""); 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())); + map.put(CommonFilePropertyType.Tags.toString(), ""); } /** @@ -109,18 +114,24 @@ public class FileInstanceNode extends FileNode { * instance of this object. */ @NbBundle.Messages({ + "CommonFilesPropertyType.filesColLbl1=Files", + "CommonFilePropertyType.instancesColLbl=Instances", "CommonFilePropertyType.pathColLbl=Parent Path", "CommonFilePropertyType.hashsetHitsColLbl=Hash Set Hits", "CommonFilePropertyType.dataSourceColLbl=Data Source", "CommonFilePropertyType.caseColLbl=Case", - "CommonFilePropertyType.mimeTypeColLbl=MIME Type" + "CommonFilePropertyType.mimeTypeColLbl=MIME Type", + "CommonFilePropertyType.tagsColLbl1=Tags" }) public enum CommonFilePropertyType { + Files(Bundle.CommonFilesPropertyType_filesColLbl1()), + Instances(Bundle.CommonFilePropertyType_instancesColLbl()), ParentPath(Bundle.CommonFilePropertyType_pathColLbl()), HashsetHits(Bundle.CommonFilePropertyType_hashsetHitsColLbl()), DataSource(Bundle.CommonFilePropertyType_dataSourceColLbl()), - MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl()); + MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl()), + Tags(Bundle.CommonFilePropertyType_tagsColLbl1()); final private String displayString; diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index 146ea930ff..be9ec00ce0 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -30,6 +30,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.commonfilesearch.FileInstanceNode.CommonFilePropertyType; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; @@ -50,7 +51,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 +59,7 @@ 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())); } /** @@ -122,18 +123,30 @@ final public class InstanceCountNode extends DisplayableItemNode { * @param node The item to get properties for. */ static private void fillPropertyMap(Map map, InstanceCountNode node) { + + //map.put(InstanceCountNodePropertyType.Files.toString(), ""); map.put(InstanceCountNodePropertyType.Instances.toString(), node.getInstanceCount()); + //map.put(CommonFilePropertyType.ParentPath.toString(), ""); + //map.put(CommonFilePropertyType.HashsetHits.toString(), ""); + //map.put(CommonFilePropertyType.DataSource.toString(), ""); + //map.put(CommonFilePropertyType.MimeType.toString(), ""); + //map.put(CommonFilePropertyType.Tags.toString(), ""); + } /** * Fields which will appear in the tree table. */ @NbBundle.Messages({ - "InstanceCountNodePropertyType.instancesCountColLbl1=Instances" + "InstanceCountNodePropertyType.filesColLbl1=Files", + "InstanceCountNodePropertyType.instancesCountColLbl1=Instances", + "InstanceCountNodePropertyType.parentPathColLbl1=Parent Path" }) public enum InstanceCountNodePropertyType{ + //Files(Bundle.InstanceCountNodePropertyType_filesColLbl1()), Instances(Bundle.InstanceCountNodePropertyType_instancesCountColLbl1()); + //ParentPath(Bundle.InstanceCountNodePropertyType_parentPathColLbl1()); final private String displayString; diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java index 0705636ef6..4d74a5b9b2 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; +import org.apache.commons.lang.StringUtils; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; @@ -30,6 +31,7 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.commonfilesearch.FileInstanceNode.CommonFilePropertyType; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -52,6 +54,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 +69,7 @@ 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)); } /** @@ -121,8 +126,14 @@ public class Md5Node extends DisplayableItemNode { * @param node The item to get properties for. */ static private void fillPropertyMap(Map map, Md5Node node) { - //map.put(CommonFileParentPropertyType.Case.toString(), ""); + + //map.put(CommonFileParentPropertyType.Files.toString(), ""); + //map.put(CommonFileParentPropertyType.Instances.toString(), ""); + //map.put(CommonFileParentPropertyType.ParentPath.toString(), ""); + //map.put(CommonFileParentPropertyType.HashsetHits.toString(), ""); map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); + //map.put(CommonFileParentPropertyType.MimeType.toString(), ""); + //map.put(CommonFileParentPropertyType.Tags.toString(), ""); } @Override @@ -173,13 +184,21 @@ public class Md5Node extends DisplayableItemNode { } @NbBundle.Messages({ - "CommonFileParentPropertyType.fileColLbl=File", - "CommonFileParentPropertyType.instanceColLbl=Instance Count", - "CommonFileParentPropertyType.caseColLbl=Case", - "CommonFileParentPropertyType.dataSourceColLbl=Data Source"}) + "CommonFileParentPropertyType.filesColLbl1=Files", + "CommonFileParentPropertyType.instancesColLbl1=Instances", + "CommonFilesSearchReCommonFileParentPropertyTypesultsViewerTable.pathColLbl=Parent Path", + "CommonFileParentPropertyType.dataSourceColLbl=Data Source", + "CommonFileParentPropertyType.parentPathColLbl=Parent Path", + "CommonFileParentPropertyType.mimeTypeColLbl1=MIME Type"}) public enum CommonFileParentPropertyType { - DataSource(Bundle.CommonFileParentPropertyType_dataSourceColLbl()); + //Files(Bundle.CommonFileParentPropertyType_filesColLbl1()), + //Instances(Bundle.CommonFilesParentPropertyType_instancesColLbl1()), + //ParentPath(Bundle.CommonFileParentPropertyType_parentPathColLbl()), + //HashSetHits(Bundle.CommonFileParentPropertyType_hashSetHitsColLbl1()), + DataSource(Bundle.CommonFileParentPropertyType_dataSourceColLbl()), + //MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl1()) + ; final private String displayString; From 7af4bfb8801639c7ec2450a0f8e8a082b4360175 Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Tue, 26 Jun 2018 10:38:17 -0600 Subject: [PATCH 03/37] single source for stings --- .../commonfilesearch/FileInstanceNode.java | 24 ++++++------------- .../commonfilesearch/InstanceCountNode.java | 7 +----- .../autopsy/commonfilesearch/Md5Node.java | 9 +------ 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java index 7854bdfde4..bf1e139ca0 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java @@ -113,25 +113,15 @@ public class FileInstanceNode extends FileNode { * Encapsulates the columns to be displayed for reach row represented by an * instance of this object. */ - @NbBundle.Messages({ - "CommonFilesPropertyType.filesColLbl1=Files", - "CommonFilePropertyType.instancesColLbl=Instances", - "CommonFilePropertyType.pathColLbl=Parent Path", - "CommonFilePropertyType.hashsetHitsColLbl=Hash Set Hits", - "CommonFilePropertyType.dataSourceColLbl=Data Source", - "CommonFilePropertyType.caseColLbl=Case", - "CommonFilePropertyType.mimeTypeColLbl=MIME Type", - "CommonFilePropertyType.tagsColLbl1=Tags" - }) public enum CommonFilePropertyType { - Files(Bundle.CommonFilesPropertyType_filesColLbl1()), - Instances(Bundle.CommonFilePropertyType_instancesColLbl()), - ParentPath(Bundle.CommonFilePropertyType_pathColLbl()), - HashsetHits(Bundle.CommonFilePropertyType_hashsetHitsColLbl()), - DataSource(Bundle.CommonFilePropertyType_dataSourceColLbl()), - MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl()), - Tags(Bundle.CommonFilePropertyType_tagsColLbl1()); + Files(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl()), + Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()), + ParentPath(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl()), + HashsetHits(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl()), + DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), + MimeType(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl()), + Tags(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1()); final private String displayString; diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index be9ec00ce0..81b5cd0557 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -137,15 +137,10 @@ final public class InstanceCountNode extends DisplayableItemNode { /** * Fields which will appear in the tree table. */ - @NbBundle.Messages({ - "InstanceCountNodePropertyType.filesColLbl1=Files", - "InstanceCountNodePropertyType.instancesCountColLbl1=Instances", - "InstanceCountNodePropertyType.parentPathColLbl1=Parent Path" - }) public enum InstanceCountNodePropertyType{ //Files(Bundle.InstanceCountNodePropertyType_filesColLbl1()), - Instances(Bundle.InstanceCountNodePropertyType_instancesCountColLbl1()); + Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()); //ParentPath(Bundle.InstanceCountNodePropertyType_parentPathColLbl1()); final private String displayString; diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java index 4d74a5b9b2..3a6f1c986f 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -183,20 +183,13 @@ public class Md5Node extends DisplayableItemNode { } } - @NbBundle.Messages({ - "CommonFileParentPropertyType.filesColLbl1=Files", - "CommonFileParentPropertyType.instancesColLbl1=Instances", - "CommonFilesSearchReCommonFileParentPropertyTypesultsViewerTable.pathColLbl=Parent Path", - "CommonFileParentPropertyType.dataSourceColLbl=Data Source", - "CommonFileParentPropertyType.parentPathColLbl=Parent Path", - "CommonFileParentPropertyType.mimeTypeColLbl1=MIME Type"}) public enum CommonFileParentPropertyType { //Files(Bundle.CommonFileParentPropertyType_filesColLbl1()), //Instances(Bundle.CommonFilesParentPropertyType_instancesColLbl1()), //ParentPath(Bundle.CommonFileParentPropertyType_parentPathColLbl()), //HashSetHits(Bundle.CommonFileParentPropertyType_hashSetHitsColLbl1()), - DataSource(Bundle.CommonFileParentPropertyType_dataSourceColLbl()), + DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), //MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl1()) ; From 52d13face45012d2bb7f2dbfe011f514d7f747b3 Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Tue, 26 Jun 2018 10:59:55 -0600 Subject: [PATCH 04/37] more fiddling --- .../commonfilesearch/FileInstanceNode.java | 15 ++-- .../commonfilesearch/InstanceCountNode.java | 15 ++-- .../autopsy/commonfilesearch/Md5Node.java | 81 ++++++++++--------- 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java index bf1e139ca0..8c2f774dcb 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java @@ -76,15 +76,16 @@ public class FileInstanceNode extends FileNode { } Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); + //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_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, "")); + 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); diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index 81b5cd0557..1dd2059373 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -103,14 +103,17 @@ final public class InstanceCountNode extends DisplayableItemNode { sheet.put(sheetSet); } - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); + //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_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, this.getInstanceCount())); + +// for (InstanceCountNode.InstanceCountNodePropertyType propType : InstanceCountNode.InstanceCountNodePropertyType.values()) { +// final String propString = propType.toString(); +// sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, this.getInstanceCount())); +// } return sheet; } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java index 3a6f1c986f..2a88684cb4 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -105,15 +105,18 @@ public class Md5Node extends DisplayableItemNode { sheetSet = Sheet.createPropertiesSet(); sheet.put(sheetSet); } - - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); +// +// 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_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSources())); + +// for (Md5Node.CommonFileParentPropertyType propType : Md5Node.CommonFileParentPropertyType.values()) { +// final String propString = propType.toString(); +// sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); +// } return sheet; } @@ -125,16 +128,16 @@ public class Md5Node extends DisplayableItemNode { * put * @param node The item to get properties for. */ - static private void fillPropertyMap(Map map, Md5Node node) { - - //map.put(CommonFileParentPropertyType.Files.toString(), ""); - //map.put(CommonFileParentPropertyType.Instances.toString(), ""); - //map.put(CommonFileParentPropertyType.ParentPath.toString(), ""); - //map.put(CommonFileParentPropertyType.HashsetHits.toString(), ""); - map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); - //map.put(CommonFileParentPropertyType.MimeType.toString(), ""); - //map.put(CommonFileParentPropertyType.Tags.toString(), ""); - } +// static private void fillPropertyMap(Map map, Md5Node node) { +// +// //map.put(CommonFileParentPropertyType.Files.toString(), ""); +// //map.put(CommonFileParentPropertyType.Instances.toString(), ""); +// //map.put(CommonFileParentPropertyType.ParentPath.toString(), ""); +// //map.put(CommonFileParentPropertyType.HashsetHits.toString(), ""); +// map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); +// //map.put(CommonFileParentPropertyType.MimeType.toString(), ""); +// //map.put(CommonFileParentPropertyType.Tags.toString(), ""); +// } @Override public T accept(DisplayableItemNodeVisitor visitor) { @@ -183,25 +186,25 @@ public class Md5Node extends DisplayableItemNode { } } - public enum CommonFileParentPropertyType { - - //Files(Bundle.CommonFileParentPropertyType_filesColLbl1()), - //Instances(Bundle.CommonFilesParentPropertyType_instancesColLbl1()), - //ParentPath(Bundle.CommonFileParentPropertyType_parentPathColLbl()), - //HashSetHits(Bundle.CommonFileParentPropertyType_hashSetHitsColLbl1()), - DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), - //MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl1()) - ; - - final private String displayString; - - private CommonFileParentPropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return this.displayString; - } - } -} +// public enum CommonFileParentPropertyType { +// +// //Files(Bundle.CommonFileParentPropertyType_filesColLbl1()), +// //Instances(Bundle.CommonFilesParentPropertyType_instancesColLbl1()), +// //ParentPath(Bundle.CommonFileParentPropertyType_parentPathColLbl()), +// //HashSetHits(Bundle.CommonFileParentPropertyType_hashSetHitsColLbl1()), +// DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), +// //MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl1()) +// ; +// +// final private String displayString; +// +// private CommonFileParentPropertyType(String displayString) { +// this.displayString = displayString; +// } +// +// @Override +// public String toString() { +// return this.displayString; +// } +// } +} \ No newline at end of file From e47069ffd8151acbe774a36e55f45a9d38fc9e87 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 26 Jun 2018 10:15:23 -0700 Subject: [PATCH 05/37] import --- .../sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index be9ec00ce0..92ca205645 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -30,7 +30,6 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.commonfilesearch.FileInstanceNode.CommonFilePropertyType; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; From 8f961de710b125a3bebd75e2f1d2d5944de76a27 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 26 Jun 2018 10:44:24 -0700 Subject: [PATCH 06/37] Columns set properly. --- .../CommonFilesSearchResultsViewerTable.java | 1 + .../commonfilesearch/FileInstanceNode.java | 78 +++++++++---------- .../commonfilesearch/InstanceCountNode.java | 67 ++++++++-------- .../autopsy/commonfilesearch/Md5Node.java | 9 +-- 4 files changed, 76 insertions(+), 79 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java index 1792df94f7..85d95d8b99 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java @@ -38,6 +38,7 @@ 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<>(); diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java index 8c2f774dcb..86d32bd0a2 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; @@ -75,13 +73,13 @@ public class FileInstanceNode extends FileNode { sheet.put(sheetSet); } - Map map = new LinkedHashMap<>(); + //Map map = new LinkedHashMap<>(); //fillPropertyMap(map, this); final String NO_DESCR = Bundle.FileInstanceNode_createSheet_noDescription(); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName())); - sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, "")); + //sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, "")); 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())); @@ -99,40 +97,40 @@ public class FileInstanceNode extends FileNode { * put * @param node The item to get properties for. */ - static private void fillPropertyMap(Map map, FileInstanceNode node) { - - map.put(CommonFilePropertyType.Files.toString(), node.getContent().getName()); - map.put(CommonFilePropertyType.Instances.toString(), ""); - 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())); - map.put(CommonFilePropertyType.Tags.toString(), ""); - } - - /** - * Encapsulates the columns to be displayed for reach row represented by an - * instance of this object. - */ - public enum CommonFilePropertyType { - - Files(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl()), - Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()), - ParentPath(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl()), - HashsetHits(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl()), - DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), - MimeType(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl()), - Tags(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1()); - - final private String displayString; - - private CommonFilePropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return displayString; - } - } +// static private void fillPropertyMap(Map map, FileInstanceNode node) { +// +// map.put(CommonFilePropertyType.Files.toString(), node.getContent().getName()); +// map.put(CommonFilePropertyType.Instances.toString(), ""); +// 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())); +// map.put(CommonFilePropertyType.Tags.toString(), ""); +// } +// +// /** +// * Encapsulates the columns to be displayed for reach row represented by an +// * instance of this object. +// */ +// public enum CommonFilePropertyType { +// +// Files(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl()), +// Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()), +// ParentPath(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl()), +// HashsetHits(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl()), +// DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), +// MimeType(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl()), +// Tags(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1()); +// +// 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 5ce80cd5e9..b61e439939 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; @@ -106,7 +105,7 @@ final public class InstanceCountNode extends DisplayableItemNode { //fillPropertyMap(map, this); final String NO_DESCR = Bundle.InstanceCountNode_createSheet_noDescription(); - + 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())); // for (InstanceCountNode.InstanceCountNodePropertyType propType : InstanceCountNode.InstanceCountNodePropertyType.values()) { @@ -124,38 +123,38 @@ final public class InstanceCountNode extends DisplayableItemNode { * put * @param node The item to get properties for. */ - static private void fillPropertyMap(Map map, InstanceCountNode node) { - - //map.put(InstanceCountNodePropertyType.Files.toString(), ""); - map.put(InstanceCountNodePropertyType.Instances.toString(), node.getInstanceCount()); - //map.put(CommonFilePropertyType.ParentPath.toString(), ""); - //map.put(CommonFilePropertyType.HashsetHits.toString(), ""); - //map.put(CommonFilePropertyType.DataSource.toString(), ""); - //map.put(CommonFilePropertyType.MimeType.toString(), ""); - //map.put(CommonFilePropertyType.Tags.toString(), ""); - - } - - /** - * Fields which will appear in the tree table. - */ - public enum InstanceCountNodePropertyType{ - - //Files(Bundle.InstanceCountNodePropertyType_filesColLbl1()), - Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()); - //ParentPath(Bundle.InstanceCountNodePropertyType_parentPathColLbl1()); - - final private String displayString; - - private InstanceCountNodePropertyType(String displayName){ - this.displayString = displayName; - } - - @Override - public String toString(){ - return this.displayString; - } - } +// static private void fillPropertyMap(Map map, InstanceCountNode node) { +// +// //map.put(InstanceCountNodePropertyType.Files.toString(), ""); +// map.put(InstanceCountNodePropertyType.Instances.toString(), node.getInstanceCount()); +// //map.put(CommonFilePropertyType.ParentPath.toString(), ""); +// //map.put(CommonFilePropertyType.HashsetHits.toString(), ""); +// //map.put(CommonFilePropertyType.DataSource.toString(), ""); +// //map.put(CommonFilePropertyType.MimeType.toString(), ""); +// //map.put(CommonFilePropertyType.Tags.toString(), ""); +// +// } +// +// /** +// * Fields which will appear in the tree table. +// */ +// public enum InstanceCountNodePropertyType{ +// +// //Files(Bundle.InstanceCountNodePropertyType_filesColLbl1()), +// Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()); +// //ParentPath(Bundle.InstanceCountNodePropertyType_parentPathColLbl1()); +// +// 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 2a88684cb4..14dfd7388a 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -19,11 +19,8 @@ */ package org.sleuthkit.autopsy.commonfilesearch; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.logging.Level; -import org.apache.commons.lang.StringUtils; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; @@ -31,7 +28,6 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.commonfilesearch.FileInstanceNode.CommonFilePropertyType; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -110,7 +106,10 @@ public class Md5Node extends DisplayableItemNode { //fillPropertyMap(map, this); final String NO_DESCR = Bundle.Md5Node_createSheet_noDescription(); - + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, "")); + //sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), 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())); // for (Md5Node.CommonFileParentPropertyType propType : Md5Node.CommonFileParentPropertyType.values()) { From 29ff5dc826c5f02518df69eb249648246df3a084 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 26 Jun 2018 10:49:01 -0700 Subject: [PATCH 07/37] Removed commented out code. --- .../commonfilesearch/FileInstanceNode.java | 48 ------------------ .../commonfilesearch/InstanceCountNode.java | 50 +------------------ .../autopsy/commonfilesearch/Md5Node.java | 47 ----------------- 3 files changed, 1 insertion(+), 144 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java index 86d32bd0a2..07c87035e1 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java @@ -73,13 +73,9 @@ public class FileInstanceNode extends FileNode { sheet.put(sheetSet); } - //Map map = new LinkedHashMap<>(); - //fillPropertyMap(map, this); - final String NO_DESCR = Bundle.FileInstanceNode_createSheet_noDescription(); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName())); - //sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, "")); 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())); @@ -89,48 +85,4 @@ public class FileInstanceNode extends FileNode { 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.Files.toString(), node.getContent().getName()); -// map.put(CommonFilePropertyType.Instances.toString(), ""); -// 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())); -// map.put(CommonFilePropertyType.Tags.toString(), ""); -// } -// -// /** -// * Encapsulates the columns to be displayed for reach row represented by an -// * instance of this object. -// */ -// public enum CommonFilePropertyType { -// -// Files(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl()), -// Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()), -// ParentPath(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl()), -// HashsetHits(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl()), -// DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), -// MimeType(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl()), -// Tags(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1()); -// -// 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 b61e439939..26eaac3ec2 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -100,61 +100,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(); 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())); - -// for (InstanceCountNode.InstanceCountNodePropertyType propType : InstanceCountNode.InstanceCountNodePropertyType.values()) { -// final String propString = propType.toString(); -// 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.Files.toString(), ""); -// map.put(InstanceCountNodePropertyType.Instances.toString(), node.getInstanceCount()); -// //map.put(CommonFilePropertyType.ParentPath.toString(), ""); -// //map.put(CommonFilePropertyType.HashsetHits.toString(), ""); -// //map.put(CommonFilePropertyType.DataSource.toString(), ""); -// //map.put(CommonFilePropertyType.MimeType.toString(), ""); -// //map.put(CommonFilePropertyType.Tags.toString(), ""); -// -// } -// -// /** -// * Fields which will appear in the tree table. -// */ -// public enum InstanceCountNodePropertyType{ -// -// //Files(Bundle.InstanceCountNodePropertyType_filesColLbl1()), -// Instances(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl()); -// //ParentPath(Bundle.InstanceCountNodePropertyType_parentPathColLbl1()); -// -// 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 14dfd7388a..83f94f5c9b 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -101,42 +101,16 @@ public class Md5Node extends DisplayableItemNode { sheetSet = Sheet.createPropertiesSet(); sheet.put(sheetSet); } -// -// Map map = new LinkedHashMap<>(); - //fillPropertyMap(map, this); final String NO_DESCR = Bundle.Md5Node_createSheet_noDescription(); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, "")); - //sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), 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())); - -// for (Md5Node.CommonFileParentPropertyType propType : Md5Node.CommonFileParentPropertyType.values()) { -// final String propString = propType.toString(); -// sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); -// } 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.Files.toString(), ""); -// //map.put(CommonFileParentPropertyType.Instances.toString(), ""); -// //map.put(CommonFileParentPropertyType.ParentPath.toString(), ""); -// //map.put(CommonFileParentPropertyType.HashsetHits.toString(), ""); -// map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); -// //map.put(CommonFileParentPropertyType.MimeType.toString(), ""); -// //map.put(CommonFileParentPropertyType.Tags.toString(), ""); -// } @Override public T accept(DisplayableItemNodeVisitor visitor) { @@ -185,25 +159,4 @@ public class Md5Node extends DisplayableItemNode { } } -// public enum CommonFileParentPropertyType { -// -// //Files(Bundle.CommonFileParentPropertyType_filesColLbl1()), -// //Instances(Bundle.CommonFilesParentPropertyType_instancesColLbl1()), -// //ParentPath(Bundle.CommonFileParentPropertyType_parentPathColLbl()), -// //HashSetHits(Bundle.CommonFileParentPropertyType_hashSetHitsColLbl1()), -// DataSource(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl()), -// //MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl1()) -// ; -// -// final private String displayString; -// -// private CommonFileParentPropertyType(String displayString) { -// this.displayString = displayString; -// } -// -// @Override -// public String toString() { -// return this.displayString; -// } -// } } \ No newline at end of file From da6ff8a04aab66e197560d68b9f7850e0a1b5d1a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 26 Jun 2018 14:22:06 -0400 Subject: [PATCH 08/37] Add object detection classifiers and python modules to shared config. --- .../configuration/SharedConfiguration.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 03b06c18e0..d0a0718f55 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -206,6 +206,8 @@ public class SharedConfiguration { uploadHashDbSettings(remoteFolder); uploadFileExporterSettings(remoteFolder); uploadCentralRepositorySettings(remoteFolder); + uploadObjectDetectionClassifiers(remoteFolder); + uploadPythonModules(remoteFolder); try { Files.deleteIfExists(uploadInProgress.toPath()); @@ -271,6 +273,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 @@ -511,6 +515,68 @@ 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()}); + + File remoteSubFolder = new File(remoteBaseFolder, localFolder.getName()); + if(! remoteSubFolder.exists()) { + return; + } + + if(localFolder.exists()) { + try { + FileUtils.deleteDirectory(localFolder); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Failed to delete local folder {0}", localFolder.getAbsolutePath()); + throw new SharedConfigurationException(String.format("Failed to delete local folder {0}", localFolder.getAbsolutePath()), ex); + } + } + + 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 +894,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 object detection classfiers"); + 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 object detection classfiers"); + 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 From d835d0a6883fc165c575eb612c7cd9eeb97afeb4 Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Tue, 26 Jun 2018 13:38:27 -0600 Subject: [PATCH 09/37] default column widths adjusted a bit --- .../CommonFilesSearchResultsViewerTable.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java index 85d95d8b99..5bd87524ce 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java @@ -42,12 +42,12 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { static { Map map = new HashMap<>(); - map.put(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), 235); - map.put(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), 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); @@ -58,7 +58,7 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { "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" }) From e2893dd6f92a7010cf23b26476338eb452755c63 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 26 Jun 2018 15:49:51 -0400 Subject: [PATCH 10/37] 3956 add tag and comment option to the replace tag sub menu --- .../ReplaceBlackboardArtifactTagAction.java | 5 ++-- .../actions/ReplaceContentTagAction.java | 4 +-- .../autopsy/actions/ReplaceTagAction.java | 25 ++++++++++++++----- 3 files changed, 24 insertions(+), 10 deletions(-) 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); } } } From 4b044a0a9b4a0e1592feb42821a9c0f4a1cea65b Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Tue, 26 Jun 2018 14:26:46 -0600 Subject: [PATCH 11/37] verbiage change --- .../org/sleuthkit/autopsy/commonfilesearch/Bundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From c2226a30831e4d6659d9718c1f953431404e9a8f Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Tue, 26 Jun 2018 15:31:01 -0600 Subject: [PATCH 12/37] added multi-file icons --- .../sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java | 1 + Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java | 1 + 2 files changed, 2 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index 26eaac3ec2..987e6faa08 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -58,6 +58,7 @@ final public class InstanceCountNode extends DisplayableItemNode { this.metadataList = md5Metadata; this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), md5Metadata.size())); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } /** diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java index 83f94f5c9b..0b10d73e30 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -66,6 +66,7 @@ public class Md5Node extends DisplayableItemNode { this.md5Hash = data.getMd5(); this.setDisplayName(String.format(Bundle.Md5Node_Md5Node_format(), this.md5Hash)); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } /** From 6ee018e802a296a0b574e19eee7ad29d3680811d Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Tue, 26 Jun 2018 15:54:45 -0600 Subject: [PATCH 13/37] polish --- .../commonfilesearch/CommonFilesSearchResultsViewerTable.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java index 5bd87524ce..733b40db80 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java @@ -71,8 +71,8 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { TableColumn column = columnsEnumerator.nextElement(); - final Object headerValue = column.getHeaderValue(); - final Integer get = COLUMN_WIDTHS.get(headerValue); + final String headerValue = column.getHeaderValue().toString(); + final Integer get = COLUMN_WIDTHS.get((String)headerValue); column.setPreferredWidth(get); } From d94d684fbf2bcf21fea96d2e0fb9c7adccdf1435 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 27 Jun 2018 08:16:46 -0400 Subject: [PATCH 14/37] Add logging if the folder is missing. --- .../autopsy/experimental/configuration/SharedConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index d0a0718f55..388690616f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -559,6 +559,7 @@ public class SharedConfiguration { File remoteSubFolder = new File(remoteBaseFolder, localFolder.getName()); if(! remoteSubFolder.exists()) { + logger.log(Level.INFO, "{0} does not exist", remoteSubFolder.getAbsolutePath()); return; } From 293e2639150ca96551652030a6b3de06d6b7f109 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 27 Jun 2018 08:43:13 -0400 Subject: [PATCH 15/37] Fix cut and paste error --- .../experimental/configuration/SharedConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 388690616f..6ed43057f5 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -929,7 +929,7 @@ public class SharedConfiguration { * @throws SharedConfigurationException */ private void uploadPythonModules(File remoteFolder) throws SharedConfigurationException { - publishTask("Uploading object detection classfiers"); + publishTask("Uploading python modules"); File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); copyLocalFolderToRemoteFolder(classifiersFolder, remoteFolder); } @@ -942,7 +942,7 @@ public class SharedConfiguration { * @throws SharedConfigurationException */ private void downloadPythonModules(File remoteFolder) throws SharedConfigurationException { - publishTask("Downloading object detection classfiers"); + publishTask("Downloading python modules"); File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); copyRemoteFolderToLocalFolder(classifiersFolder, remoteFolder); } From 7bfa2647804003368cad0b50d67d83ee3d29beb8 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 27 Jun 2018 12:19:35 -0400 Subject: [PATCH 16/37] 3979 scale up forward and back buttons for tree navigation --- .../DirectoryTreeTopComponent.form | 33 ++++++++--------- .../DirectoryTreeTopComponent.java | 35 +++++++++--------- .../btn_step_back_disabled_large.png | Bin 0 -> 1505 bytes .../btn_step_back_hover_large.png | Bin 0 -> 2108 bytes .../directorytree/btn_step_back_large.png | Bin 0 -> 2105 bytes .../btn_step_forward_disabled_large.png | Bin 0 -> 1468 bytes .../btn_step_forward_hover_large.png | Bin 0 -> 2068 bytes .../directorytree/btn_step_forward_large.png | Bin 0 -> 2078 bytes 8 files changed, 33 insertions(+), 35 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index 48dfad0bb6..a59e37665d 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -18,11 +18,10 @@ + - - - - + + @@ -34,7 +33,7 @@ - + @@ -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..90a239faff 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(44, 44)); + 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(44, 44)); + 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, 27, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(showRejectedCheckBox) .addComponent(groupByDatasourceCheckBox)) @@ -244,7 +243,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addGroup(layout.createSequentialGroup() .addGap(5, 5, 5) .addComponent(showRejectedCheckBox) @@ -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) + .addComponent(forwardButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(backButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 830, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents 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 0000000000000000000000000000000000000000..6a496e490b10c19572bc7d5cc10142636f8bcd6f GIT binary patch literal 1505 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6db9bfe2f88db1bf5f3048269a6f562c6b6b9fc1 GIT binary patch literal 1468 zcmV;t1w;CYP)i^JRl|%nqFFiNSDMe8fb#e$?pg?K_u`SXf zMRK`2J=D9PXhoJKp~?b=A!)UI`{vEe+ZA11T`@D`RD+0c58#HG|6=A}t+l^2P4mza^pt5&eB}anTe-krPp;lu}`5CmaM|%*Nv5aJI2AC=d@y;e>h|9dI25aK%_#1#?E z06-m@2tEq^O&(#x1Ykl$UpeOz08qzZak$1IYYyZ$cFw8286_fEYf)7d>biz821JB7 zj*%n@k|cqY63)4Ub?cm?y@3iLV2r`-?JYii`h>c!A%sAdWjH%K!}I6QA*DP>_sk4t zc7C(!kZ?CmgVkyUYb~UdsH!UHHp?=kX$mPNthIY9;UGpmOGE@ICA8KMLcm&!vMgb( zMb~xES|dr4Ll_OuQiqh0(P)IpWP+~if*w87d7eWEf$4OL$z-w@vquA(Wf=fqxm==Y z8UTQ`7RHz$32QB)D0(up4;bwi`(4HF*&C0?kWz;CjWKY}K`9029NM-;Q51pMEXyE- zfOBr^bNV3H7=3DI^7Eo7f>H`y*MXT)mL-@u=(X!Q)OC&P>uWgY5JeH9DB3H6YM0K_ z??A)w3n4I{&ygev;yA`~xx{Ll(}qtu@j#eH7r<(0kBL)1W8{v~3Hm^@DKreXKA39@x-e`0C&j%owxP0}mzlXfk=W zJb=$FJWRa)y#7W!Tf3U{tw8;UNs{2%vuD92Ip?-MU|WUo>uRlo%i3lMhY6(~tCO?Y z3{eyXpq~TioI}@ju+~1P5}rY&6p|#tWHNaa=mSQ#P4vkz9*?&!CZ!Cu($`UIErbw( z86gDXIL353#e6;wes$CHhh1qejrPCnON(dH{~ue`j zO`WOhI^5pgq9}^BqVDCOlnP9qot>?<+@spOO}qBYHcf-7s<2orP?lww>mwkg44O>S z6k6*w0NWCCqkG!-%yf5m7j$Wi3B}$^CP@-xS%&$14y6=~F*}`BH@(02;f|THTCK2L zF4w}{XL357B282H+nA@+(4#qg?>~((p?wY$XEvKbYrUq)eQ#s-21-QGT89iKB1BOX zjwjw!@2bgRTRsY-{>!7$2wm5qX&Mj_#^W)vEJK!M53+k7CU@@j+kgrokftf(I1U>1 z#>E%$+Y7fcjh?|DS(s@Ibk3 zTWZ_(rfHhrs;YWZ6vYAn)ZN|PsqgRmj5fxY4{h82zE~`NeEx+wv8vsycSzev` zHXMLu=F&O$v1yujWm&#?`}XbYmoHzw1HhxvNT|!p%O6e*leWAf!iyI#nC^Gf1bkRC4U(#PAThDrHAj?Owh1+G}r#?C$Pr-2Bo2&b8M6(^|h&O8qws z!+&pYZ+`*+tu>fsSpWb+$ju_vI;FKPXsz!^DZ7SY96fvXEa><9Jwk|Jb8~YbgxoYx zqLgx^l>Z3BaG7(yqP5OhmQ^5x{0BgrnWh;7y(zF-YiO+vDW&n_(w#62w++MSQA+m! zw3(Fh#(~DP5`t7J1ps<98r>y?d;w4Z0H(DbJJ~Ej8DlUEBVq&qpp=4?a;8|x8ljYe zQc3`10o>ABTN6NkCS^(~aL%E%hG7`VRL&eL0-A{7NGVwiRBJs`>KSA3eIM`My~E+* zA-dfz48uURTE)`R5|);h;5ZJ#FoaTSCP_tjegHSL)&u}pGSGEdFbo5}@8kLN=Xm<` zDL#Gr1k19pv9W>e?QJ-Y1IKYLZhGydl#1}E==)geXE{0N9PM@+$H&L;eIH6Gv|23~ zh5O#w|2XY*RNm0vMfA& z_zP29V8 z59xFozVBZ<^}hr(>SmQn1=(yCp67vc9&cn>7F^dwx7&@2jdPAtsf0?U5@&GQ-j@Q6 z#5yhYhGE3rCt@uM7PULZ7`C>yu&}Uzy}dm&n@t=aA0rGybB-Vg5Cj1jW3hb!fa|*G^?G>o<_##Nu^X7C8OtVWp^+^`fXD19shcRx zr>Cdb+1bI)&JJ3w7F^ez=*fm*AeBnRjOKDVWHK3~(`isj<4>(M`u#pmPENo%$K2c; z?%ur%LI{#%U1W5uGhGyS^u66~V{dN{olXaZLIH(B0hH1)i4a0y7zRw!gl*eMr_=Fp z5*^4iO=L2eSO#Ceenq$2ooEDFYfwrj*qmT=n&BFT0mpG-0F1FQjV8x4##o&4=sF<; zl+tLHt9wG2OMb≶d3hPj%gdMcXi6z8%R;Z$!`|K=_V@R3a&i(^ zzSbI2N~F_ilu9M6t*v2ma}$o^KuS4Q@iQ};9FWuLG%U+PI-Q=lhcN~r1O|fvJkN_i zqeqd5N!zwjsZ>y}*HN$6Q7V<7wZ?EboRD48o+f}^w5McG7ec`I{RxnS5LlLle!q_w zFJ9p7+qd}g~Zj)6-KN9UbA-t5;|?n-D_8Rc~1qY}-b)TE)uB3hMPb^7(umZ`vRo+0#@;e`Y9Q zj3EdDeE9GIySuw+Hk;`6ddTPVux%TJ5SXTkN~MC;)m3b6ZX%n_VmKU*`Qyw($t<&I zG-L}QaCCHp{r!CmheMdA8T+hl+qiY>7S`9-ap%sR`1$EFCa*O(UsX(3&5S|_oS&bg z)9E0e&m))1#f7u5uz*IRfsKs~!uT=kq9)N^w^z z7K>P1TtuVMK&@7TWm%9?#*B{m+UFpSO^hM zLt5*&K}4F2=7L#Q0w@Qde(5EdQVO2u!FAoKZ{=pm(6nB!l+s%3bEQ-`@uuWwn7yhX zeg>qJ5<&&Oqobog4h{~^BcNYy7$XHBwbriIy3INN;QRic2L}g#JbCiu0|0GV z7G)hy zG7=$x5qL7#7{_*hSM9~pE!%O}*o?%qWXX1S)j9Rnd){+SwbsMOq ze`~G(6-CiM9zA;W?(jH2j~ zVHlqb!}t$yVy0=DJGzLm=>;w_J{w~g28BWaK#xYFO~WwSLWnAWxd;kcYlIM(riqji zt#xipYfYM_D5Wm6OXK#15EzDmX_`U^Q39?gr5phBYtR5tN|7W9N-0d!oS0{k`3%Fz z$7-$JG)>Jjh+dSZQc8j#;PCK}w{PFl>-CUQvbMIy)vH%oUth;{U7{#LDK*#JWhaYIS+Y|}~?%uu2!-o&?JdbJFn+M%7DyeLt<~Zl(xsoKI z)9LX0@4pjO3`Yy`1tW7 zrBaE}Xhf&e;oZA;{PfdLj7B3?R#v!t`EqXbB6h|lZ*D}hXEOA@`|dkD&%?4TUcGun zzuza0W2)6EzVFlP_1NFvr%))+Y&N-b=MIkJ;QK!3*_}o-b9Qb-v%x|LR#sNFFs#2htQ>KGe!tIPFyP?e04XKSW|QshZA{Z7j^mk*W-@rL;2Va4)|yVI!!N)5 z!rtBGYDJc{RxUP%iI7lfcN=M7GD3wYGA?S9y zeE9Hze!o96n8yE`8TGSxlI8Vsxtw#)G|ibZCZ)u(ti1Faejys&D zDFC%vjeGa*vAVj-$;nAB^wSl(VHl)o%3v_S_kF@JBlZze~l2|XdQripFad4QaE`M%F!FvzuXI2_<=afdrtfE*{Ns;5Xti2AefpF)Z{7glx-LnQ zVB0puVv$;{mLplIR1iW;6eZ`iA~Vapi?j>llUkNV6h#~!9`fwjGhV-bO_C(Jgk{fE zt5q6}2F+$OF9F9k3% zC%zUzDHTRh^ezm;yJwOW7s#*T{5@0ntylpluSr@>&*dG_qt`|N?`KjRmZb^9smFU07*qo IM6N<$f(!@(i2wiq literal 0 HcmV?d00001 From 814eea4b3a55be1ad10720cc2e4c57475481ad4d Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 27 Jun 2018 12:39:08 -0400 Subject: [PATCH 17/37] Don't share the group datasources selection --- .../experimental/configuration/SharedConfiguration.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 03b06c18e0..8eabe9babc 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; /** @@ -348,6 +349,7 @@ public class SharedConfiguration { fileIngestThreads = UserPreferences.numberOfFileIngestThreads(); hideSlackFilesInDataSource = UserPreferences.hideSlackFilesInDataSourcesTree(); hideSlackFilesInViews = UserPreferences.hideSlackFilesInViewsTree(); + groupDatasources = UserPreferences.groupItemsInTreeByDatasource(); } /** @@ -364,6 +366,7 @@ public class SharedConfiguration { UserPreferences.setNumberOfFileIngestThreads(fileIngestThreads); UserPreferences.setHideSlackFilesInDataSourcesTree(hideSlackFilesInDataSource); UserPreferences.setHideSlackFilesInViewsTree(hideSlackFilesInViews); + UserPreferences.setGroupItemsInTreeByDatasource(groupDatasources); } /** From b1e456a6884a485d5748547b5fff83df9859089a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 27 Jun 2018 13:03:36 -0400 Subject: [PATCH 18/37] Clean out the local folder even if the remote folder does not exist. --- .../configuration/SharedConfiguration.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 6ed43057f5..e8048b5cc6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -557,21 +557,23 @@ public class SharedConfiguration { 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; } - if(localFolder.exists()) { - try { - FileUtils.deleteDirectory(localFolder); - } catch (IOException ex) { - logger.log(Level.SEVERE, "Failed to delete local folder {0}", localFolder.getAbsolutePath()); - throw new SharedConfigurationException(String.format("Failed to delete local folder {0}", localFolder.getAbsolutePath()), ex); - } - } - try { FileUtils.copyDirectory(remoteSubFolder, localFolder); } catch (IOException ex) { From 87478909a860a2684d95143c4f4e27c682c7beca Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 27 Jun 2018 13:09:56 -0400 Subject: [PATCH 19/37] Update jobs in snapshot when they are prioritized/deprioritized. --- .../autoingest/AutoIngestMonitor.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) 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. */ From 23483254618cc98e083c4693c741be0a59d189a2 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 27 Jun 2018 13:16:23 -0400 Subject: [PATCH 20/37] 3977: NPE when downloading shared config Don't try to rebuild the tree when there is no open case. --- .../directorytree/DirectoryTreeTopComponent.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 7e1ba36b67..03c89f69a4 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -800,10 +800,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(); From 0a06675fdcebce079e25e2bc3688a0b1754f5e72 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 27 Jun 2018 13:23:50 -0400 Subject: [PATCH 21/37] 3974 - reword default profile to include (Not Unallocated Space) --- .../autopsy/modules/interestingitems/FilesSetsManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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("\\", ":", "*", "?", "\"", "<", ">"))); From e7ddafca15eba714db1822069d4e156ebe7b7782 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Wed, 27 Jun 2018 10:26:43 -0700 Subject: [PATCH 22/37] Don't enabled common files search unless there's more than one datasource --- .../CommonFilesSearchAction.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java index e470fd16bc..2100bbefbb 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); } - + @NbBundle.Messages({ + "CommonFilesSearchAction.exception=Unexpected Exception checking for common files search enabled."}) @Override public boolean isEnabled(){ - return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited(); + boolean shouldBeEnabled = false; + try { + shouldBeEnabled = Case.isCaseOpen() && Case.getCurrentCase().getDataSources().size() > 1 && Installer.isJavaFxInited(); + } catch(TskCoreException ex) { + logger.log(Level.INFO, Bundle.CommonFilesSearchAction_exception(), ex); + } + return super.isEnabled() && shouldBeEnabled; } public static synchronized CommonFilesSearchAction getDefault() { From 0a9b4312c008098c5872984c459a40072ce3edb8 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Wed, 27 Jun 2018 10:41:33 -0700 Subject: [PATCH 23/37] Don't enabled datasources combobox / within datasource radio unless 3 or more datasources exist. --- .../commonfilesearch/CommonFilesPanel.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 From 54c8cbd9a79f90bab6a1f0f45f79f35b9d6be66b Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 27 Jun 2018 14:19:28 -0400 Subject: [PATCH 24/37] 3979 resize forward and back arrows to medium size --- .../DirectoryTreeTopComponent.form | 23 ++++++++---------- .../DirectoryTreeTopComponent.java | 20 +++++++-------- .../btn_step_back_disabled_large.png | Bin 1505 -> 1098 bytes .../btn_step_back_hover_large.png | Bin 2108 -> 1426 bytes .../directorytree/btn_step_back_large.png | Bin 2105 -> 1426 bytes .../btn_step_forward_disabled_large.png | Bin 1468 -> 1032 bytes .../btn_step_forward_hover_large.png | Bin 2068 -> 1385 bytes .../directorytree/btn_step_forward_large.png | Bin 2078 -> 1409 bytes 8 files changed, 19 insertions(+), 24 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index a59e37665d..ce50160e22 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -21,7 +21,7 @@ - + @@ -33,23 +33,20 @@ - + + - - - - - - - + + + - - + + @@ -91,7 +88,7 @@ - + @@ -124,7 +121,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 90a239faff..7c6801e05b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -192,7 +192,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat 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(44, 44)); + 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) { @@ -208,7 +208,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat 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(44, 44)); + 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) { @@ -234,7 +234,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat .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, 27, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 51, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(showRejectedCheckBox) .addComponent(groupByDatasourceCheckBox)) @@ -243,19 +243,17 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGap(5, 5, 5) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) .addComponent(showRejectedCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(groupByDatasourceCheckBox)) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(forwardButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(backButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .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, 830, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 839, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents 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 index 6a496e490b10c19572bc7d5cc10142636f8bcd6f..53e6bb25d4c96e60aa38c94a436729cd7deb5280 100644 GIT binary patch delta 1063 zcmV+?1larG3(5!~iBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPPme-aQ0 zv%Kj1000BmNkl|kaT>LiKm{RHJ%EG+7fu|jJOC00ka)1Z z1#iJ4^uVFYp-o)Hi5;+ay~}~vSDxqn%sHPB z(bq&Y#CcZ(m1e>#~4w(ZTS zuq&nT{QQjBYzD1$6ZNTv4=6CkoaBHI0?XwRv)K%b#RA3{NGZV>Ll6WAg5cypthF%4 z{7bguXhTXw2oztPmo_k9FG0AtL~A={j{iI=q&p64NoB5=-I9o$L6`h)GKwHEz; z9{`%Vujhx2io$hWIF8erL$2#02m&z1jwa%VWf+Dbgb=8z3R#vlTw7})rNm$`z-TmT zh1gmPtu?gP?<26Ce*^mzXssco#P#(xUS3|XTrQ!t1`#zvl;=6d<1wU^2a&b~RXf|- z3HOn()`BqxAq0lQAsAyw(-cZ67-O(ruThpIhzR|DAFk^jE$QvxS{u=^jkV4PLC_ph z_XT4NilV@5HiHlXp64AsXFsrm9B89pXNoLKxUP%wcnoVTe^#qigHTZv4H8|nfOS#B zdYB|4TwY!xj$`<~4qJLQ$$lg2gg{xAPfDpjilX?u7MxR(smb*DA zHV!07^1Be?eK#rPPsW%}gb@6k7n4nca{yEXfB|5SkB?v7-QE2f1i=qNh%W*7XTF_j zYwe#D$1x>If`^BPXfm06Iv5NtrIaBNNdRZL9$9Ni2%#OvQHY`l_xJZ~gT(*7Xzw@M hJZbO0vj73W{tZfjxYkNbVJ-jw002ovPDHLkV1fxR;oATJ delta 1474 zcmV;z1wH!82;mDMiBL{Q4GJ0x0000DNk~Le0000i0000i2nGNE03L)|0g)jye-AMU zE8WdJ000GUNkl_!QS?qJ^mQQ^RYOIv{nVB!TV7muDmQ7!S8UT0+z}!=j-1sgUJcZ#Fuyc;(fBSti+jXH+ zuGuR(?>&VO_*8Jr4CfqHs}-uM!g{^Fx{20Ek_2g*LMe3;Z~=^PwVf;lYb|E88K%=I z7K;VE_lV;d&!0a>9LF%mz&Up`%PrtjHl-Atb66}ED2f8}`5epT63#hz?|W>YB8P6# z(WynzGz}(`31+hys;X+4e+?nPT6-@R*<<(^uw7>7^Ery5zyz|1GhLT~HWyTB6>m>J&tRl(A!43o(Oe@)Z0G7!MV7^G>6 z(P)G$%b>M}wf10!yX|;e(H(Rl@zD8vj@fL6#bSY`X?n~mrH~{EhQlGAJb8j7Nm_Zx z=WR)BABLWW4*~9+L(?=UiUQN=v}IOn-9C)4Ym9-`8s7VzmL~EcqMb$AV|cS|ZUPt0 zmSu^eC{Pqd%dFPAf4!kN&gJDLmdho+ef!qh+BU#48d;VhO;Zd8gBw^x%SSQ1TrM%4 zPJ7lRQjm*Fc9qeP=5ZU~G)-Zxg;J_5%uVucFdRbIS-k?tTHDJ90=64&WQg0|ul0?a zb~lk&mSubO>%G5Q(FvFk!VR$92NQ|x^4)HsbtQFB_}LdJf93Z$jxih#_ZHE%Xn9+@ z8w>`>vJ6R*Y{jsdrw?LS{!5Ysd7k6)@)A{5wdZYu61hl0?s~YvV1P7D5k=9~<2JGB zLp-qyIEtbc#CpAMlb#fuQmP$^JkJqD(ZO+Y8{nfD?hc_+3d7+LA%vFc&4P8#wY#t= z4=Klq8+h+`e>zF`oI8gfYi$d_7}H}~z&YnomL;mHLRpp=jYc>-JA*N1Ywq6VAbS}; zq&3P#B-n%yuvjd5Obct`!_15{O?PVgw&L9E5qg*1?243KrsZY=S~_FXG$_lmmC1NK zzR?)=In?IyN9Ez}aZ0Jy^oa>p&tpQ*@pU;vc`AchkW{k-JJOvP#ndF3rTG+nMClo>;09UUJK7al^xwyFa z^Y!c34FHa!=qF>$D`rj}!z%;tJvrx6W(Flmf5NNPDgdAlA3pr>=FOYmvMhTW$MFx$ z{2dWx02BcKKzIp&15go>)h}MW2w%T`1ps{f`0-x=f8=@ocakLeHN%FkX#U?dAu?p< zV2o*)*-@INl%^?Gs}+}JDZn1nn;6050YE={_AFRysj4d9`IEMs^eBMS7ErXYKQ!R( c7-)z80D0E03jr#h5&&eZlXa7H^n$Oj&HlOJAG(&YdZnVLwY33UXS+7cfRwT zb7q7w1|bA@@7~=LkH>$PQl1t?#1?_c6x&3&0rT7-Nu9VvHFIy%2&}EEc_ESr(q>(d~8_CVj@3fBygkK_HDW zLq%Lna=9FpN`(Ul4&eL#SN#M*(8mD70+3RowWe09asB#r zu3WjovuDp39UbNT`SVOqPvd#sH!~3ZA%ys18e*{+@87@Y;>C+BEG)3Oxyj_@B!Fwo_v=f6 z@B2G4pp?QG!}I6QxpwUuH*VZOYfUmK&*zz)o#o)cgLt0zRSkrD^rfJexLHkJKOblv1h0)YKIF_wOejkN5fiB7o4r z){x@n&6{Yg$>;Mpe~yDv3avG-U%zH?agmQ7Kho)R&{{`qN+~Iq%bYxUlCx*el1wHe zfZG@d=^qrKF@|cjN~6(WY;25FDuq%ia*|9YDHe-lv)OIJ^E{7syUl|K4>)t?OcZr5 zfE~-Q7l789@$qpcCMKe_a9v7?Wm$c!^_pc_?Af!2nVA`4f3X;@>uv+kmx4hNhR=r& zA4Ukl($W&$ZWrJ8qe+>~W+@hn6pO`Zxb(7@&*zz#n1}#`YhPqwumK%9bcjqQL#x%| z)vH&u+wG`lq|<4R967>?6DP1NYx{}Ab!j%6kv@8dR0vi9oI7{!SKGFK3QMW?$=oxHE!Lyh2uDke~pcywcfVrU@IP~3*ckkX&tyUv_d~Fu=iEyVJeQqkY+ijFm%+Jr0%jJ0Z@FA@OolX}m%W{MeHehyXbD@;d zwrzWijEpcfH6^#0_}@(o{FYnaVqk4o?x%qH2V247nxTn2Q~&?~07*qoM6N<$f_7A^ Axc~qF delta 2081 zcmV++2;TRS3%n2^iBL{Q4GJ0x0000DNk~Le0000i0000i2nGNE03L)|0g)jye-AM+ z1K)L`000NcNklj4_A^ z08gJjZ8(negHq}rBJxl~R)DX7f4@$+xFb_a4T0Da5q|pVC#zbmRz1(VV_DW+rPPX2 zswN_Kelr(}Hp@toge=Rj0BW_`SFY=RueJWcb=`m3wtd}k9HX_?N~sG)EFwfv#9%NW zNfNODlu`>?>%S|dzPD|A#c>=Q$3bh2h;Y#?XIX}b{EuM~sfoznlu~zcf3PBgF$QDI z7a@rphxO@$5>OWrTPcN7ssKAHU=cxUjZ&(3FCwIAN|Ge!8ZQTIjQI<2nx@JaW95Sx zW5}}X+>97wNYj)wO%V|+%Q`m!`FMu_XIUnI8vkOJfsHXJrEnaF;c!U1-R6%!{$OWk zha^d;R;x6dOnK7Ra&f7ZG<_dLWL0-V1Y zm&0k25D`4jV>leLxw*;a<|Z2(8|>`tAR^4o&EdK(j^nVjv_!Mn#Ih{XG@VIq+e9$^^r^5si5e>OJQ+}vbuZx5|CzVD;8#&H~KwHmEfi-!*%vb3~>)|xnu|9_G= z4RAg^qtS@{{e9Ng*ZJj_U)bN@C(APC=jSPx%P6I&)oR?haf7wBHE!R&O{G%dJZSnm1b6&i7!JmKriPoBOxr}Ywf0WB*8jS`wZ{FnFZ@=aG z_3KnB6_iqx%jF^$Og5M#Nv7V+nKe9(;UEa;cDrnBZ1DK;WBUC*0F_FGa=DB#2CX%7 zb8}RyRm$ZuQ54Z`w@H%Zh^6IS(6TIS+eT}BVwN}rc)ZvqTeEH3BuT<=zx`G?LciZH znDu=h+qTKFe~jU9$g5Ycc>DG(KmYu5VQYDJ%ZU&XN~IECfBiMLZrx&ab(L$^t`)gs zlHnZNp~f-!cYN?H%Xs_tEx-QyE4#b90QkO7rBcDRZIn_)cJ253ynp|m!C*iT1jKPn znx=<{$Zc_Tb(MpI1Ipzx^?Dtp)DhX7=n~Ty&d=%hfBSSg9gHzltJQ+pJehsU);NyC z+}s>mYbupW0d}(4qy4>uqUcJiN+8VuHuV5_)n-8v(It1XP!*yMj zmzP;te_5f~Y!+iq7W|ofc>H2{@NP63Jb3Vc?d@$koz9^)n*-0AhwuB$&COw1Rzbk{ z&1G4JWm(kgb(+m4moHx~#`|oB<2VMO*=%y<$`xGKC5j@t-EJ|Zd38IE!<8#nxOwv? z_wL&!G8&Bt!?2ji@tepQK2&s*{yQxn8)G;)I6y>bG#b=u zH5|twO;h&v_81O_hzO(6h<3Y8r_*73dz(9V?(p4r-%+d87>!0#^*MRHanO^_btZ<# zf3=+Z=luLUtE;Pp7I$`b2*a>o?8Ao-^m;u;qY(;mBP}D zF$6(?QVQF)34(yvuU}KE)wp~2?x8}Oel%kWtPyfKEsV1U+|Mx(*}{Cv?2c6N3O z$H=A8?RJZzn`N1F<^X@vR86+eG)*a$O0-%n9LK@;ecru$N3Ykzwrv&`7N}Gzh4r0< z;qjt7v2mP5SCa)X7!0s1i>0Nd!aZ^ub6uB9moBlqyo}cRNUk{RHKqZUNw=6~e>IS% zDNz(*Sr&~(1K;<#apMMAmf?9Gl}ZK6vWTK+=1rO-fIodNkSaQpS>qB-sxgvOmCdw z^KL$8S;lBIVmKTg+Z@gd8bASre_?nKMbUqfBxy%cH1poUaRoC=b2!Z{ii8L7{{8#D z<2dVD>jkZK)fn@YWm!fkC11ibjx(+=xjj|^VCc+qXOTK{e~o3SxQ*|vSpvaE)P zcwYiwV~k{3<^xEnR8phS$N=)=k3X)huC6}vJnx}x+iN1SC?aJ*1K9=PcNHKNkr9ws z3kwUT)9C>4{Q2_^@UKdxvgtTZRcq~ui2dnD=7QKoDP^?QvDP{j&-0{IDiMaE3W6X9 zcJWVQ4l``>8Mdm`s!7vS!Z6InzLY6H`XYeK6i_npx@f@Tvu*whpGH41jl~qa00000 LNkvXXu0mjfI*1Fp 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 index 0de6c30b3114911ac9ba783b82d5a5fef6db112a..56810faa2f1aaebd00927d66e93bfb3a037505bb 100644 GIT binary patch delta 1394 zcmV-&1&#W-5RwZaiBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPPme-Z&Y zU$v_G000FaNklA&01!oy z5gUyszBdfRAeBn7ySq!f-R8@eFRZMraOu(|>h(Hd7@p{e5F%k?mVxO+yy$zzqbQ== z?XtPK$)iV)`0(Kaj^l9m?p-!EHcky+Y$w)$)*9oOfrV+eZJXh6$o>2GdGh25p6AhM zG_Wj-AP9a)j8PQ*!9Wy6f8Py(5Q1DT$Gdm$c>MS=+uPd!6bc2h*(}v+l}e?O*n1k7 zSq9`R1E+-`7H6l^;px+-Jb(TiAq4q+9>;NT9EWDJ$@=>GLM+6#5)90GK6^lnb~GCC z;K2jlymh(HTu3VwnY+_j!T5FV2i5UC7kMH}5Gh-o78JG`2{51@Not+)Fwzk;b-o~;l z&Ye5Q^71lTYx?~@uV26BDX`V45Z`U%uqo zvuD!-mzI_=O_Lx9f7sjGY-AKQmI5fpC=5%#D3rRIXpb%?c2B9zI~hd%k4xB%!3KTkn`uy zbK$}Tq?DMZISr9%n$x()Su%Tg5CoJ;CDzy1v2FXC>^L7q=8sWMwLI}FuF1x$CeE$5IVzG#nlCNLCGMP+pT^HANr^1}q+2P?~ zI2;ax6SH8JO5gVpLZFo5+O=!gw$0AY4x`Zs&-3W_`*gcqq?9z9O^U^0;xd}8th0^F zcPrt1-S#{W&-18MDpUI3zkg2@MN<%)o0}{xEm1C)f2Zko+HH7Z7Mxa!XGgEsV{L7X zR;xu21avwb!Z0M2O0l=MN3YjgxIIf;$BQ%}@jBOau`G*Qw{CIu>Qzdm5>iU)^*U>7 zYt!sm8+_heJxq>gps&6!)wRU4lexxyVT^G;u5_!Go-gu1!@I0^MdEW1X!QetJ zmz%x|7Nv2PokjKuA*QXlJic&#%VaXY?(gs4@jNe|PN&WJ`hEs4qZAMdzyOSU_wHS) z)oTAur_(>1rnxSJ$eiWdP-`7%t&fD`IASmuNO1G!&81eWRmtUYYf{Rb5W)iV85tLr zWd*iv`zWQT*XzbH6My>Px# delta 2078 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 LNkvXXu0mjfo`9q| delta 2041 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 delta 2051 zcmV+e2>kbf3!V@miBL{Q4GJ0x0000DNk~Le0000g0000i2nGNE027)jMv);ie-9TV zt=tlA000N8NklTb8Y{gL3r;Uf?jCjkis!vzxJXYHSuU$i%S zG182d%@xZ`W)m_JA%GEhGT0c$c7Ipx#nLU?aoE_5#I$6|c6Ze|_11gdb56C`+uPH8 z`%7!ByISj4TI+vnt^XB8(LWwNe|q%p^G!_Gb@_{2w${3;l)9OwX-`Vo`tipf!+yX2 zNeGdcwOS1!1VV^!>7-Cfxk-}T2*YqAilQy8b-7q9R)rA%1WwFCq41|TDuh65z3@1t zl$27+&wkvDqUe!f7@rKo_z!SmrfHfxx`?sq1uil^8)Fy-g+c*9k4B?Sf5R}^LWnAW zxd;kcYlIM(riqjit#xipYfYM_D5Wm6OXK#15EzDmX_`U^Q39?gr5phBYtR5tN|7W9 zN-0d!oS0{k`3%Fz$7-$JG)>Jjh+dSZQc8j#;PCK}w{PFl>-CUQvbMIy)vH%oUth;{ zU7{#LDK*#J7hp9dUYkO26M{d3l*)vAAfeLMfF~f5*(woCj5{HMVVY z{rYvbx3`I+h$KmnQWAzColb}S{e6nXB0D=f+`fIAVzEdN1pikJvTe^kGq?Vt+oP1i zwrw^yH&IH_YPIDiP308r_BUV;cxP19?ZuBB{#wBlVM6+iy z^uGJ4vypC`#$H{oklcs zc5Xzo!9oaDR#xyl58wAm)0DSw-=ej~aU3kmB93F)?KV%IJRy!_YPA}xtE*@1&9ZxD z>$8W(xsy%wIZI%gCWc{5tUOD(rBaDzv&q`p8i$9693LOke{Q#lFFs#26=>r4qJnqm&{|Q<5aXvaB3&fPTNvU@+j|-~cHl&1RGB?QKlcB#z^m zj%G4=uHYMnf!3N%r^7G5{KDSe9xq?MWHcI0IGPnEg+c+(^SE^B5=%=v_EtZ#;+1}npN;%=^EN21CjGMEf@WY1>Jb(V2e_pT0%F0U4+i^sN5J)L06biVm zi{m&*DJM!t%d#kyN(dq7cDsD|@PU56KQoxd|C<^0vv`u_^>VqKbI&x*nKCA&#Imfs z^c#OBTL?fDMMP0_rqcS7=udETd{7oR*REaT@#Dv|S}i)A&O|9UJ!V`XrX9_`Tb4z= zUdMG^f9mymUb~)$=uD1|JDjE|0JU0;d-v|Ky1L5A$w@Bs(-pd57^G>+U@*Ym0_VuTUsZtJS!1;|5o+UY)Qt7oyYK?s*=b=W+Aq&2uxC5CX?>I6XaOe}A8sFJIDb zw+X`#%d&DMa2$tnxy;tq7CSpTc%Da^rZZ)4e_<=afdrtfE* zfBfT*KWMdDJbn6f&qda9= zmX!;Cy-bSBi>x+WFe@FwSl%m(`(d+f7R4P;|m3+I`*Vnmno12^5xpRk7sl;F~AW4#m8ew5a7vX4@p&E?_tE;Q{f4+}l z7*r}1mX?;VZ96|bnJx?GbW(g3N0TJMFbu9;yGEnYz_x7w!Z0KZLxLb+{P1m_xu4B%7D)=Php01L5pc(D!|Yf5RbB6dlKLd=N#^0Km-hc-Cbuaz^xLoK!2Nh@waj zheI(K47x!O{OtSw(|*6-129{y)>k3%C%zUzDHTRh^ezm;yJwOW7s#*T{5S?`%*rIa6r z;iti1(0TUk+57B)<>h59KCkGnb_qZfi$(1?jtYWcLY}@QNv9)N{V!~UHcgXiwWLWQ=k0ae*wxD^i{{ReI5V+002ovPDHLkV1gVa07(D< From d276c41ebec9e803d97777f7a6326d36464cb48e Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 27 Jun 2018 14:31:34 -0400 Subject: [PATCH 25/37] 3979 recenter larger icons a bit --- .../DirectoryTreeTopComponent.form | 17 ++++++++++------- .../DirectoryTreeTopComponent.java | 14 ++++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index ce50160e22..732e34c20b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -33,20 +33,23 @@ - - + + - - - + + + + + + - - + + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 7c6801e05b..9ce642a987 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -243,17 +243,19 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() + .addGap(5, 5, 5) .addComponent(showRejectedCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(groupByDatasourceCheckBox)) - .addGroup(layout.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))) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .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, 839, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 843, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents From f6b73e794a3dd8b02ad03e393ff227897d0f44e5 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Jun 2018 16:59:02 -0400 Subject: [PATCH 26/37] Enterprise health monitor => health monitor --- .../datamodel/AbstractSqlEamDb.java | 6 +- .../ingestmodule/IngestModule.java | 6 +- ...eHealthMonitor.java => HealthMonitor.java} | 871 ++++++++++-------- .../healthmonitor/HealthMonitorDashboard.java | 32 +- .../autopsy/healthmonitor/Installer.java | 4 +- .../healthmonitor/TimingMetricGraphPanel.java | 2 +- .../healthmonitor/UserMetricGraphPanel.java | 2 +- .../hashdatabase/HashDbIngestModule.java | 8 +- .../autopsy/keywordsearch/Ingester.java | 6 +- .../autopsy/keywordsearch/Server.java | 14 +- 10 files changed, 509 insertions(+), 442 deletions(-) rename Core/src/org/sleuthkit/autopsy/healthmonitor/{EnterpriseHealthMonitor.java => HealthMonitor.java} (78%) 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/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/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); } /** From b3dffff410c2c1830de2528853acdb5e54185f7e Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 27 Jun 2018 17:28:21 -0400 Subject: [PATCH 27/37] Made the DataContentViewUtility class public. --- .../autopsy/corecomponents/DataContentViewerUtility.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) { From 120f3c1dc9ab1ae96a92fbd92ed6d7bcea2cfd14 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 27 Jun 2018 18:02:01 -0400 Subject: [PATCH 28/37] Make scrollpane start at the top. --- .../casemodule/AddImageWizardSelectDspVisual.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index ee14581bc7..e06610f952 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,11 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.weighty = 1; gridBagLayout.setConstraints(vertGlue, constraints); jPanel1.setLayout(gridBagLayout); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + jScrollPane1.getVerticalScrollBar().setValue(0); + } + }); } /** From 7035261bddf3fa296034b26a90f9a05e3a53c249 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 27 Jun 2018 18:03:51 -0400 Subject: [PATCH 29/37] Switch to lambda. --- .../autopsy/casemodule/AddImageWizardSelectDspVisual.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index e06610f952..056ff4e3ec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -173,10 +173,8 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.weighty = 1; gridBagLayout.setConstraints(vertGlue, constraints); jPanel1.setLayout(gridBagLayout); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - jScrollPane1.getVerticalScrollBar().setValue(0); - } + SwingUtilities.invokeLater(() -> { + jScrollPane1.getVerticalScrollBar().setValue(0); }); } From 57d9022c525d077a2b4443a5f1964e031b206df3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Jun 2018 18:30:59 -0400 Subject: [PATCH 30/37] Remove redundant data source name tool tips from ad hoc kws --- .../autopsy/keywordsearch/AdHocSearchPanel.java | 11 ----------- .../keywordsearch/DropdownListSearchPanel.java | 16 ---------------- .../DropdownSingleTermSearchPanel.java | 16 ---------------- 3 files changed, 43 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index c06394bf23..9b84a5c785 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -43,7 +43,6 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); protected int filesIndexed; private final Map dataSourceMap = new HashMap<>(); - private final List toolTipList = new ArrayList<>(); private List dataSources = new ArrayList<>(); private final DefaultListModel dataSourceListModel = new DefaultListModel<>(); @@ -153,14 +152,12 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { */ synchronized List getDataSourceArray() { List dsList = new ArrayList<>(); - toolTipList.clear(); Collections.sort(this.dataSources, (DataSource ds1, DataSource ds2) -> ds1.getName().compareTo(ds2.getName())); for (DataSource ds : dataSources) { String dsName = ds.getName(); File dataSourceFullName = new File(dsName); String displayName = dataSourceFullName.getName(); dataSourceMap.put(ds.getId(), displayName); - toolTipList.add(dsName); dsList.add(displayName); } return dsList; @@ -183,14 +180,6 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { Map getDataSourceMap() { return dataSourceMap; } - - /** - * Get a list of tooltip text for data source - * @return A list of tool tips - */ - List getDataSourceToolTipList() { - return toolTipList; - } /** * Get a list of DataSourceListModel diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java index 72a1cbab5a..17d5921e0e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java @@ -73,22 +73,6 @@ class DropdownListSearchPanel extends AdHocSearchPanel { dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); }); - dataSourceList.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseDragged(MouseEvent evt) { - //Unused by now - } - - @Override - public void mouseMoved(MouseEvent evt) { - JList dsList = (JList) evt.getSource(); - int index = dsList.locationToIndex(evt.getPoint()); - if (index > -1) { - dsList.setToolTipText(getDataSourceToolTipList().get(index)); - } - } - }); } static synchronized DropdownListSearchPanel getDefault() { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index b859df85ef..06a0bcd8b1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -88,22 +88,6 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); }); - this.dataSourceList.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseDragged(MouseEvent evt) { - //Unused by now - } - - @Override - public void mouseMoved(MouseEvent evt) { - JList DsList = (JList) evt.getSource(); - int index = DsList.locationToIndex(evt.getPoint()); - if (index > -1) { - DsList.setToolTipText(getDataSourceToolTipList().get(index)); - } - } - }); } /** From 7211b2b9e0f134c5df38728cc214a263dbf70623 Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Wed, 27 Jun 2018 16:58:20 -0600 Subject: [PATCH 31/37] polish --- .../commonfilesearch/CommonFilesSearchResultsViewerTable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java index 733b40db80..71ffe1a21e 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java @@ -72,7 +72,7 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { TableColumn column = columnsEnumerator.nextElement(); final String headerValue = column.getHeaderValue().toString(); - final Integer get = COLUMN_WIDTHS.get((String)headerValue); + final Integer get = COLUMN_WIDTHS.get(headerValue); column.setPreferredWidth(get); } From 83cbb9111770380fd1fbadf17491f3a99a817cf2 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 27 Jun 2018 19:12:14 -0400 Subject: [PATCH 32/37] Update CommonFilesSearchAction.java --- .../autopsy/commonfilesearch/CommonFilesSearchAction.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java index 2100bbefbb..1727c9322b 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java @@ -36,19 +36,19 @@ 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); } - @NbBundle.Messages({ - "CommonFilesSearchAction.exception=Unexpected Exception checking for common files search enabled."}) + @Override public boolean isEnabled(){ boolean shouldBeEnabled = false; try { - shouldBeEnabled = Case.isCaseOpen() && Case.getCurrentCase().getDataSources().size() > 1 && Installer.isJavaFxInited(); + shouldBeEnabled = Case.isCaseOpen() && Case.getCurrentCase().getDataSources().size() > 1; } catch(TskCoreException ex) { - logger.log(Level.INFO, Bundle.CommonFilesSearchAction_exception(), ex); + logger.log(Level.SEVERE, "Error getting data sources for action enabled check", ex); } return super.isEnabled() && shouldBeEnabled; } From 139fa6aa72bda7ab565839e2f1041f58942fc9b1 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 28 Jun 2018 09:16:35 -0400 Subject: [PATCH 33/37] Update common files docs --- docs/doxygen-user/common_files.dox | 2 +- .../images/common_files_dialog.png | Bin 12170 -> 19107 bytes .../images/common_files_results.png | Bin 45236 -> 53625 bytes 3 files changed, 1 insertion(+), 1 deletion(-) 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 d1d780e66b9635366a49f8b2fc20c23bd287a3e9..8be9e7c6f1f7f4d3ae9c0e283619204b937e35a2 100644 GIT binary patch literal 19107 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 literal 45236 zcmb@u2UJsCw=RqgR0M1&MZkt2RRyGjih%T9LlprbgdR#LQF(F49W^QW6MV zRFKdELg%eroaQ>s zz`($w@#L`~1H&;K1H)0_lZ?QbUtgJ%fVWegPb_>G7?>{{{2gIPPUQejGWlxgs51R# zyu`vF`IzyI2m`}a293uLjRS@jMnZy(r_<>4&7_;R?AXr+<@uRijhwip`u3H^+c(2W zFD@qdlF_ygd;SV~QuxcznpD>Ny7#z#qrSnX26qhi@y9p4ovB5)T(xTMzqt0HyK)Rd zK$@K7@@`J`359Dr-II`YzIWm1@c>1>*sOLa$|sFHlPa;@4xNee+THR30kQ$Iw5nV_ z{P3Xk%;9^}y~DSaCn6ymm)BoqCo0v2>oRx_EQ-`7=mM8!zfJ0i$qVdQuQscP(d+$b zQFtM8B6s?!ppk@We$C#ml#c7>Hnl1qBI(~akBt{|RtGSp0rzzl^y3nL)OB>G$xC_t z{-(R=G5S??vDXi>?U>irmgZgJ05eNSp*r-uQCxW-dF+DZrY%j zFAIDOtM(bLK!P=dmXQL3URJ;1J(D7DW{h(1AbxeTYsg-6zqZPW@DuEMOOMh9wA6{ zX-Z)2G9H*+A#N-s%}W(%Dl2V;JLP#P9A#j5HQoAPP_)4%*&qxMJ*5AxT@1hZl3-)> zrD3^*#GOU@h00pnJvSsc&|o%qF^deHO(R1kZ@%->^daVE)y_`y{jPDIU!D4fb{r5M zxo^jD!Ln=Wv30tFQB9q&)XYFaA^G#(6>CLN5qKaH}@qwyrsn!<|YOl%n# zUNFCONnQ<5Xquk-`N96B_ncg8%MHBb^xF0i2HF^w30fmK;ZT=CB8ykC2m9 z_6R5O^y8uf6L7^EQ$yEHrMb>WW4ZIo!L}!9B_SpL-FlPtd3@p>-SdiTm9D`ADF

-g@swqVMdKycD=o8yNz# zUZ&Qb@Ch^n?0n=$UPfqPtP)Ld3;X%=b+?c?IgNW~R9a$qz?ifTdpn!L^78U-#F%+I zj_^y$3bt>}id_>uR+`tESD}5m57H~?*l%~oiYdyxl?r;#-x1wiK%Cm;F(^W|1{vB{ z=Igt6(;C`uvhUan>raBx248)rEqYuga|60A58rjrNv^PgLUWQOUFrsuy-n`WZ|oq^ zt0=5cacUM~+A2e$;@c&+{`mxCZ!8;%g*mDPX`SbVeA)i-9p|tjdPEL)-)PN)rTsLz z6N~OV>sMi881HT7+EKjad<7DbG`$=WtOBm}TW)?iUYv_RGm?Ipy4zvotNx@m(3fzD zv_x_FI*zG?nC-3+)P{~ODpoy;K+v3bzRq`BN)u82Lri=FH6F^v+=g+IP9{i)`i*-D z2rd}|N_SzSTR^-LYlZAG_I@!1v^G@oJoMXwN8XNPOubj3|9IHTZd!Ygm?h}9?7rpB z*z^|J)9R@!1)A)MdDYT68W$n8wRD?@wlBKJzC zC88U$_k#TAFqB<2aE49ZSp6FJB601r&CvdNN-G1yCy*?3-uM@p+r28Yc{e&bdJ*k& z-U30qpGku6k!_`(bzcYjN-CDL+#8cj}rd2%(r0?dV8ukqq<$Rx{_`qlwdm9Vi5ibvzFH1zWzVW?Ak@6$8PnHwhM>DHMO(k0!L6kV6GUE`ME ziHsK`nWymZ+}pP`>@nMFJo3^_^|pCm3h_h4Tbf8>#`6uA>8j36m*Nm3FvYJJLzkbL zpjt@XvjoY>?GItHI;Xef3y;`OmPz|N=MBl*jXH4EF6RwRS{F=5m_E00XKHc%7HA0f zRIN#xd)}f>kJ7FS$_9JC)3C!X5QZ3H4uk_<_Nyf{H9|}^BHeu zG~)IPo~NT~f4}+Ic3M>Y&fR(AuI@Zo4X`B*omvcg078onBKULwDtnFz*So(+FA57@hY zbHx42Ggdu(f=+IXYF`IRzF7H;Eh=O5>Ad-eOeKeplVs#E*}Ik0EwcNFZgswYKOW6l5+b@EVZ;4)gUC)v5 z{t3Sp9BHeCX{~>uT*}AvJ#WzNxsD`^ep0i0pR&?NJ_XyYG)Dj>@KgXt4e`FLtc6Mh zYkv@>j^J*srSv|h0wAps;j=qb?on~bcIg@yNe}p#nK(=XJnWsN!eoRSm z{nB!IJEUS1*gdNh#$*dZXQSDUJW_W1JE#s+&AOAQm7LmE>gKZL zy$9PI_Ivpi(vniqH;PoPGm3@Se&j&By|~P*4_EtL@Oq2G zhl>2Jw>0X)`W8)Qpq}z9Lros_m3Hd~Ep20k)XiGsUYYJKB>wpd=j8gNE_xGDjgxYl zJQvLe!6^;>q*b81-uPY7D2wIodHL{zFA}>nc)Ck*EsB*_<`V$;1{y@RBXgCOV~5s< z8(h}9&Pzhnj!@bFw@ZBP?(P>{WnyR;=U%(=!;hStl9KT5-8gn6T6$DaBZmCBLqt8u zE|GYjr;^kK-!C`aUz;?*omwCi)K)c=U)epj+i)MeGQ)N=#5%SCV-$OaIzN|TNoepl zhMyi8Eo;V|%61+@1&tE32=t4NaOZ7#92$4sp_bQDNR;!wddZ3XR>bdWlaR`D~0^c>Nb{>g%-u3`u7(pb3_mqwPjL=Ho0<(XXM^U!)O@~k!-Ao8%6S+ z0p(Y`_JJZ>-i4248SJSH@nJ`5)Yl)5j~T3<81m_QH3D%_kIe@bkXG|ND zB-ppe-q$K)pKyC|pIsgU$HG5w{AT>CShD2UjL~?H$zM%S z=AK&!RN6LngNSkU&ESPZ8rk+4IMljtcX;bm7evrB;&RE=dOv=mP&vZ@f#YYoxI)xsQ!@(`sw^7s-^G(JXFOl ziq?f+D7c+G;2h>2u=;6+^~&#$;#C=;)!519vs5zj-Ck{OX!yjhmU} z*D*LMy-b%g_WR(dU`sJ5WTWApnI2-X`-Q#Yx;X11{skabX~g8pxvh5si|>!jw=r$R zCf#|cs;b=g@ze!LsfGviv2yMTrw$*k6#0j{n!rNOiG`nO?{2!G?%VwCA-2M~O9ERG z{y_k`+hn1T=ilg5wE<`A!>U11jLqA~? zCP`79yqh~Sf8T`TEELzlrNj=0x^@PZZK`o znYKt7!#8afQ3h-VW&y9n9E0!w}f!X%Hb{S~bl=iF?%2J8Z^{r4|d)ql@jN?%$FqS6bQL;p}ql z;Ys2uwX1Q}I{8t4vcZ*gp6`|dwC76DNKV~Gy{zWWz(Y}5hY{A4aaM_ep=!=*qq@tb zueAgW{AHBOt&>KyHno%UgKp_8_)Vv+*~#vF86)|}sxlVO-Z7s^jf{O?xZ^K9A4)^5 z@yWCY>(IT=QEf3Ul#hASdw;F*0&(kzr1_Hj9MeGv%h2TgtW^6?VBS=AQ|u`>VXbCjPv`dLumZFeU2Yvr>@(0vsa5olE)tlu-!o zRmceDOD1(;1XLF!-fctSf(w}YB~l!Qhp_H_qh6LEXaAaGr+_{iRk_{`?Nl^gi}BQo z?X#ZbiM`p6IU$IY)RW zE10mJzx;HR#>MHszKC)eueG@2M;(}Mfp<#3F3I0NkXwuk3T`RT%eXzn;XvEINC~*< zvgzkSdU-zj?$FVWyTWF!QTC79-Nd?;i0;v?m!CJuz-g~SpqAXb{w?G%gF1(5aHZcv zq5$*D-Metjv-irc)W@Rr1m_20z&lf4S3bOcs7ayF6YZmV+7cX|ikCwU>q|9nC5!d=qCud`D#}Ly zd8#2%A8?-uoxnA1^pxpy_L0lOp6VyIT1>4^Jfzd z8M(_tyr;vR*B%c*{rXPZOhGajS!d8@ot+$s%n&->_V!G#B{v6*dqWB5mmd^;DLnMi zU7s}9Ic|bGSxn1)WPPHIrh8ileI~ z{l&7IRMwa<3u5z*ThgBG5GJ^{(Od9+3x3qj^PR7-mVU{n7mC& zRmOpyj8{PM0WCK)!bV3>BkJOfy8blxa_3@GUS*U+%Pm*u45cPeHji=$N4a5345X*x z-2C0WKf8kP2JV$e^Yo0|LLwtU?(EM$Bhq@zMqZizGNE)&9aw^V6< zQLhi-kd>1ql}9-BgCimJrFRyknO^YC!&c+NB^D!!>dWk7LlTWA*<9J!j40^niuFeK z9N3wbRn~>{g4bcR=jMFym(6)PKB*S>qmCq3iv{l#6lKI;9Ai5E<70wcsc&jHm9r#= z$**Pfhru!5mR|H-zrwt`c8E*5WaRCT`}4U1X5eMEtn~wNU%a&$8S>&nxAGxa$>mS* z=qvcPyWwD%f2_YIoF@)G_(sYPa9#HC2R6fSxw6+8p7|hnAyfX~E5k|k|H?}C@N4ex zl+{So8?hW*mLVTF$KBClcZ;TJWow+^VJDqJ?#&0JEW-;s&Vx6GS3pPr9G&*13!(;o zev!3bat}4yv;G8*e=z(^)?S?TqhHJiZK|GxO@BQNLPKz<+oqVXOxi zJ-rxlJpo!}Do!co{r3m0knDrHq$ZXG$YTq(gX@jkTq1ewrd!Tw5YPrnn;j{*S(0CD z_~RBFrt`&EvhuMMlK4x=>>;d@m(9)WLta@yaN?nao-dE%>?OWclXD7P;rgiwd67te zmpq+zBU8sp*D#`SktR8T`!x?jB}s*;qTdV2WF;p8>Gz5qx&ztVm^2Q#Xx zSSeQyajGs8Rjd=ylgfdt{RsCqwlpd(9DbMa`S)hx;bTX%pB6WHsj3DT>K2IBRD=wi zuDL_oaCzMsc=*`4TME91?HG;&)HraQ6*-*e{|~inEwB=t|BO`LV3&Q^%J~}*Ldely zIp%2|Ht%Lf(0!2(uOO^56xJ9@HC+jj|2Tlz z)K@Hb%8PpTM;{wcw4xyZIpHxTZxJ^oT4PIJUi6q>wMqPII*8n{8J{G3XNxulueLsQ zja&@~^;SUi-y*dSVTvQfD#r){aB#0}Vdynn1vRnIHoy93Ukjaz4K&E7-AfWY(bUp{6-u+T|1qxx1bi*68YN<>a^CYi6|EnW^(R5kcsV#WvZ<6ivE$& zw8@6?DUyJm5(P6*;Ek{0b#JQteCO-ret=bLDI|juh!}N$upG?`>t42Po;Ph7LAbB% zPn_@Cov;A0If}V0HZWBpg{hkQZH)BJ=4RPgc%o}l|UOv7LEe^*o+kR^= z6Y%_d5Y2;rLc*@OyO41yf7@ zd~WCciG`R%i;&qnSZ8V;myeb54-QEQREsXM7me&&n2Tkr8ZN&)(m4xiZD4B07n$Io zo+P0_QQ;Y%j@!01*yp7vGT*OR8Aa;43>|;8P8bUEBqj|(EcPovW^G^#NT9oQK{6Me zZq~*6lRGfI;i+mo$&l|7LZ*u?XyK;fvTo)4@MuIWYfE197mjrCsVb-pM$S61wd{WA zw;eua+;`u)H&1+mGFY`5pgFasQE=gw_4K8Ee-#lby0&PSvggDE_y23RuDkBX4VVIc z$)hxXd-|8&5){0HaqNFiMM5U$=ryEE$RUg&W<<`Y+@mgg#C*-)cs4fj^8PGeANfbi z`Z~HH(eKv$mlj_9$GZ-aF1yu%)%BvWN^?WS>TK2_4fIFqXG^CW*x>`N_eXeYm7~$`8IBLWP9T*g~ zzMikqyOC=*n%8iRl=fQA+u4YW##4u5x1aHzl(-6T!LpyT?xuqq(i3MsiZ9^p9cpjs zjD7m4Xw>J^xwNd8Cvn-;lN?BkaEy=)TuPs#GN(6#rA#XqJJ<4lEcOhy-=H=999XNJ zcZY<$Tf351vQ&@*bN>dNr+B+pC@YP`sLpPFmdioQW8^@IezT-L;XwK^F<**EpIK0D zzH{s|>cabCEfsen6s!9S7+QB$SY;iRt1{$odR;>nSap4KeS=WTS^_bcUmUaD+9;V> z@<#bfUldJ`l!rf7rq)7CvW5?Al!3ol#9j3&=EKvQb!P*C z(*;x=h2`8G-OZ32s?y5JkM?D@=Lc%25w<%G;bw~SG2c6DFMl_=EKjR$-r@6{ zur9F-`)Q|dRjCk=E^i}1v(0N70#?l#918&tCsnPfuj79bepji63`Zks-LoS&k=qz;=F_ZKUQ)E0TtcHp_l ztelo}XKc3viLkG1^_gdFF-9-Zp=VFVFUa;ygIEu4dN20%^eebBW!%HrB{f}cFuRNE zmWz7VMe4#`;rShJZ3+oRX)r)R4EAmOvEi}=;yikC;$qk18nv3~*uCVDdc7Pk?Al9~ zp)bxDKR3@pE#e|Eu4Bv~&x;#G|HpY}D@SWr5}sO4}Uo zifStLi#AqCjAz%h%j!UD_1FXLBhXzOMP@D4XR~kC_*J^IRhwu*)qS4>mZm%|#}b;i zsDKoPKcpz!`3digh^H9m4tK8oeeb2j=P-x;ikR;K%1yCUg%gcZ!c%hg1k}KAo~t9r zWF>au6CzqBKbd~HdF=Gv`n-&d$_~e%7MTB}b2#>ggmt{Ksl#OvLgvr0zD&p0osN`| z%vrVJEP3}yNY^of&|Ms@(w6hJgm9CthdLX#9usd;+s zk_15$m^*)kc%R=;4<;8K@uAL*r*pwbkLcC(M&D@t@rlR4sbh-1gdA?krxpntNZZ`I zDnWC-1NU-~)|X7t#QGP^!bjcd3~x%;=F`-}W|M| zx+r6QhDBq{dicK`Xl`OGZc};uI@Sidh4G=2pDD{dPE_KmQZR8DGxKvvS;!~6`89@D8HG<) zp(}GLt&N&d&UCD9SOcF8W$##LlLxQ=@4Xvac6TVQ(e?WVbvIn*a@5zq!gJL-JTOY+ z&x@2wbZEv0u6;~2xqfGM4s}laiT}Ds$|;r|ZqI`;3D9I#{f)KVt0oxv1KOrnIOg%Q zr}=}gfifD(N)&iqzWxdej2R^Us92S zwyBpKb$${X|7>qFgZ_E6PRvL%Ha*pYVU~#jWmeVtqj^+9JyEqOhQ##|%|( z5EuV`o1I-@i$?<~aTq|nkZ`7Gb&>YfxlcxQ9VMa@BqqQ-Q?%spvD93?y*YI(rGBR` zcwVoqtBWAtpm?!;f(^0g<4HX3>6KOnndO+}Zrncdg3K<4UE!#~UfX&nrqTOCb_i1< zTBSm0Yi<4ecGuY6$e2rCzJX;OtP)7#(l(mPL{p9&?VQU|nA#Ek*C6c_JZEB9&x+piFti}eXN**k`e;5cUU9 z$JX9I`BwwJ^7S}!gu4+D=T_FN!|hw|oRN}}dY!ZO0`OVDas8&BQx=~XXcpUv7UATL zb;I?B1YU@L4->6BEF-E~4s*nnNn*xBTcA2R(XdG*hB^}DU0Lmx=LX{XsMh$W)yY@W zdP-m~D?)d7elEg@HWv`){UfFqow)g=OzIAjukKHn4}UdXx3fXsYmTszV7Ve`?$8qD z&WzA8IyV-{dhe!`bVhHB02jXoChAH73pX6a{`T!0C?F%9e@Gbqm7I2A0`7IIi z`=Um?iO&ZeI9#8*^@T^Lgt;hX;3%L#t@t#X?6!eKi|EMg!9Z zp%^;#7$*d9K0fH`0fDJadMvnHfj9R2`SVZN5TZm@G2N2pSB`jr*cMN#xDOxtH{0rI zzZ2_=#Y0KB`5;GI4fNWmkETiTg@E^kQ6h{7EH6WM zptPCi7JtC+!cR8Tg@Wdsd;O+Xk!jnAHI!$lCvo zXZQFr4=q?zTV^lGkpfU3g@y^F@S_c1+q=~N&_k7vHMr!Re1A~{{W*3mO@k$HVn#%l z*X(7?oIr&cuvpS(5R;BcPF7d zN%0t9H?umYT@C@o0Y2a+F3yb@2LV`MBT-RNxzJjm;K$}4zTh<}D^}?=W840VUvZA0 zGEtC#-u-R3|6tiOnzqz^srhe_di^zoY1%Se+0D%K^G$#x!51}JeBEaJmlLoS=jZ3= zxTzKAXko9Vg}c2StdxWxu_3rqo+Tl)6Ky>-c&sacrAX0v*wwfj{7op$& zsbOa{Iy&;!S0dFkdPQ%jyEgyG0Du2|9d@Yvp8Lmf6h5Ww!E)ysC(Cjt9rIE*%UlJq zp0buZeciwES-7Q{@WJ&)V*G%Xw)WE+al6)KT0R^=3JvfnveR3V3+-OaS?>I zxcK<|;t6S&-8q8;XK+iWHGw2y{d9V<7u7h41shvcKIXS!Je(g-_Y)1}ZmPTJ_~})U zQQWg+r9g#F2^>(Y0vP<5-HX(BAFuw*DNZ#^V6^Qk#NIRdfakb)@X!a+rx5(4|8C`C zP2R5r3=(g?R+errD;6|aF1#skt(ves%)df+X}811mlg(?ThrMh3=G@SQ}+Na{7nryJoHwSa#Pmo(=gn z9>xYlPWEvT6H6J*h+39P-dsTTNw-7ReybhDmI0u3Fv70Fkz&SUqHgHbRTC zXy35=)9QHBsz64i3<~`Gxv>%Mc8G87Lsz!j~@h5DzBg#hQS5D1G@_ zN4g?W3ZEB`VU-HKvy+wsh zKS`RrROs5}O^DV8HiFbYN?%a@Q@K1fwO7Sa+->oWzRl-g$GZoqOokUr7ezn-0R(t_ zz_@5rD>6*~sJU&Dl2Kgzfuh%mcxa#z&-(J8{?>KWb9``cAH#^CIU>8p$?g43D#hPu zd5oBz^qWIOpz1j8RQhA$aVe0DfdmZlkH2_dEFCci#IlWC9q*PC`ocTkhXD~U4#t) zBp?h{`3r3PQ`1CKNmt&tzwl`mhj)JBX2Jnrg>{E#nl;Q8_r}9%aQcX?eeY=#gQgb; z9WY=uO0S6HBqwQ|W#1vc4$iz_Rx(Fx>!jSx7(JxJ&x$Yw!~=35sZACF=~1X1TzyN)`O9(Dew9igPUx>^f2 zdH>Mz7_R%yyZlnNLU%&bAQtx&cn1+VJFB+@Z%{2^e-afFOgMz2i+j^v3^_TOi~0lP zL9W@Ha`hS19YjmsiGJ~?)w$!ijNpOaRjABN10B)4^_~^R=_sBYh&bi_Gq9bd@_)GD zpv^_XP6S)E6PL{M(jPILBbP){a?do&mRu{S-i}vN>JikyI!---{MzzCnp-{gIGmBr z47+Ur3rYrUxDm9$1I4EREQpJ5;kVr22CcJ^aOmIjRuo&BWgsd!$YAbW7U&=fe)y70=6 zgS;O@_sA8F&8tg`0!xk_RovcbWgso?6cw zgkaA(O-=xBd3@^>LK&Rcv^yLXR;zTE6bXaaD4E8sN7v@S%H0g^D8_Zo@`}i>YzK>h zy^URoLJ3p!{Z-=x?J4;`5t94){|c>qSr95YLO0$XIduN1LA-h26jM%TF*W2C0DiRn ztxIy}iG<7fpK7UNqiI4#?Z%AdK%Ew#bv`2hfT$(CVqTf&o&lu`Yq#%aGCLuRB#aZ> z1@#U0#ZvN~{t;EZRKc%(u5`>`>CRTA2tR<*+9-lZeRjWVSqGgl%!M|2*NWhy$nrP@ z@sC4%&Jgx){1$gFIR7SZxyUh=mh^!4RSkHSD>bpmE7IKMV@ z&P$i$?FZt#cze}+f2#aa3zUT=+SOGyCpJb}(0M@+5)>GF6Lbtu`wvGuTI10$svO)Z zg>QTqCIw6t8gLno9G!tI{}-?%sobgB$r9rS`QN~jB`HDickgR#;^IxLvVi2I+8-ai z6gC}@q_k-#d;7M5MYD&Y&j+}ZtZYt1o6RpSetm@BAljRQ`CuH4fjlB92gsudsDmw| z*$kTHcE#W1zj^CPS(?N2qKOcy;Oak)ugh*5e}Rhim>Bir1P}!IKYL={Fc4z;q7Zn8 zrirxm5L|}L>{_EWBUAsN4t{e#<|qJQEH#yZLY)r)3GFbAq${o;I4*S{2E(k}A;g%8 zZkGvj!~^)^{e6Q#3&+9@O<9G>LrFbZ1Lex!yCiFBE`O{2m`l>vjJY*iLWnzGLeKHf zYZH=4c=+jX1CVJs&rhcadW>NE52w62%rQeR@>CwMIT=}I%`fa2CZ>~Rl?udXn z?|G zN>|8>^gsF~62W!fjpGm4sIm*3J9HWhClQBp@wbpc91w1kPK}F91Rs-8X7H>#I{6Zn z7#G(|ZSpR$shhJH7qM>rk8u1GSxQ>k-z9K3{4o4|@Ygp9ljZ*rU%Wq*<^K`BIQPe8 z{3m=tX8OOu7ioh7h|8_5*5Vp%V~$0VvmzdRKS7@t3VL(jJj#JaWM$U$&$`7P0_=}Z;KswF#^aFZ zJ)WE$NKPL?aMXvmMwX?aq0uVkDjWS+1=Er07XvO0?xZ)popwGfoP+#@q_%z5BE9Fz zYNWAl*rj;?RNnjPi^R!jtTJXdb4OS|u-3s+f5|2sGnJaLtW4{PeT5?-@JV~$QVx>+ zzB_iZhBH;K4G1a!VN<w-GT?*PB=XOXr0P#ZN6!EaimnbdYN`8L7~UU2iEq*+AVXm zn|zf_-9MXHvzLahuIZTY&iq?jT3P*8j(8CzA%uU>;OLhCCqq!!$K>Dd7)pU)U$w9g zHqF)JY=Fa<6;$OKPx!B^xn)pml}=lyC7&Bk1cx+p=kSx)s9Sp(iN-)##7bqCOT*n9 z`Sr!PC@vF))N^rM@Q|TROm=GTyTMUo$7ibKvDV$nLX;cJUp&1fT43dLo5YQ(rLJoY zNN!tNP|`gT#%60-SgQ5bTB~i7gHq zAsl597w!jPgpE{%Ej@g6N(Lrht|u!bsHP2e7`W(sDJy$p;y^4DKAj+@@zPE>AjrKM zT~cCf0DGo?@k_|Gp(rxIs{ds$z=sg0=;?i{pEqxf)TH{K9k>j`P&%cDvjr|4S6~>QgCnDujX>#qzr}qFM*5=y(qksKM!q5cf)MdgGdyEL-Vv!JGJE#bP z85+o!DJl~c^vf@i{;a4qFiT!}@D6cMx+^go2K@7DfPyW&^@k5vtmI*3m z?_RgwL=yThI)})aqF&kQj~UQ&3rfEtAbJBErGsG1I@)gNi=qY5ci0|Y^LfUfdE+g$ zlz8mh^_D)Qz-ezD{IMetk%yo0QC_teQTY)@9!sYR1|@IC8euQ)gEB$vhpJGy`Kg~j z@=H{bkWJn|eYf2p;Ea6f{$9ep7lSR!m0LwjbrKaT2jMB6HQY`3TSDSR+Wj`K$9|AS zd*TY8LWEe3drKK^7_(E50|+lMD$8+g;Wyd8xu9F)PI(&uRk(<@jaWbL5%YBtGeFE_ zfiGKdSS}g6Ox|)C>XVtjp!La3#6V5w^_9ui?tL9*`c((-P!yV=>8+*5BHUW7$j(VC z_)<5r9~0ovPC=TQn1FFulkCwD*C^uH=D>UduoxF)@Qo&1- zY;tOYjn+}4L?r1w+k00G{Xsq0dY+r>nR?R8^(7G_$Ngv1*vc+QgM)Q1K9!hs&fy!; zKIF5z)#nAjmt{e8nAYj9>Em@FA&7g1BX|dU!5=G8tqpYw*?UQxntCbu3e^=2BOkE| z6BHGJmyK(_PR0&t2{C!K0Y`u13*;q!0)o%QJWaO%0A(rKeS`}dVtxoKCnTK__|w#Y z+)UqQzGu&-cQP#9Dngy5=G74A&C~1i zxe~9FO7$XBB8qUv1)3(g+(M__4YN*T_B-Cad9*r1DcgI92En3iKXyjCD5$Jadi`#6 zcLpz(?Nu{Nzs8KwYdSj}=1tWAhcIt_)G?1i3K8K`J?*6#d#1Ikuf3EQ`djB~E zgsh&u!^6e18E~7;5&sN;Kz6YNIKTtyS(K-wB#{!Yr<4J_3J{1(05~WzE5R8XZ0D}# zPLZ}d9EAdw<>P+uc6uxPgSlPq9RlbV?i20*f-TJt1X4TO?;N822hyyF2Ib8ou*0ff zfXj6KRcgO31`L2hvDSbcl3rl z6(J~J^LUgw2QxBsA<4$(D!?gogxAaDb5}HEdCff!c@lKG&4Ch|%naE*kk%@oX%&c7 zT{ad5Q8?hk>uG5qozU!Q{ZGs}-uoL&^1acEMhcrApa^x^Scy!A^9oG91=yo+#-W1n&(joas%baT5~kY$AIEt ze^p9Mad<9>&#uHGl@@WV@o*H7`BFYDmIu;x-nif)2T0bv_=lfSv-bE;;q0V(>>*%r zd4$r=7$m*$j9g7%=MtRNtZ{>TngZ;}&!V^j$2Ks_uLbKQq2wX1i~R?v1B|WHN>Vl2 zd#QVXIc&0i7xD|OXaf8=(q-S#tUf;B{d_Vn{H!@r$WiFVqNGY+Yp7R`BUPz+(WM14 z{JD*d_cGc#^J~TPmiKF0HIlu1TWPv7}Ob0;N-0{<;--{-Qr)Trlr;{)>?J2&RHYNA0Ri*5wBh<$Hvx_KL$ zV*PNN|0xBPW3A@Spc`w|kezQA^n8UlSVMSDy44t(B_6neRH~gt+*0xvRsW7(pZE9N zPwx{WU1;&+cdsOB#hr(`bfsf=S8=}E(}|?sbVWxCM5sCra07q-FTfhBa8dl1pUMtq zM#bY%X_%WJqsjv4%&#ufKga`nZiQzMps5s4^ABLKq}BWEg(J`MfN&^2FS3c%QHtS-ELG|h0 zB5HsgqT-gGBhUI<(0;(YsA~ITN}F-dn^nQdi#_RY&g@CX?Eig#Uw88UHDs0_nsMd(OMgp}FP!H_LKnXAGW!s`* z!Bqkr1c@=@A0!D`8lb+GigzQ5uGVP>O0i#i=$m|6*p0o=>Vz7)5d6q}gr)uKkXU(} zUowEU3X7jY-bCfr^>>o)q8kQ;O@N&D3n-SjPHTU|+2FEenvyFMaaA@~%lV zSa2446*)iO?iu1iD$9vrAj7?N{4smKyE3v2iKZ@g+zy3r1l-~#x@}2=hB~U5z1M%x z_Y?zB(*!wQ+kzoZgZ-6BwkpQry4khD*k zG0m<_fG<6cN+HzQeytouLuB~WQ-&ZEK01VNN;Z)qYJi?xG3i0=(!@>+rXAJJqo&=K zKm;GJ0L1>EP|3p95!=x;$HMlE1+H$+Hb5Mz<@w;F8@dT5PC^cD;rE1ra(M@tkG#9A zt~(C~L562nL;4Otf6lm1$vE2k_w9zh>8&?X`hKVN9Rg(%LA-gsc_38Ram+svYO2#>yn=@Vl+`k9($qOr2@rrp=H=`KxR~DlB!p80bXw<#nu6G?4bDI#RuT` z(2DfkwS)2rAd9Mvk8?=|YOHfzNC6>r!Fj=}+rUe^vZ5lty4s=+W}W+{O*%oEU$4tU z%m}E}e}8e-A2DhFCqb(Hq34i(@?~}w`-isXWt5xnbqY^1IlvECuhQLvL}?B1`QM3B z|HI9HmY>b~@GQeW|8v9usP1n;9+Q#(?<8M4CGLnb<^RN7r+&$^r+W^{5uj+tpUVNy zK!_z=%BQG4Fz3N9lo%Rc^ND~EIn^gNRWa5avP9~&i1Yj*%8d_Fn-xmu=(SfluzB{bYmYyQ=TqQ$V1teQ=31CT%`O zJNjzR*&9?HRW*;3yg@^#-BTB@P@|9Ld@y${FL}^0H!`@AjS=xvNJ~8W-6V16W;^!Hnb`p@&`z(nlxnNl-hDehMTs{7|VdsP~8pFWArwrw) zrf;%b2767ZUiyNFQ&sD*x4Ix0&h^#(dRWiX^SH=!SH-VBh2G`Ry?C|k%YA#fXqnGx zx^~r36SWWSI=x9d^-S%5vG*20aYb#qE*c09!Gn_o5AGT~AxJ`S3+@mgI5h5_;O-LK zT@u{gEqHKCW8DpR6ZvPR?muTv-KleDZr!N@ijc1Ey}S2b>sjyjzTXOy4fI$zrHYjo zL=fP3G_pg$IK(?z47z>P_oLFpb0GjkB1hb#UR^dK^6+UG&Py#F8lwHJNH|w&W|38E zH8&55;2`CvRLMOm4YQpYogPTrtKQT8>I}(JO++{{Q9Cm4 zA8i$3F@bZ;??F6~l~xi%5s|dXOcF8}S%GHBk!JLf&&ARz9x}j*;hUFI2ODY_X(3bL zSrh5$9?Bld9q^?Qy)Pf-789YDF2C6plkup^iQ9lC;kHF+1j;TodI zOc$^_&sP?LgBJ2Mx7MIpNg6(+qT7Z`hsYPJmp9<2S&zN$bL$+_OdhLxNFi)#-PNb0 z3)=>?qC*5nZz!$8re$w=`5bB`z`_%F)h84W@4sN>MkJ{~(@n9QlA3DuNKT=*-|Cv+ zBk1s>KoC$G7eglYbUN|){VAJuopqw@H=La*I_0(BrkRp=pV@_|i+~Q0M4YNx%{tk% zU!|9#1spzdm%znH*z-juA$XK1!A|1q_}oV8V@<1jsmY0(z@tg_W(%kz`Fh+xI!V7T#%y%kQDrn=z6c%smTWt2d?c9XjAyAp(=D{<;M-r;R(ne+6 z$ zYFWTyp{Y3g0ax_X-1W+bfwh*`0fgdJ^wE;=52dTS@x?n*(FxZ(iN@B_e40Dl(HVDO8n+8ij@cQvlTc{E5)6 zKmo3N7=AQ}@8nld-VHR2|S*Qu;+Xm{tSo*Jm}R^Up0O%L|v*yRlrh9 zX_nWP$Y3Yt8KAn1Z{k>aSHuQCx<{=EW0GXyH*zAJw@P`iQ9#|%%AgK*n(3oUe;fVg+9 zx}V%?GB}fCwsR*N`LEYBzE5Cpg6d|s0*>>1-hN4H*VP?@wq{$3&Z^;NEKjSI@XXH4 zXm|TB3YK1c6jbPS6zE~HIi6oQZ``WScdlAf@h0ca=szQWkb!%D${2V9;H1avTM4=t zOD7qv(UKRGZ5#Yv>sqWE8SM%BVbK!9H#Y)-fq@8>j&nqB_Ij_!P5ln5sd`bU{ZHJw zFQ{aqUgi0w@Y=2q#4+KBXR2~46;Hn>eqh0kXt}>%DDov+vlRH!H#J3xdxb0^5tXCv zhQ6Uk(mOosAnS2h40m%?o)fM)JS#XDXhq6Hoh#pvsPuE(^!HROd-o$loyWrEc7HWH z=;6cY92#w3A;p_;5!(B~fP}shsP)l{$Bfw$$W^UZ9xa1RY_+u(GoHK9t1ia@J2bule_t zA_LVy>mhU>*1jR{UIK4HSXdaG*w@#;ZgW&>BSk)($%+iOJ%*C9vUR{UXT^U1soWni z{ve!EsjA&A)U~9f@*c&(Oc z^6~N~k04sCoS^L0;$f3zTN2+CAp!eX|?fO)ioXGf4$cZTH z7k6tz3k!?c?b-avQ_$WXSY4b?7aEb!N~YF&^_?mTrn`Ss9|7AGwU||1k?6|=qg?cN zehqn15{r{9&-OTQ9NO}joWv2S4?S32BDILbtow=hpfSb{awxkpoB|c6rsUgXk<}_h z;4ug!!p{^H38_CF`Z@{NJYNr~p1*W2?r>Zd ztx2(Iv!`&5fk1W^UlW|;x7~VQw?;48c=mGH14xl6?=2TH)GWU=GT{0*aX6ie+Ia+5 ziJr`{c2xy2)YsTh?7IIy> ze9mZsuyn3Sr=4kB4mY`aHaHDXc1bkN+Pzn-&T!_fL#f36)aiJ9GGPl_l4yyvv*g!> zv?NsZi+u3h4xCdIx%%$6ZQ98uS*rSI+CoLh?WgR)!9nohp><_#?Nc6J-tNvI?Cyny z;s2FLB?EoTE|LKj>P?aED` zS(2>x^&s2bb&ji`k_?9*o9Om6bDo|=Ww6kV?nMx<@%-rg{%P9YR(BN0+F8q4^mccO zx25KD!tLTA?5nkCAxFyvL-~feDCB*6nVQi_dO7rB(FS79dPys496e%XYa0giDpawu zu?dZcz^rV$6%s|DPHfyJ`P!2sB*bnu2R>ACb>WkrQR^Y-nCqBYptg_6Lv=& zui!8-F|FSWVOG}FJ)4=C>FSV-A4aY6+^YqbI~e_j0M%%V{O%ZA^a8?Y)E%}mg2kMY z$X)6uVF)h)&UzQi?ci3yyi8H*SVt+H<%hCxxY{I)@>ET{2tRN_fZbnZszCCX(Ydku zv+(pI;j2jnGS`?=nRsB*{D6S$u$n-Q_b#?60o8vfGK0adRTBH*n#+8~=|ohOZ{2S9 zYU_;+E0No|=BUEBNdY}XSNe-@=*iiw*n^@BtaXFXTrUiNb*D!I7o(xsd_RVsgl@|< zx_L65NEK5l=i`na9Cr;&Xwi5ui}+p}XX^AHDzFaade@kw*erc*)9rc7HR0AiYjf9% zQw7BBrG9*Q)RD>%o3}M^=w4>3c6;zstAoBLhdxI+i_*adGwc7yShzkKs)`se>jyAW?s;U=>zzZ?uQp?#%~? zQbQ{B2`7gMDu|+)Flq75EQ2uihMxvQ@dx}j@ckLIU6a{(I$A}{g^fALxCC?~X(X#;uhxdwJBOhO8l63ofs%|(OgPf)i z>z;u^yiMp!tr}HT8}^jmmh4Q5$5FI;dbgz1MKCN;w7G$Zmj{qCQ=^^)(Xf6cOKvMT z{igvgx>PCCN~*)=o8#LCdNxjxHvuyq6^_Y_JH|~qbXv_+gGLJW)h;^0|xTF?-kZqJK) z&p}%~Vt*0YNQ%~Q*Lus4gnh;OOV;&_QO_d>m+d6elddGr;hI(G@M_mal<3O!cbM+? zeP9lZM0XW-IT0%Za?Mw%WF5k@O;o)AV%p4uV2V z>PFT*p+$T*z-QBzF0pQ@hs*|d%-nvt;@;q}gXMkE-BtqGHif%?7w6Fb0E2&n0r7*0 zq`M_;Ovr5e%0ew^-DAk}cvii(A=M{;a0HLORiRVl1rgpK%HR+8xE|E&IfjlLe}5ET zbOWGFk71PX%&B^)@m~-L!s=v;cH6bEovfIho!xyYeu4UL!AQA4q4sdrqL#-F`h88} z9i_^fQq?!5GZodbNKV8e<^8U{?a)f zFf#>A7AOjWOo_p+o2DbFUq35~^v}@zd5}F<*V!XQAs=+2=d$hP?-;CZzI&$KYHXp2~x$vIq5!M zOfoO?2$7$Z)PXVH{h}&-0usxt07+UMpX7O+6mfAMg9VG~jo4MoM&u{SyASbGM>;gi zf}pTVhWTQYm*u?+yzM+`k(bJ9<-K^{PS?jNY(Zk`d)9cl|96lz_b&J`D(@< zqC^dE3?PTT4%8d#W9yv=d3HqM~&o1vVeV~B@5 zk1eH{XTu;4T+6k{`n?QQjAakk7>f6H(UmX~2!Ho5cn7(vQhMbB+SBtnGg3|V5S&yl z7hHoHo5h&0n0VAQJ#kGY0W{;kNHkVOFi5k=;V(Po)rf#RG}^;H!-h{vF2ki37)dOS zwdFKmTg1-sh#~fsnEK1L%Ql!by=~b5%#9s!dfmBW-4?G;A{OS;3!;t}e%+Yoc&>Cj zt5D#On3}p7;g3Ya#;+?7#+>xs{ygEeM?ZA?*`8bLMdNg(avF4M*_PyjXD@cN$#!OV zzq3~PmWpDcH4@Q18P?KVDIDw>O;+X}lmOb_p;&he*BU%s*lCTGzwp`TSQ$IWh*OrZV?XQCiAU+1SWHA>u&~fWZQ$nK^Dmez!FXOLlN*%`^YL zUgVOD9CJH>i^yA_Rjcu;t3w<$AKyt%CcC>T?4}P!gn4MC-?S+= z*6W-#r#sWNYqk@aP=x4I72mZ1JwBSx<|c<=3cCt*Ms=lLhbcshGoFx2&`8#2*1mBH zN(Ik|4XKaJox`@vSA(Pf9Z|{9X#UklvK(r-U@Fpx8WBYAd$@>bOmz6ko65-TJ4_nd zADweO!(~EF!W};K8or+ykX4B0-HbFw#W3ljiPY=_e*x1ci+R-I3dw48S$_xO*-+HR z)c6){6jAYBUx~(ca!+_Kw7ljXyK~FMOE?3=U_(-4^5B3g!>LTEBKHunzJseIjybuX z(ZIR@M#2iCXEICCg)SE#U)X2ymV*!Qt(`)vj*W8}3sG5*v5lf{cB!7{%ii>g1sn5b zS>2V;>!7u5al8uV0};Fe)-L3cvz&>#uMD4~!vU~InyElls#8t!xZ8y*XweO|KU-|V zzC{4g1Olo!B7#-D)Eal}!5s0~xY|;vv{+khs^Q;{<6MKFn-!~YEw5P*dOHVnTYF!k z_J*BzCe+>rd8WD&#H+SnrF}VY{NhyZ*=MVD&0xz45sTgkiGHzH@FJ2{WP(ch0CoG_pDbZ@+1`6eskF~D_5lXiG>Y(?9*PciE?pY9E(?vQh8)cOLP z-xFd8ORRD4Q34zJ#}@}R1`QdW;y3d<^Q#X&dqy!25}tkX6ahh9)VAn@sZ%n7@KncA z)5zqTf6UKJ9aMb!!m61E$SiR^M%!kVZ^}yot1J}UBVQ3gEEaV8vit#Ur73Z;9To=w zk3oKK4AD`sMR&_K_ES5SviFinrK~3b4o4#_c{nyB*O9cWpE6aCj@HdY<^<&9&N-IL zK9}0WOuwvy3rKJ{shUGyoBL7ofv|Vbh)PtzRrpdt+1342zrLIjkh1&?`YhIK&&T@mq$9i z0WB0lE>!m`0iaWM_uk%@2Iu2+bq$S&HZKA^w5djn3*CAK#m#eB(S1LBMX|>x>ez5u ztNl)O6KUNA;pd+_Tr*{FS3q|@XKuB95f;(ojaU=Z8_5v1xUB?JC@l4-g=E7hMVd_wI) zhgq-9BdJvEuK<$CC;jEU3XmE7?FAtEE7kz|$6q)IW}@@HkvsK|ZuQxSz=^~bM=e+L zKYZc4GgYMgHO#n-LZ)v8@Z_wZu3b?9qLkDe#S<6CxL27Me3&VJpRTgp4-X-9eNq zkLqiUPmh|7D;usjPcIh3vftelxttHzcL^Rdhdy3yr3Sj)bphJ9fwi^#{Zi8>ggFJ* z2r^~4ueZ}@kD*bD!CgW%{W^?xl~93&3U@?bo2gDlgM3m#mJs<9JyljAz2)}L_Zm&Y zn)_Cw;o>ORHH+o6@;|p>sPaNaH)B(TZXQ3S?_Y zXxnY9f=>$R9a(WvlgLGt9|%tSOjbPX9sC}V0E)$}k!QUX>i-GvzS@bY*rnF zo6k%ORuQDxDTcF35G$3ion#fUwOh4guN7$4T=l)CxZ)?Cj&@%ug$_*#%7gxhQ>Rk+5rNZwIu7!Jb$D8_rQ-}xj>U3Ee?IAsFHJrDZrbJ~{$Ik$=SM*s%NpH#I zEff75+}IIvnaxjI3d8s{ni!s0k85f)c3V-Lhx)o%2}OE%UR(O-UI;?z-!t((t2}N_ zk0H^!Wqu}6I$Z(pABPW=fvr>}G`I_5WKzOB8|V4=h&0c?&X|8mP1toxvvLe8QK&+u zZ%OrAY{a8^ghj@hMpTHFJpvjXE>EMfc5jVzIZgXF=y6G@0jfWbzU~_2`%!0meTeQi zhSEQtlfZtwBXF=KvIV}Cx**&F#cS2;&NEfe*#;;ADNI%e;P83g zaC!C9q7dHV$^VOL09zZAieGg<7G7#}KoNr&=*@VZbHWLo4{;tK|3!tHfwr)%!35T7 zg1@2&27d1kpce9{AY_pA7YOXOkd(X_(s8PIKeXU}JAHw=1`e$dmOV)oaAb6;9Gc!h zoo`0x?|n>7M(K~ETaLQuhoqSI_Tdyof&QbvPselN$$bDsyw`s2PDZYh1o{A)aVo#V z14WVRCt6{DUa4;!XAnqjAU)S^pn|mlTXO?QIXg=E(a zXQQmEjBP^s#2*%6;QAd$TnyC*Xrrsr$(PNiv#pKVjw-u}~d zfQWH#hY8K5F=VNUHp^vF;%v55mZevEt!3?$dgjhj?=8tG?BhF0qCNGE@ku72OHku{Mx8P5 z!sUJxKS(@+j&GRnohD{d6KvF{*Z9Z860w1Y9BlPk(7 z1$mTTpxzHype?=xbUls7kbW=x^3nrkqp=%A$dhjS)qpCMCyxW+JK0j-u}nSuUd#Xe zI)o#TJ!~EiXVfzIbx;f={Ar6A&rIKAg)AeA$MO~!I+v5R zl#QXtgkq^f2ExMqqqosF|h%>nOvrL!(`c?V$3iGvj5Q9BB6Z zXj5(f^4kz?rJNIZRZZ8`SdGFxxp9?Yxj%lW4qWbeZm`&7D>^gEKW7avfBk94DGeoS z+Q31#9w_qo9V;v7r)BeE6XaB}pl!Se+x4)0d4+Q6!jG}jGUH4j{h}KCV&6^b!SVHg zZTn$e#)ZA;nev0(p^J+;7oZVQ_x5)F+}s?_{ysLaHfzVD^sHeNfu5u{l(K|5ud;-7 zcV~puuj=CGO(=G!*tR`gaW#FjbajLYh|n};qf0oJmX`P2fbrUBKAdW?Kv(NdKsvru zvX`9{!5cMuTy~!m?uRk6)MR29zl>LB(OcTNlB`4w{$yKu!jowCFD(L)Gg|EhZ9 zYDZA-@HF<7K7CPqL^KJV^7Mo zmEu9+BU#H?tDbu_SgWJBH_OzJl`FtGsAiSCrp=ySRXJ+ZZVk^fLsG;27A(^!VFp6;L(#f4~r>mVi!^V6XneDP%_psWT7XWaw!w_?`qB z@FVZB!Q3HaPueZl4^Fie)fjtgglK4!>{TbOxxc2590+j1vTtF-zD~za8JC( zI=nCAc(~Azu>um$w%~}2owfH1yl@!dK$YR;{(``d*^dPTov?#P6Qd%dw>*gl?nWAQ z(Fq6VK=rAaQRW54JX9#Q!gu>%(`-R%v@YKyDDIu-I3o)tcL~=sP8qE*H)HNt6^8|f zcTIC@lNaJxPh*v;rn*ya56hQ?wh1_eBU?V`sW5GGd(nqj+9A^pSb6%eraCy=Fa&&X z7;y$J52RXqXCj&ZBOopciVf2P~F}()1 zb?8hJbcU;8y)a(p6-Dd-dKPx-{?#XP%MY5>+dQs0w0!=~=VEr@&;JS<;`H%@F}TioFH7ca!;R_WtWu&9yzaR8_36F}Yxsx}>6-z~`KnVE@~I6}Y+ zMUZ-vN&gUk$VeEmHmMA7zXEObobc&Gd@Y;O-)Nc791G3YPbq7bZDBk@!{FU@Z}2C;z0JT&^5C?u@mFI$Jcu-hl~-gRXhWR$O>D4au+dS3?-dto4I zR2jyX9bqs2$sRqVA_o>7<(8k3;&;}f=oLu1mG?@FE23C6O9){zKx}Bi=($s{7tnqi zQFNtv7yz{N!5z$i4=rTS)Ah?1-;+*#kHq>*-2Ju;sErd}-9tb={=5knW?ZaP7i!eW z25gPK)`>Z%)vYj2V_caL0Vh)*&$*6d*SnPTiG(-LF%6lKsGhvEojf3UJ=$X@ z?+^{Qp`A^y+gYEC6nGJPg<}Q^-ZJ_38 zSL3g>$kFeqrQ0d%VB2f`3dh*%d!=5mF}AXe`LfUyq;_(btOU$C>KASEPhBN#3ZnQ8 z)BTFeuy|@qS`Cr?Hf6ykFoEdoOHu4y-CiHzVbIF*97UIiEY@smr(8SO0sB;-r*zBZ z@LYG`8KmQW#VvaM-48oC1G5vSR#P5O6;FSR(^ghj)7I73cLP_{{em8wQUo8EWO>kC zab@1+ay#ff+AVZkeXS5?#>H~b-#p136DbY{0L3W%OWP?LEZk)kv%y>I7^2fU!$U~( zq2;dGV1DjxslDJlaOTfp^YQUSc48e>-DzZWmI)G}t^bQs$#SaS?9JyKZjqLsxRFF_ zOt!qYXAT|(Zmy>}dW*|5t&iKC7@KGRe`qL4JD> za;mWY#=Ri&L(4(8i=C#+K}8g+=4U2Vs1(k33))rlCjs+|vTRNvm0WHm62r&FaVte;nw!1C8^x>Sz_JxgfsDi8rPsQFkC zOi@DSdfB+Ry5eb3J^_*8jvLkjme2nyeZr0Wt_5_J^PdEOU}miX^+BX6Fe|kT?CXd> zkpf_31SVRH72zkSK0}b%_Z7!6TbHX|lz@|R5j;{g`O}IT37DRT_0IZy0@@`X{fl2bwl!!4I6r>>4YB**x2Woc z{(BbHf8SQo3$XtmR0)po)Wd`TT_OQNrDnr9Q2*W`^?%4$fqcIaEVsCuk%a~0n1?@x zxR>zW#?!ttzbXQM@}!7XzSYdd*x4z+d6b9xUODO%eEv8%;@^QycKMu-$VSqI!+?!u zNwu`J4oLtr1vg?VYb`yMer>5~CL)PFq9=wiQn1LT!kOXyGVk;@FX@Q;g4n8aN zHeYuu-gbW13&ppOh9E*V*S>dAslGxTf0l^ZX$D%6q7N}(zY_RCLa+un7xL|D(|v~| zu{M&$*r#xMACZI;enut0Q!3lnew9mC3oc!>wceUe}ca>wad}*r-HJS*gvd1n2 zL6YYgOZ^zI3QBzauc9sqqmR)5b>eEH``8tEEvWN~&Gwjf##8iq`QU@qBi;RZx_)@V zLDeiPg8o4L4mJ!r6?8`g7i%mRPB+Is zm;T>#zlP!DI4(wNhu28{fZ|PPj=!PbCn1=`#q1srh%Z$qoR6oL%)T8Yp#PPf3CtS% z!I$00@v#fXp1xigBo_hr<+I))e0zn~etBps;p(7rG#f@zNZ5`@==E8QBKrVa=mNd+ zK!1RxG$qKb-AN$LuNJiJiZc<>vFWnQTxH}tIIPEU`t&>{rEnj-FuNOsP%m&@)V#`J zWc_5&blKTLroL@`M__k{dU>C(qF{cs`SGIP97_LMle5ciJ?My9x3{5|tlV=&Zr+Hp z0dQFz&{2Avyg0AcC(c$a4zZ|Zq!4y{;^gGi1uW?278dYQQc~8}I)i{kdFiruJmI*R z=F-drO=zL4rFgK1cE=5WGGV=xZWlc6Xgy^?ql%4MdJ`rb3&h$SJj^U1CnLpG+L*8t zd(^np&{w}zbSxO>zWB6#-_`l780@aU9W(c|`R16ZyrqgMN_cqbIQGa$&Sddsw#~KD zdbyr*ahTtwRFq$Hqf?G@%>}pZ%xQyFwAcAd0+HxV?XvZ9GY~eZa{nKN;vd%3{}f5Y zK1W`)CwVGK){X{b&-$@?_eox}=CLOrd*ghP!c?M*o++CTk1!BQ?z9&6H+n;5&aVqp zH%i1clB6Yz+l!YR8r?|b#!<^?{BeWq^_ZkjE(clw^>EG>PB)+EVne@s-;W}IW6kBE zQwtx9kkedm0_B}+lE{+vDrz?NwKbFI)}b*as=W{{P>JE{v+LSP|7r>>+v>tL6|`o% zdT>~`D#i}+Mzc@@Q0qBJaXlgJiQL#{S?6~)dZV0GB7KRgu3hQ%k%#B|$7{mUt>uTYn2K()RWwO0 z)XiU;MAi|42k!GGH9LhLE6ei_e^)+=!uVl}dW`@=n7EO-uC;wp9kRSt!kb-3la8Ig zLD1vdIa+#)wzi#m4Ain7T5KIXkKFbvoqWg7I(aBCzqj>uaa5c|cf_YX|C5bBVy|2U zf}E;p`_A^PLi9g%Q~cjc8VO{t$NqW&{+2hKSWfL>yB9!t<9~FD|0X&`j%~629T=>Z zxB9Tt+x~#qU;q2Sl|!&L6hq7RuTHW?G#IG=Jtd_51JuUd9sTa&_|8Z4?lW#nx|TOi zM;;C`=U*tIrHVhcJ>Ub?H#P)^2Tf{rT!s(kX1rIdtIw7 z-CQ?M9J6{}IJjJ3w@u;Qr+&Nn%Mmi)y82I}Xu)}}-mMdz!e(zDa+c@L(WH%WD$)H) zGs4mT2I~9jT#20Ei)qvL!=HnrzlkZDHed(2%?1Y5@z{908g1i5t;1=-mrp-o?O=(% z>U9X-u2Wt<)d1fM9^FfL$duQQ=()IK7OKWg2KhyQV!V^n<<2>NE;B5aFs=ONji_Y& z(Ig5ju-FC)P)4kp`zE#Z%_Tosp^z_3Uv~%9K#L1`=>h%{jcGUGvo|hgN&X0V`hyF- z7b$>-usu_pW;1Y_qVVg~`#Qpm1v|a2s`f8yf#_p~kSoIYDh#0wnm2fLmhgNdD*f?` zQqA)s>1`hmohN8n)U8~Hj6CQeF(x3ER{xklC19w;d<f~Vgt-(tsE5D=l!VLXnc+PhamPI>!{U2p5`S!wXB$teJ*T@6=u>&w!*AEw#f z7pT^J0eEmeK;{$wRbqE=wphqYmvC`X7%ny#$mN39m~;pmovJ1y%3J1&>cO@_n_%W4 zR_apYN=xac+|jGDs3~mlEid`FQVZ$sD;G`V+*z|?;f#YhAUu@5XGBo<0NukDsZoX(Iy8wUuQ=3KsOz%1aDB=8wwLXCZ$UGyjNCR^lqB<@;Z z<}x?Ie0mvePNaHv2Ex3$4n8k!4d#mis=$(XX|CkJXGoH}$0yF+mh?gLZ_+xiQo){D zK?1SyI7yte)nXPji1PMF0%)@SgAFD(s*hzxhK}FM9QUY|IV~)@b>*d>S3k}DR!2#u zeG{H>V}>W1I9W=^HXCZ>jvsn~S`2Ph7*}U>mf%Hk@COyQ1}yfwvenj^p(5v zH%h;|PBzU_7Bfhoa-<0?`H&piA}9>|jJz4$qf+cUIjA1GFk!@Pp&&OC9U0Foiq97d zMu!`Zcfkaduf3ms-nA5jUrYS~0KOJnIQ_w-CU|I_=4AbDFxG)3A`f>TGKA&Xqv-hr zG?ryZttQSuP?uYPQ`d%AYO0M~O%(G3pZ!A6dfuG0W;#E`yI!Wk&mZt&+@Dt;)!M$E z4D4oBs|=!QfmwMFm;70MhEu5%eEA`anoEt|2`?9LSU^wOjWXN5Q@7tcReg(9`FVj4 zF4-a1PVE)}Z-9#?dG#`Uv4CcA(%mgbW%RupLquZ<11h&znwWId{N9i1lAgwPB)D-d zF38@P+XJt~U73I~kEeI1KuyD!^7*WqigQUp+}Pjv%!0Kkh~%VosrEA^+lGkrNm|k7oqC;K zqI^@GLo9=4&DndOFt=A9bB1K<;7hYBkVUN|)DU-6x1>PGlY21b3XcW~^-ck6I zUxoJQ7hV4>luJQ7nDwL#lL?gxeAiAEp`}V330G8sa4yQ(dyY_K>LVxmVT5vUo4uSq z!pw-wRlg_N(de0zOq}uczCo@3r@RBe@&Tmc@7?p$c~$>wFBB{hjp^>qup9RJ9XQ#8 ztJ7)pRXMpsK41$}B(b-n>VN?RGsk9N9VLmpL(MZ4n%Q_Ox~)q2`JpkL&z`Z}a}A)! zNi)^4nvuF{zAO_@PjVnzLVgbB z7*0~o?boLSJPNC)XoYl)vM+(xp8!fAbj}I6oDcA9D-cfo;b- z{Xf27jnS8m`(5}TdH-Jg5a% zuKNWCATv=S;xypju+;qpk!U~Kc(423BU++fYB#XFQ4rRBP)O6DQPnS-Z$}009b;6* z&3kmX6Nny^rtNM)RDU}TU&~18A0)!M3!}GGIzU#>xC;^fZ5D|}^!bnZBjyJNL1=c3 zZHTX&Ji093c5NOLBS(|oV>q-Blcoi(_rn=tU&kZ4bqr)i#p0$}qwRI}s!{G06QJy( zqV`~Vh1}9lQcFkRx+lF|OAs;$^geOepH|@Ax{{~+g+WH&yQ|XHL_Oxg5KUm{R!kT2 z`Ed+_*SnoC_PCZyIF%WV$W$%mU%2o5A#6GN8A6N)$||($ z0z~z~97*(j*7(~A(KA)SwdC#L_6(8ioa}%{z9`(-ylE&hA}l7q0wZ7B)pH@IaVFS_ zg!44is?AsroEGhXM6r}Er`mtz;dFQ|EkN(?wJD46nlRQoNW5Lipk4CDkT9qU)@ z-FA?<{zq34y%e2vBBy*Jry;$kkE4Vibg~J=>m28|l;O zm^TFU142FiDn=s)xz>!;EO_+(QZDn>_-vwD;*2d?7zlFoyy)%0vL7EB5A(y~pH)8+ zuX4dO<`hotywb+Y@*arG->);X0TCA^NyFzomE7Lj)g+ab=gQ8F+#P9~;$(crHX9r` zi%U<1tDrwOv`;0uxVV@XiDk`AQG6Gffc(wl87G zhJ{XnP`9Y|ej|M!VAl%5o;kg7PVc^Fhdc0yVoC-;6033bnsH&q1*Mv$O++h%D$fo3 zX*}Y9{E*y}iswT2@ZiDug;=7Fzm+#?Z3<~ZR6`8QT-y=jv~E$>Qf!gS2AKF`kkEpu ziz8@f@v8xu3@`~eDMa}jJ9&$L!%rcd82==NbD>*SBSKvx<0@m~lV`7kFVoAuFxHR- zbncL+6wKRKXq&2xX3C01(^_B%SiRM&zA}He?HqIw=s@nMp#}SvcNE!h)kB0Qm|JZMXZ}-tCTZ9aihC;PdPPhLSm%tBVp`sxOi&3aDQn<9>UCb1bT$PR!O5! zgY!!C{F8&H*?ZiKhrQ{PFP7w%NnZXQ#QlPI`p)i$W80R~!2L^LT+r+e*?1ut|2Vso zR7Dy?G(!eQ4rf<{sV5IP|Z{Dj~Q z%=neM1>)Hv+{9vBrL`-gak;%@2jOtU=7Mx#Fz*3J1;zD+3f~XQeA#w`q0@QqI9rca zi8B+@CgGo?dF*B?5p+clk4l(dHI%+CxWMY)|1q^vG-`B$_>z1dK}(8HC1FwjsY*HB z6OqqbQP&kY^&X9Nb=&l@X-HoZx*LsLQzs66Tl8Y|HTXf3zbD;!#n}7A*&u5xal@LZ zJ9Wr`s7J5EZCAZ+kEj=GCn0rEG8pWLfr;rjG&{&;su1%m$P;LC@jEw>TKJB!!S(!E znRX)rH4@OMqgE3$P#@^rQBl_xxtGHTcn-fzlLFc*5L0F2`pFlXSI>4EXSd`b9Mm=2 zhSsqoI0$01;MO~4er)rHlYv1jS~%CsV7_qey4)*_;#n@bTL=c{m)~tJ`ryL0+Hbb? zCYt{iCg|f!J#No43Y`C%C&*+Y$NypCMD6DIqYU7z4?N6lTN0o)vi^sm07@uQ(#O#&`Fi;D77xJX zXtG@AbV@&r^y1rDbbLP1*P!Q1H(8KcRNKBhE@_K4n_1p>o!^_x^HjV|xRF^$F$eOi z((%zVfCHqj=KwO&yGv5*I1;&YbP7e-bvSfv(~O@ktvE&m0sS-@!4?H6_R##_E?JHf zgZYsY%C>_W&r(JrVDp)Dv7gTE-mG4`9KXbioE>SU;+fQU=p2vu!+9lx`><)bV|<>y zoVE6~C$*D=N4*CJv6Uh0@IrgRDO6|=YA!CPwgoNE#9XC zxk&Voy+W z`7Jlug?7Dstve8@NfhXpm&(OP)|+YYQ@|`PER4|cll)WO6)HI%)KDisz7r`QBwf6E z@5NWvIE&rg-DOpEL?51Zyrg)J7lwQ!LFxZQlVgrYBa`(7-e6^k7pKF84~^7Y&MBpA zqkb63+owONlQU>15fSEM61ZjD_LWOk>0&`KXCLS&=uPk_UdM1CZ1((1#laj}0Q0CZ z&}(_2#kjf-bckgQTOhyx{RyWtqEnZtxIr2?z^YlH=bY4oTBLQG7IUZRV=k9ml2CTp zQiaG_X@SDEkz%*ijtuDtfy6{dH?{GZaI)itwaPVlDBVEB&oq*#{0GKoNgBtF7mzda zQKy%rGes8<_R6gknM<|PKs=;l-787AArwdN49pscvtc)19oiB1BJ<*u%LSO_RI}yo z)UQ2m(9zm&3#}miE6-cG$cI`7)kZsS=*s!E#E$DoxiQH&NrF8?>NgP(aC$|2Axa!lzyuz<6#Fkt6!55m0I*x~~%NA>&Zl;Pj$Kf5dI^Fsc4GNi! zra@fAosMO=7hZZ4hYUvG(MIa-bLnTA9@Zr`ub!e_ zc(eNAM*rDvFhpge(sFD{BakYn@g{O zQ(SFeSG{z7+Ix|qalv#u|k?uk z5%Olw1BM13c*;j7*#O7;o}YeMw9iqev5d)3yD?G=bkr27y)nRDW=nqF_H}lvy69$Y z)@}+93t5+}cjJNHc*BCdc^usdTEbLg^hV65B=carX3AB)(k$A|WPnKV{HUx6 z{j7V%Uo+<=`R?8{(8a5QX^+8ZT9+iYb!8U8rIrrlET1IjIp9csifw)Wl%7rR3hDI< z?z9ZMFWv1jvpux5*{64dx^X^itB!Z>y_5HXM7*fJ9d)fKjXu0!)^sIxsur(V1(6n& z$B4B=*KvLZcAN1wK{4*NGhVDC>y6oHm>ew#4KqxrC)w~iF6rPLEERO#1ssKr^=bje zi>gmrI+C__Ej1Snr)NW)WcN8*hk>RMGpK68dVd z-?BmY8Iz8VBD>3WY_W?x&F9(o(;yY@L5;pTrugS8223V-`?{C=)9F7iN>+yQ2&yLt z9O;IL6cJ zx~efHHfC*bW=nUw#CYkOZ91QC9Is)xn?8yAe7b6BNwp!ClEV2Ir5k~vr#^Y+oU?1Y zQtkK!=;j6|ce<*#S2fAnYPP>P$Ncy=?B4il@aF=}81e0z_|>yJJ<2zR$n=@?rj0!M zGH_*|=-d9x_jBws4OP`VA|`3JJkXoNx+Ff!jBl7|V%!OKLdg@#D&T&QgoH-wBTlXG zVH@k;i&?Bw6@Ea$o`|h}EK%Fmx_q1wM{ROK7>;wT02~5T*i}A^GTfLz$QUBOMX?v} zt`>LdVsf7PvQ=`ty<@mvYL?aftvT{8@Th%f(zMmsPZr446S;bJXw|vPkdoCsMgpU# zlJf$6w`<=E+zRz~tg@+VbP<5h5lLOnwBO*?XSZP0$aBRCR8r$8Y>Va@(r%Hw6(V`(1$=S?1ABH}o09zkq?SEGn>4pL1=LmQ0gHxj3IN*TXp` zip`U<1=g8w+I4<@O}73aIrMZyB1KEds0)KVTBI{TJ>+9uWbyFAi>ShJiA&Y0fJe-` zvmjR3RIP%Rv^BN7DdHTjp?S_NBy^UZy4jC-#r0V1#f@+*DC>hLgChCQgyV=FGxiE= zs`Bq*;=v^u%VF$QqhVHNsV3LfbawBHJCD_=mhPF^X_nlBp1Tp>hYSG_;87PI-1liy;gp{bBY{325FU$Do$ChU5f77Bt1P7$5C z7LuNPh3IZ79;K+N;hL4rDj8qpI%r-w9b323XXmkW7Cm2>p*fy?>l%h7OeJCB#lM;i zA|(&h*N1QoVB+@ig~Yl1RTI|wv-{!d-o-x;z$y2cy(NuXW-T>Zt@tBUw9+kzP{-Ty z50#V+_w{AMS)$TDM}xD16} za&+M;!s&DQn-g$ikmN1iVF}qpMn*XiO@0q1@-ts*NJ~K8Nc-*z6XC$k}FQsNt;tO(QmzxA51M!OKJG&RJT;T z^$_@P{TN!~(42*O?kUCJ;y5{F^+E$OR69SUP3c9*r9EhaJtNp6JaHKDR9~;)d07DV zb*t6i4mN$|aZ{UC<5g=_4HJ${rf7DO!`=1D=l0e}C{5Ku{nFRG&1I_`b5W^0jkm$Y zZnEwwL-ag}8+Y#+V>5r42wbO81E?iI*09mX=Lve_t2{6IZ3P!M*#;e*e(_qUrR*-cb*eNRdEZ^%>lrcCAhb|^KI0;UR?19ujWJfpNCsTP^z9`B@Q=Xw%S zbYDp+H0;bLvDgEWxzcwI1O36*MvqTWmxt}Si_~)gdlGk^O`g>FcK=hJ0i`y%;A9N+ zjd}6hybBh-x4S2_lER57v?<*RJ3-B2OBQ{SXHrH6q%7vl9I+~5_Rm8eD~(L7TR`s3 z#gIlJFV6B&$#g(`5Ap-vI>q2Yh~wZBnkxcQUqvcbhL0>bHhG{m1Dn+IUg*C>>4+C+ zkm`yt?8dLJY&L?o3|3x#cYD%IHsdCJiPUzEQup8mBQWZJfE&Bj2{jeh^1{;(Nm7I# zUsZ!^<*+jH`i|Fel49zIXp6h!h9g`1T6as7_qB!{?|+I)otte^2;>ZqjU8LvN}~SO zv@gf-I_L{V=BwRKkX^UQNwMnc-*GYHZS&jqP``a&p5N2@B$!%xwL%)$=$z=96(-t7S!9dCv2A@sUVaP zTKhJDY?2*4^?Kp}x>A26bSmoe?L6J5jA((iIMBo`!8U;|ZB^CY0nWK!l)_Ss+G8xD z2e*>0HCbDB@pnnmpt!EAU0i$QFCtcIqAv-~bX~Q}BDb}-uPnSUoD&|W_0%+4sF?&L z9o#0;ju-G^GpK18{S+cl(E{V9|{@yBg7RD27?%YG*2_?%K zF1XA9r+ys(A(@1@zOarm4IQm~67jVXww!i`SO$cgYqPG*P7E?LOYS5eLRI_g(eID4 zW*);metvDm%>#4;#?NngV#Q>>VlN4^sJbcej#3$7%)3G(N$*aJ`1I!w=$`hxW*NzktXPq=@`rr4We6w17JZZL zW6$MAV~JzSlY5)kS$U-=-92l1AO2= z@#|2DQ~w68-m66@<}mb~!1@1FXaK5Pen|P%bb%_i&TDI9&|^JV9iyZqks9j8#W7S{ zqk`UV(~9Gf&`38&BHWNt1B;8hLU(89AQwl)-cal0ymN;X?(i*VQF}kiu*qPy1{M)< zWiric`}#ce3{ z7rMQi+Q{v>1i()^e9rjgHidS{YupAPL|t2&Y)k+)ue<$*k|AB~IIWxrw)h9D>X5UR zG1X5UV!YD$L;$m}4=6em`Q%O%$)wnPKk`y&(@P|0KY*~|l zQROQOAJqNSFRup(t6*bgHPTO8p5cH6DvQX!UwG>AKTbIFf9owN$H%_#WBLHhNpZ+t zPxMv!wjK~p{j&<9a5)CYI1+=U`%##cL1aU4Y$=Q!&b<(oI1u8KD(Fl0Hb+a@6rKKD zz;6>}3T+JaE(y3qW0+gr^%`ChApq7S<{MFEs@SbIoyW@QJe>ONCqE;)X%{{A--Bl8 zUF;24SWShM=<`nuUSO0st-#m#;Iaqy8!{B2^dNu2igm!`suSM@yTM94Bu6Wq3q+mY zvu%3dPs<-o#q7Ds%dfwa;DZOnH0{UiG=M06O=5do&Vsc&;jiywoFai5j(n!F36ux{ zlXE&n->fHo+L&SprHtYKZD5k=Xc>JFmzJRCvp2QZB#Fj>~8<4w(Ukdodrv;xt zi4xXXwEmfy8*Da8*`*?U+N$-MDtE2j5F?8+H8Gj_)AQltf4FY^^{$srRO$BK7fOyR zz}dtA+DPR{73|eQ(-sm&uCf%@Tf7}&qmoDgvfZBY=7TaF?D`#X2K-$2&eW@njKzI~ z)QG7sl5ft1UaX6v4TrZz7JLe71;>@qJvnm_eB5DX?zx#vESG^2!xn?wOh! zVz~0u-H;LcM(5e}0sic2D}x<3k12rT*FLW6AUCQ*vVlsNyc`HFv0Ue!;p-m}bOnn( z@96GFwhPzrzA#E_iSha)??$u(z9iZGvMq1fM6t7guXI!U2*|&LC}B{{AP{Y&YByfe zA4>x^fZvWcr`jH?6N*XS@*B6{V|dKPD!0-ID|4Tfiw-s@PSVxXQogF}ikNIm${+A< zh?%R1-fY_Ni{Yyw?pjhx%Gv7VtZ{B}Et=h!JDW*q0=NnoMIA@&kkSv0S3aLc&j58h zdyja;UA^){vz(Z>oOA`j>llN*Ug6W9Z&^#l)J2foqI!**Mfz?8yEY*acB`^y1Vya3MB@j+;SnZQ+77-@^i!s3O_nXxlhpuMZ6!^b) zCyEf1m6aJWp|mf{9UTv}U?XxQ&3SQsM_VUDX=FT+v83I&lr-DF=R9WTg$bM<;@iH&jH^PBvfkdS`oJ0Cn8_sVM`)Tn zC$>WyYv?qiwY9NinC^rN}$)mT;@|B(}b{uEacGFex z_I)7ic2;Aa?CAw21s_RsZUE{}>V*Ed2PQbDHa(oXPeH7+{Ha49Mt}|iGa^y&skBrz zDJjW6EX=$Lrd|Zv-waGeXA0IsmWha?jig5$25Ia^y$^B;Bw@1EZ~q3C77HX-!FcjQ zLPF24oC*nsR>53=_s_~gu$*9H^-lQFNSC)F79_yNS}Nh7{ZA*W?;H;Yde;UZC32X| zg38_!AD612Uhe8Rc737F`oo70+FKg)eI<)nBva{)0m32rk2Yh>&uQul41SrkmO>}{^_b5ML4eT5-&|iQbiW*1Yfm2x1>|21Jq@b^WK)Vd9MyixazSkw8 zpRbi>uogyL^j!W9Mad};B~eNOj7G%z&e42wL@X^WyV&cU@@a?CY9nbDHJFp6Mdjn5 zU8)AEMyEj+)H4COwxg2B%OGfJ@RSq$K;f;Z(rB5xil1&ONb~{je}z4b_MdDM4U@)* z15UWPOIs)A60i&dQTW^1Y=_btUnd8`yF_2CKq7GEb_;oqWO+ZfHyR*f$xnj2rh*2pZ=3-a5fEHY;WCmZ_2;Odw%&(D`U(> H&%gc+{3Mm_ From 27a33fd103716bb94f628700c2fa3d2af4ce90ae Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Thu, 28 Jun 2018 10:22:53 -0400 Subject: [PATCH 34/37] removed hash search action --- .../datamodel/DataModelActionsFactory.java | 2 - .../sleuthkit/autopsy/datamodel/FileNode.java | 2 - .../autopsy/datamodel/LocalFileNode.java | 3 -- .../directorytree/DataResultFilterNode.java | 6 --- .../directorytree/HashSearchAction.java | 52 ------------------- .../keywordsearch/AdHocSearchFilterNode.java | 25 ++++----- 6 files changed, 9 insertions(+), 81 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/HashSearchAction.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index b4b88463c4..a5eb26cab9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -34,7 +34,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; @@ -82,7 +81,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) { 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 c723506363..823d25a137 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 3aef1e5a2a..199934d614 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -398,10 +398,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) { @@ -435,10 +433,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/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/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(); } } } From a05c5a7f313df44e011f6f1ce206853a358b682f Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Thu, 28 Jun 2018 12:13:59 -0400 Subject: [PATCH 35/37] removed hash search action --- Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties | 1 - Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties | 1 - .../sleuthkit/autopsy/datamodel/DataModelActionsFactory.java | 4 ---- 3 files changed, 6 deletions(-) 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 66f19fc52e..88e90f054d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -75,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<>(); @@ -365,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) { @@ -402,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) { From 2c0f856406955a8ffaa303fe3a6d8625cda115a7 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 28 Jun 2018 15:48:04 -0400 Subject: [PATCH 36/37] 3994: Image gallery does not work with Postgres --- .../imagegallery/datamodel/DrawableDB.java | 6 ++++++ .../datamodel/grouping/GroupManager.java | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) 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) From bd202f5fbff08d7e7178374ef7816b31d77b9e31 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 28 Jun 2018 16:35:16 -0400 Subject: [PATCH 37/37] Revert "Remove redundant data source name tool tips from ad hoc kws" This reverts commit 57d9022c525d077a2b4443a5f1964e031b206df3. --- .../autopsy/keywordsearch/AdHocSearchPanel.java | 11 +++++++++++ .../keywordsearch/DropdownListSearchPanel.java | 16 ++++++++++++++++ .../DropdownSingleTermSearchPanel.java | 16 ++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index 9b84a5c785..c06394bf23 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -43,6 +43,7 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); protected int filesIndexed; private final Map dataSourceMap = new HashMap<>(); + private final List toolTipList = new ArrayList<>(); private List dataSources = new ArrayList<>(); private final DefaultListModel dataSourceListModel = new DefaultListModel<>(); @@ -152,12 +153,14 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { */ synchronized List getDataSourceArray() { List dsList = new ArrayList<>(); + toolTipList.clear(); Collections.sort(this.dataSources, (DataSource ds1, DataSource ds2) -> ds1.getName().compareTo(ds2.getName())); for (DataSource ds : dataSources) { String dsName = ds.getName(); File dataSourceFullName = new File(dsName); String displayName = dataSourceFullName.getName(); dataSourceMap.put(ds.getId(), displayName); + toolTipList.add(dsName); dsList.add(displayName); } return dsList; @@ -180,6 +183,14 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { Map getDataSourceMap() { return dataSourceMap; } + + /** + * Get a list of tooltip text for data source + * @return A list of tool tips + */ + List getDataSourceToolTipList() { + return toolTipList; + } /** * Get a list of DataSourceListModel diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java index 17d5921e0e..72a1cbab5a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java @@ -73,6 +73,22 @@ class DropdownListSearchPanel extends AdHocSearchPanel { dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); }); + dataSourceList.addMouseMotionListener(new MouseMotionListener() { + + @Override + public void mouseDragged(MouseEvent evt) { + //Unused by now + } + + @Override + public void mouseMoved(MouseEvent evt) { + JList dsList = (JList) evt.getSource(); + int index = dsList.locationToIndex(evt.getPoint()); + if (index > -1) { + dsList.setToolTipText(getDataSourceToolTipList().get(index)); + } + } + }); } static synchronized DropdownListSearchPanel getDefault() { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index 06a0bcd8b1..b859df85ef 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -88,6 +88,22 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); }); + this.dataSourceList.addMouseMotionListener(new MouseMotionListener() { + + @Override + public void mouseDragged(MouseEvent evt) { + //Unused by now + } + + @Override + public void mouseMoved(MouseEvent evt) { + JList DsList = (JList) evt.getSource(); + int index = DsList.locationToIndex(evt.getPoint()); + if (index > -1) { + DsList.setToolTipText(getDataSourceToolTipList().get(index)); + } + } + }); } /**