From 311a6e9c23e091248ac6209c596cb1508cf2d422 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 24 Apr 2017 14:59:46 +0200 Subject: [PATCH 01/57] fix text retrieval on "upgraded cases" --- .../keywordsearch/HighlightedText.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index ffe3a66456..d464cbae89 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -32,6 +32,7 @@ import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.response.QueryResponse; @@ -179,7 +180,7 @@ class HighlightedText implements IndexedText { // Run a query to figure out which chunks for the current object have // hits for this keyword. - Keyword keywordQuery = new Keyword(keyword, isLiteral, true); + Keyword keywordQuery = new Keyword(keyword, isLiteral, true); KeywordSearchQuery chunksQuery = new LuceneQuery(new KeywordList(Arrays.asList(keywordQuery)), keywordQuery); chunksQuery.escape(); chunksQuery.addFilter(new KeywordQueryFilter(FilterType.CHUNK, this.objectId)); @@ -316,7 +317,6 @@ class HighlightedText implements IndexedText { @Override public String getText() { - try { loadPageInfo(); //inits once SolrQuery q = new SolrQuery(); @@ -328,15 +328,22 @@ class HighlightedText implements IndexedText { contentIdStr += "0".equals(chunkID) ? "" : "_" + chunkID; } final String filterQuery = Server.Schema.ID.toString() + ":" + KeywordSearchUtil.escapeLuceneQuery(contentIdStr); + + double indexSchemaVersion = NumberUtils.toDouble(solrServer.getIndexInfo().getSchemaVersion()); + //choose field to highlight based on isLiteral and Solr index schema version. + String highlightField = (isLiteral || (indexSchemaVersion < 2.0)) + ? LuceneQuery.HIGHLIGHT_FIELD + : Server.Schema.CONTENT_STR.toString(); if (isLiteral) { + //if the query is literal try to get solr to do the highlighting final String highlightQuery = keywords.stream() .map(HighlightedText::constructEscapedSolrQuery) .collect(Collectors.joining(" ")); q.setQuery(highlightQuery); - q.addField(Server.Schema.TEXT.toString()); + q.addField(highlightField); q.addFilterQuery(filterQuery); - q.addHighlightField(LuceneQuery.HIGHLIGHT_FIELD); + q.addHighlightField(highlightField); q.setHighlightFragsize(0); // don't fragment the highlight, works with original highlighter, or needs "single" list builder with FVH //tune the highlighter @@ -348,8 +355,12 @@ class HighlightedText implements IndexedText { //docs says makes sense for the original Highlighter only, but not really q.setParam("hl.maxAnalyzedChars", Server.HL_ANALYZE_CHARS_UNLIMITED); //NON-NLS } else { + /* + * if the query is not literal just pull back the text. We will + * do the highlighting in autopsy. + */ q.setQuery(filterQuery); - q.addField(Server.Schema.CONTENT_STR.toString()); + q.addField(highlightField); } QueryResponse response = solrServer.query(q, METHOD.POST); @@ -362,9 +373,7 @@ class HighlightedText implements IndexedText { } String highlightedContent; Map>> responseHighlight = response.getHighlighting(); - String highlightField = isLiteral - ? LuceneQuery.HIGHLIGHT_FIELD - : Server.Schema.CONTENT_STR.toString(); + if (responseHighlight == null) { highlightedContent = attemptManualHighlighting(response.getResults(), highlightField, keywords); } else { @@ -412,6 +421,7 @@ class HighlightedText implements IndexedText { return 0; } return this.numberOfHitsPerPage.get(this.currentPage); + } /** @@ -429,7 +439,8 @@ class HighlightedText implements IndexedText { */ static String attemptManualHighlighting(SolrDocumentList solrDocumentList, String highlightField, Collection keywords) { if (solrDocumentList.isEmpty()) { - return NbBundle.getMessage(HighlightedText.class, "HighlightedMatchesSource.getMarkup.noMatchMsg"); + return NbBundle.getMessage(HighlightedText.class, + "HighlightedMatchesSource.getMarkup.noMatchMsg"); } // It doesn't make sense for there to be more than a single document in @@ -469,7 +480,8 @@ class HighlightedText implements IndexedText { highlightedText.append(text.substring(textOffset, text.length())); if (highlightedText.length() == 0) { - return NbBundle.getMessage(HighlightedText.class, "HighlightedMatchesSource.getMarkup.noMatchMsg"); + return NbBundle.getMessage(HighlightedText.class, + "HighlightedMatchesSource.getMarkup.noMatchMsg"); } //reset for next pass text = highlightedText.toString(); From 3339a0c5b2375345d9665449d5a060403d0287b4 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 26 Apr 2017 15:14:05 +0200 Subject: [PATCH 02/57] correct warnings about substring matches --- .../src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 050e2d687e..df52cfa6b3 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -316,7 +316,7 @@ GlobalListsManagementPanel.renameListButton.text=Edit List Name GlobalEditListPanel.editWordButton.text=Edit Keyword SolrSearchService.ServiceName=Solr Keyword Search Service SolrSearchService.IndexUpgradeDialog.title=Text Index Upgrade Required In Order To Open Case -SolrSearchService.IndexUpgradeDialog.msg=The text index upgrade can take some time.
When completed, you will be able to see existing keyword search results and perform literal keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. Do you wish to proceed with the index upgrade? +SolrSearchService.IndexUpgradeDialog.msg=The text index upgrade can take some time.
When completed, you will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. Do you wish to proceed with the index upgrade? SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only -SolrSearchService.IndexReadOnlyDialog.msg=The text index for this case is read-only.
You will be able to see existing keyword search results and perform literal keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. +SolrSearchService.IndexReadOnlyDialog.msg=The text index for this case is read-only.
You will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. From 2900f122e8b540f43dfd38d3bd727f4ac4fbbe5d Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 24 Apr 2017 17:40:21 +0200 Subject: [PATCH 03/57] fix reversed group priority in IG --- .../autopsy/imagegallery/datamodel/grouping/GroupSortBy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java index 8239837d6a..20d47e2952 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2011-17 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,7 +54,7 @@ public class GroupSortBy implements Comparator { /** * sort the groups by some priority metric to be determined and implemented */ - public final static GroupSortBy PRIORITY = new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png", Comparator.comparing(DrawableGroup::getHashHitDensity).thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount))); + public final static GroupSortBy PRIORITY = new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png", Comparator.comparing(DrawableGroup::getHashHitDensity).thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount)).reversed()); @Override public int compare(DrawableGroup o1, DrawableGroup o2) { From 2027728f78c325799c01f4ad5bfefcc74717cd87 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 26 Apr 2017 17:19:47 +0200 Subject: [PATCH 04/57] remove unused properties --- .../src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index df52cfa6b3..795d6ef009 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -315,8 +315,6 @@ GlobalListsManagementPanel.copyListButton.text=Copy List GlobalListsManagementPanel.renameListButton.text=Edit List Name GlobalEditListPanel.editWordButton.text=Edit Keyword SolrSearchService.ServiceName=Solr Keyword Search Service -SolrSearchService.IndexUpgradeDialog.title=Text Index Upgrade Required In Order To Open Case -SolrSearchService.IndexUpgradeDialog.msg=The text index upgrade can take some time.
When completed, you will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. Do you wish to proceed with the index upgrade? SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only SolrSearchService.IndexReadOnlyDialog.msg=The text index for this case is read-only.
You will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. From b67a6ae2b6aaf95a3df00b6b1e3fed956b8cc073 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 26 Apr 2017 15:52:05 -0400 Subject: [PATCH 05/57] Removed unused upgrade dialog properties. --- .../src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index df52cfa6b3..795d6ef009 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -315,8 +315,6 @@ GlobalListsManagementPanel.copyListButton.text=Copy List GlobalListsManagementPanel.renameListButton.text=Edit List Name GlobalEditListPanel.editWordButton.text=Edit Keyword SolrSearchService.ServiceName=Solr Keyword Search Service -SolrSearchService.IndexUpgradeDialog.title=Text Index Upgrade Required In Order To Open Case -SolrSearchService.IndexUpgradeDialog.msg=The text index upgrade can take some time.
When completed, you will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. Do you wish to proceed with the index upgrade? SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only SolrSearchService.IndexReadOnlyDialog.msg=The text index for this case is read-only.
You will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. From 1cbe32c55cb1673f47aa62e0a36af4575971cd0b Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Thu, 27 Apr 2017 17:18:25 -0400 Subject: [PATCH 06/57] AutopsyManifestFileParser looks for 'AutopsyManifest' root element instead of 'Manifest' --- .../experimental/autoingest/AutopsyManifestFileParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 0fe33092ca..33ee129c0b 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -39,7 +39,7 @@ import org.xml.sax.SAXException; public final class AutopsyManifestFileParser implements ManifestFileParser { private static final String MANIFEST_FILE_NAME_SIGNATURE = "_Manifest.xml"; - private static final String ROOT_ELEM_TAG_NAME = "Manifest"; + private static final String ROOT_ELEM_TAG_NAME = "AutopsyManifest"; private static final String CASE_NAME_XPATH = "/Manifest/Collection/Name/text()"; private static final String DEVICE_ID_XPATH = "/Manifest/Collection/Image/ID/text()"; private static final String DATA_SOURCE_NAME_XPATH = "/Manifest/Collection/Image/Name/text()"; From f91b4ab656b896422856ba2b76ad8a8f38f052ec Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 27 Apr 2017 19:50:27 -0400 Subject: [PATCH 07/57] Added a line to copy the necessary JUnit JAR file. --- build.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.xml b/build.xml index a9835ab9bd..43534a7495 100755 --- a/build.xml +++ b/build.xml @@ -76,6 +76,8 @@ + From 3c8a6a05c547f5d4146ffc1ab292d0c590b82ac3 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 28 Apr 2017 13:20:19 +0200 Subject: [PATCH 08/57] use a range set to do highlighting in two steps. --- .../keywordsearch/HighlightedText.java | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index 1aedca4939..744fdb6b31 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -19,6 +19,9 @@ package org.sleuthkit.autopsy.keywordsearch; import com.google.common.collect.Iterators; +import com.google.common.collect.Range; +import com.google.common.collect.RangeSet; +import com.google.common.collect.TreeRangeSet; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -63,6 +66,7 @@ class HighlightedText implements IndexedText { private static final String HIGHLIGHT_PRE = ""; //NON-NLS private static final String HIGHLIGHT_POST = ""; //NON-NLS + private static final int HIGHLIGHT_MARKUP_LENGTH = HIGHLIGHT_PRE.length() + HIGHLIGHT_POST.length(); private static final String ANCHOR_PREFIX = HighlightedText.class.getName() + "_"; //NON-NLS final private Server solrServer = KeywordSearch.getServer(); @@ -460,8 +464,7 @@ class HighlightedText implements IndexedText { */ static String attemptManualHighlighting(SolrDocumentList solrDocumentList, String highlightField, Collection keywords) { if (solrDocumentList.isEmpty()) { - return NbBundle.getMessage(HighlightedText.class, - "HighlightedMatchesSource.getMarkup.noMatchMsg"); + return NbBundle.getMessage(HighlightedText.class, "HighlightedMatchesSource.getMarkup.noMatchMsg"); } // It doesn't make sense for there to be more than a single document in @@ -476,39 +479,41 @@ class HighlightedText implements IndexedText { // not see highlighted text in the content viewer. text = StringEscapeUtils.escapeHtml(text); - StringBuilder highlightedText = new StringBuilder(""); + TreeRangeSet highlights = TreeRangeSet.create(); //do a highlighting pass for each keyword for (String keyword : keywords) { - //we also need to escape the keyword so that it matches the escpared text + //we also need to escape the keyword so that it matches the escaped text final String escapedKeyword = StringEscapeUtils.escapeHtml(keyword); - int textOffset = 0; - int hitOffset = StringUtils.indexOfIgnoreCase(text, escapedKeyword, textOffset); + int searchOffset = 0; + int hitOffset = StringUtils.indexOfIgnoreCase(text, escapedKeyword, searchOffset); while (hitOffset != -1) { - // Append the portion of text up to (but not including) the hit. - highlightedText.append(text.substring(textOffset, hitOffset)); - // Add in the highlighting around the keyword. - highlightedText.append(HIGHLIGHT_PRE); - highlightedText.append(keyword); - highlightedText.append(HIGHLIGHT_POST); + // Advance the search offset past the keyword. + searchOffset = hitOffset + escapedKeyword.length(); - // Advance the text offset past the keyword. - textOffset = hitOffset + escapedKeyword.length(); + //record the location of th hir, possibly merging it with other hits + highlights.add(Range.closedOpen(hitOffset, searchOffset)); - hitOffset = StringUtils.indexOfIgnoreCase(text, escapedKeyword, textOffset); + //look for next hit + hitOffset = StringUtils.indexOfIgnoreCase(text, escapedKeyword, searchOffset); } - // Append the remainder of text field - highlightedText.append(text.substring(textOffset, text.length())); - - if (highlightedText.length() == 0) { - return NbBundle.getMessage(HighlightedText.class, - "HighlightedMatchesSource.getMarkup.noMatchMsg"); - } - //reset for next pass - text = highlightedText.toString(); - highlightedText = new StringBuilder(""); } - return text; + + StringBuilder highlightedText = new StringBuilder(text); + int totalHighLightLengthInserted = 0; + //for each range to be highlighted... + for (Range highlightRange : highlights.asRanges()) { + int hStart = highlightRange.lowerEndpoint(); + int hEnd = highlightRange.upperEndpoint(); + + //insert the pre and post tag, adjusting indices for previously added tags + highlightedText.insert(hStart + totalHighLightLengthInserted, HIGHLIGHT_PRE); + totalHighLightLengthInserted += HIGHLIGHT_PRE.length(); + highlightedText.insert(hEnd + totalHighLightLengthInserted, HIGHLIGHT_POST); + totalHighLightLengthInserted += HIGHLIGHT_POST.length(); + } + + return highlightedText.toString(); } /** From 168875074932a4cc056994abe4bd8466aec70ca0 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 28 Apr 2017 13:21:44 +0200 Subject: [PATCH 09/57] cleanup and comments --- .../sleuthkit/autopsy/keywordsearch/HighlightedText.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index 744fdb6b31..541b9a373c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -66,7 +66,6 @@ class HighlightedText implements IndexedText { private static final String HIGHLIGHT_PRE = ""; //NON-NLS private static final String HIGHLIGHT_POST = ""; //NON-NLS - private static final int HIGHLIGHT_MARKUP_LENGTH = HIGHLIGHT_PRE.length() + HIGHLIGHT_POST.length(); private static final String ANCHOR_PREFIX = HighlightedText.class.getName() + "_"; //NON-NLS final private Server solrServer = KeywordSearch.getServer(); @@ -459,7 +458,7 @@ class HighlightedText implements IndexedText { * to a Solr query. We expect there to only ever be * a single document. * - * @return Either a string with the keyword highlighted or a string + * @return Either a string with the keyword highlighted via HTML span tags or a string * indicating that we did not find a hit in the document. */ static String attemptManualHighlighting(SolrDocumentList solrDocumentList, String highlightField, Collection keywords) { @@ -481,7 +480,7 @@ class HighlightedText implements IndexedText { TreeRangeSet highlights = TreeRangeSet.create(); - //do a highlighting pass for each keyword + //for each keyword find the locations of hits and record them in the RangeSet for (String keyword : keywords) { //we also need to escape the keyword so that it matches the escaped text final String escapedKeyword = StringEscapeUtils.escapeHtml(keyword); @@ -491,7 +490,7 @@ class HighlightedText implements IndexedText { // Advance the search offset past the keyword. searchOffset = hitOffset + escapedKeyword.length(); - //record the location of th hir, possibly merging it with other hits + //record the location of the hir, possibly merging it with other hits highlights.add(Range.closedOpen(hitOffset, searchOffset)); //look for next hit From ab40c61f110e586e3fec9e3f9334392092256a1f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 28 Apr 2017 16:56:55 -0400 Subject: [PATCH 10/57] 2595 changes to support Files and Results nodes under interesting items --- .../datamodel/DisplayableItemNodeVisitor.java | 13 +- .../autopsy/datamodel/InterestingHits.java | 136 +++++++++++++++--- 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 972951ea99..5cc152e465 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 - 2016 Basis Technology Corp. + * Copyright 2011 - 2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -90,7 +90,7 @@ public interface DisplayableItemNodeVisitor { T visit(KeywordHits.ListNode khsn); T visit(KeywordHits.TermNode khmln); - + T visit(KeywordHits.RegExpInstanceNode khmln); T visit(HashsetHits.RootNode hhrn); @@ -156,6 +156,8 @@ public interface DisplayableItemNodeVisitor { T visit(EmptyNode.MessageNode emptyNode); + T visit(InterestingHits.InterestingItemTypeNode aThis); + /** * Visitor with an implementable default behavior for all types. Override * specific visit types to not use the default behavior. @@ -238,6 +240,11 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(ftByMimeTypeEmptyNode); } + @Override + public T visit(InterestingHits.InterestingItemTypeNode interestingItemTypeNode) { + return defaultVisit(interestingItemTypeNode); + } + @Override public T visit(DeletedContentNode dcn) { return defaultVisit(dcn); @@ -282,7 +289,7 @@ public interface DisplayableItemNodeVisitor { public T visit(KeywordHits.ListNode khsn) { return defaultVisit(khsn); } - + @Override public T visit(KeywordHits.RegExpInstanceNode khsn) { return defaultVisit(khsn); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index e6a111a5d8..2d36ca1d61 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011-2015 Basis Technology Corp. + * + * Copyright 2011-2017 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. @@ -66,20 +66,20 @@ public class InterestingHits implements AutopsyVisitableItem { private class InterestingResults extends Observable { // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized - private final Map> interestingItemsMap = new LinkedHashMap<>(); + private final Map>> interestingItemsMap = new LinkedHashMap<>(); public List getSetNames() { List setNames; synchronized (interestingItemsMap) { setNames = new ArrayList<>(interestingItemsMap.keySet()); - } + } Collections.sort(setNames); return setNames; } - public Set getArtifactIds(String setName) { + public Set getArtifactIds(String setName, BlackboardArtifact.ARTIFACT_TYPE type) { synchronized (interestingItemsMap) { - return interestingItemsMap.get(setName); + return interestingItemsMap.get(setName).get(type); } } @@ -118,9 +118,11 @@ public class InterestingHits implements AutopsyVisitableItem { String value = resultSet.getString("value_text"); //NON-NLS long artifactId = resultSet.getLong("artifact_id"); //NON-NLS if (!interestingItemsMap.containsKey(value)) { - interestingItemsMap.put(value, new HashSet<>()); + interestingItemsMap.put(value, new LinkedHashMap<>()); + interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, new HashSet<>()); + interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT, new HashSet<>()); } - interestingItemsMap.get(value).add(artifactId); + interestingItemsMap.get(value).get(artType).add(artifactId); } } } catch (TskCoreException | SQLException ex) { @@ -280,7 +282,7 @@ public class InterestingHits implements AutopsyVisitableItem { private final String setName; public SetNameNode(String setName) {//, Set children) { - super(Children.create(new HitFactory(setName), true), Lookups.singleton(setName)); + super(Children.create(new HitTypeFactory(setName), true), Lookups.singleton(setName)); this.setName = setName; super.setName(setName); updateDisplayName(); @@ -289,7 +291,99 @@ public class InterestingHits implements AutopsyVisitableItem { } private void updateDisplayName() { - super.setDisplayName(setName + " (" + interestingResults.getArtifactIds(setName).size() + ")"); + int sizeOfSet = interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT).size() + + interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).size(); + super.setDisplayName(setName + " (" + sizeOfSet + ")"); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"), + getName())); + + return s; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + /** + * For custom settings for each rule set, return + * getClass().getName() + setName instead. + */ + return getClass().getName(); + } + } + + private class HitTypeFactory extends ChildFactory implements Observer { + + private final String setName; + private final Map artifactHits = new HashMap<>(); + + private HitTypeFactory(String setName) { + super(); + this.setName = setName; + interestingResults.addObserver(this); + } + + @Override + protected boolean createKeys(List list) { + list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); + list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT); + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact.ARTIFACT_TYPE key) { + return new InterestingItemTypeNode(setName, key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + public class InterestingItemTypeNode extends DisplayableItemNode implements Observer { + + private final BlackboardArtifact.ARTIFACT_TYPE type; + private final String setName; + + private InterestingItemTypeNode(String setName, BlackboardArtifact.ARTIFACT_TYPE type) { + super(Children.create(new HitFactory(setName, type), true), Lookups.singleton(setName)); + this.type = type; + this.setName = setName; + super.setName(type.getDisplayName()); + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS + interestingResults.addObserver(this); + } + + private void updateDisplayName() { + super.setDisplayName(type.getDisplayName() + " (" + interestingResults.getArtifactIds(setName, type).size() + ")"); } @Override @@ -337,27 +431,29 @@ public class InterestingHits implements AutopsyVisitableItem { private class HitFactory extends ChildFactory implements Observer { private final String setName; - private Map artifactHits = new HashMap<>(); + private final BlackboardArtifact.ARTIFACT_TYPE type; + private final Map artifactHits = new HashMap<>(); - private HitFactory(String setName) { + private HitFactory(String setName, BlackboardArtifact.ARTIFACT_TYPE type) { super(); this.setName = setName; + this.type = type; interestingResults.addObserver(this); } @Override protected boolean createKeys(List list) { - + if (skCase == null) { - return true; + return true; } - - interestingResults.getArtifactIds(setName).forEach((id) -> { + + interestingResults.getArtifactIds(setName, type).forEach((id) -> { try { BlackboardArtifact art = skCase.getBlackboardArtifact(id); - artifactHits.put(id, art); list.add(id); + } catch (TskCoreException ex) { logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS } From f6cd3f680b851d1d87ef59ed932ae9a2321f634c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 28 Apr 2017 17:33:17 -0400 Subject: [PATCH 11/57] 2595 changed key from artifact_type to string for interesting results type --- .../autopsy/datamodel/InterestingHits.java | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 2d36ca1d61..d4ae2da781 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -66,7 +66,7 @@ public class InterestingHits implements AutopsyVisitableItem { private class InterestingResults extends Observable { // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized - private final Map>> interestingItemsMap = new LinkedHashMap<>(); + private final Map>> interestingItemsMap = new LinkedHashMap<>(); public List getSetNames() { List setNames; @@ -77,9 +77,9 @@ public class InterestingHits implements AutopsyVisitableItem { return setNames; } - public Set getArtifactIds(String setName, BlackboardArtifact.ARTIFACT_TYPE type) { + public Set getArtifactIds(String setName, String typeName) { synchronized (interestingItemsMap) { - return interestingItemsMap.get(setName).get(type); + return interestingItemsMap.get(setName).get(typeName); } } @@ -119,10 +119,10 @@ public class InterestingHits implements AutopsyVisitableItem { long artifactId = resultSet.getLong("artifact_id"); //NON-NLS if (!interestingItemsMap.containsKey(value)) { interestingItemsMap.put(value, new LinkedHashMap<>()); - interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, new HashSet<>()); - interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT, new HashSet<>()); + interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getDisplayName(), new HashSet<>()); + interestingItemsMap.get(value).put(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName(), new HashSet<>()); } - interestingItemsMap.get(value).get(artType).add(artifactId); + interestingItemsMap.get(value).get(artType.getDisplayName()).add(artifactId); } } } catch (TskCoreException | SQLException ex) { @@ -291,8 +291,8 @@ public class InterestingHits implements AutopsyVisitableItem { } private void updateDisplayName() { - int sizeOfSet = interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT).size() - + interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).size(); + int sizeOfSet = interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName()).size() + + interestingResults.getArtifactIds(setName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getDisplayName()).size(); super.setDisplayName(setName + " (" + sizeOfSet + ")"); } @@ -338,7 +338,7 @@ public class InterestingHits implements AutopsyVisitableItem { } } - private class HitTypeFactory extends ChildFactory implements Observer { + private class HitTypeFactory extends ChildFactory implements Observer { private final String setName; private final Map artifactHits = new HashMap<>(); @@ -350,14 +350,14 @@ public class InterestingHits implements AutopsyVisitableItem { } @Override - protected boolean createKeys(List list) { - list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); - list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT); + protected boolean createKeys(List list) { + list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getDisplayName()); + list.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName()); return true; } @Override - protected Node createNodeForKey(BlackboardArtifact.ARTIFACT_TYPE key) { + protected Node createNodeForKey(String key) { return new InterestingItemTypeNode(setName, key); } @@ -369,21 +369,21 @@ public class InterestingHits implements AutopsyVisitableItem { public class InterestingItemTypeNode extends DisplayableItemNode implements Observer { - private final BlackboardArtifact.ARTIFACT_TYPE type; + private final String typeName; private final String setName; - private InterestingItemTypeNode(String setName, BlackboardArtifact.ARTIFACT_TYPE type) { - super(Children.create(new HitFactory(setName, type), true), Lookups.singleton(setName)); - this.type = type; + private InterestingItemTypeNode(String setName, String typeName) { + super(Children.create(new HitFactory(setName, typeName), true), Lookups.singleton(setName)); + this.typeName = typeName; this.setName = setName; - super.setName(type.getDisplayName()); + super.setName(typeName); updateDisplayName(); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS interestingResults.addObserver(this); } private void updateDisplayName() { - super.setDisplayName(type.getDisplayName() + " (" + interestingResults.getArtifactIds(setName, type).size() + ")"); + super.setDisplayName(typeName + " (" + interestingResults.getArtifactIds(setName, typeName).size() + ")"); } @Override @@ -399,12 +399,10 @@ public class InterestingHits implements AutopsyVisitableItem { ss = Sheet.createPropertiesSet(); s.put(ss); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"), getName())); - return s; } @@ -431,13 +429,13 @@ public class InterestingHits implements AutopsyVisitableItem { private class HitFactory extends ChildFactory implements Observer { private final String setName; - private final BlackboardArtifact.ARTIFACT_TYPE type; + private final String typeName; private final Map artifactHits = new HashMap<>(); - private HitFactory(String setName, BlackboardArtifact.ARTIFACT_TYPE type) { + private HitFactory(String setName, String typeName) { super(); this.setName = setName; - this.type = type; + this.typeName = typeName; interestingResults.addObserver(this); } @@ -448,12 +446,11 @@ public class InterestingHits implements AutopsyVisitableItem { return true; } - interestingResults.getArtifactIds(setName, type).forEach((id) -> { + interestingResults.getArtifactIds(setName, typeName).forEach((id) -> { try { BlackboardArtifact art = skCase.getBlackboardArtifact(id); artifactHits.put(id, art); list.add(id); - } catch (TskCoreException ex) { logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS } From 4c3286af8faca8a7297f1353b13665813097a44c Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 1 May 2017 07:52:07 -0400 Subject: [PATCH 12/57] 2592: Duplicate entries in Directory Listing for regex keyword hits --- Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index 197c893575..bdc4944afe 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -897,7 +897,6 @@ public class KeywordHits implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - list.addAll(keywordResults.getArtifactIds(setName, keyword, instance)); for (Long id : keywordResults.getArtifactIds(setName, keyword, instance) ) { nodesMap.put(id, createBlackboardArtifactNode(id)); list.add(id); From da1217a4dfe8e5d09508fd1d7794c215fcf9416f Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Mon, 1 May 2017 16:53:00 -0400 Subject: [PATCH 13/57] Modified Autopsy manifest file format --- .../experimental/autoingest/AutopsyManifestFileParser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 33ee129c0b..16a20b0b36 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -40,9 +40,9 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { private static final String MANIFEST_FILE_NAME_SIGNATURE = "_Manifest.xml"; private static final String ROOT_ELEM_TAG_NAME = "AutopsyManifest"; - private static final String CASE_NAME_XPATH = "/Manifest/Collection/Name/text()"; - private static final String DEVICE_ID_XPATH = "/Manifest/Collection/Image/ID/text()"; - private static final String DATA_SOURCE_NAME_XPATH = "/Manifest/Collection/Image/Name/text()"; + private static final String CASE_NAME_XPATH = "/Manifest/CaseName/text()"; + private static final String DEVICE_ID_XPATH = "/Manifest/DeviceId/text()"; + private static final String DATA_SOURCE_NAME_XPATH = "/Manifest/DataSourcePath/text()"; @Override public boolean fileIsManifest(Path filePath) { From 561133d1674c44b0cb2c1ea6b4dd78ff5eb21f58 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Mon, 1 May 2017 17:39:00 -0400 Subject: [PATCH 14/57] Removed optimizations that we decided not to do right now --- .../experimental/autoingest/AutopsyManifestFileParser.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 16a20b0b36..109ae1a8eb 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -62,10 +62,6 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { @Override public Manifest parse(Path filePath) throws ManifestFileParserException { - if (!fileIsManifest(filePath)) { - throw new ManifestFileParserException(String.format("%s not recognized as a manifest", filePath)); - } - try { Document doc = this.createManifestDOM(filePath); XPath xpath = XPathFactory.newInstance().newXPath(); From 65fceeb020d4bbb27e303c3ae167ad9f9f0c2f36 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Mon, 1 May 2017 17:40:55 -0400 Subject: [PATCH 15/57] Minor fix --- .../experimental/autoingest/AutopsyManifestFileParser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 109ae1a8eb..5b7fc5e3c9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -40,9 +40,9 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { private static final String MANIFEST_FILE_NAME_SIGNATURE = "_Manifest.xml"; private static final String ROOT_ELEM_TAG_NAME = "AutopsyManifest"; - private static final String CASE_NAME_XPATH = "/Manifest/CaseName/text()"; - private static final String DEVICE_ID_XPATH = "/Manifest/DeviceId/text()"; - private static final String DATA_SOURCE_NAME_XPATH = "/Manifest/DataSourcePath/text()"; + private static final String CASE_NAME_XPATH = "/AutopsyManifest/CaseName/text()"; + private static final String DEVICE_ID_XPATH = "/AutopsyManifest/DeviceId/text()"; + private static final String DATA_SOURCE_NAME_XPATH = "/AutopsyManifest/DataSourcePath/text()"; @Override public boolean fileIsManifest(Path filePath) { From 7a7eb296bf235e8927d828525b7aca0211637bf3 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Tue, 2 May 2017 00:00:52 -0400 Subject: [PATCH 16/57] Removed some logging around Tika text extraction. --- .../autopsy/keywordsearch/HtmlTextExtractor.java | 9 ++++++++- .../autopsy/keywordsearch/KeywordSearchIngestModule.java | 6 +++--- .../autopsy/keywordsearch/TikaTextExtractor.java | 1 - 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java index 5d79639fc5..79f4cfb844 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,8 @@ import java.util.Arrays; import java.util.List; import java.util.logging.Level; import net.htmlparser.jericho.Attributes; +import net.htmlparser.jericho.Config; +import static net.htmlparser.jericho.Config.LoggerProvider; import net.htmlparser.jericho.Renderer; import net.htmlparser.jericho.Source; import net.htmlparser.jericho.StartTag; @@ -49,6 +51,11 @@ class HtmlTextExtractor extends FileTextExtractor { "text/html", //NON-NLS NON-NLS "text/javascript" //NON-NLS ); + + static { + // Disable Jericho HTML Parser log messages. + Config.LoggerProvider = LoggerProvider.DISABLED; + } @Override boolean isContentTypeSpecific() { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 6f3c11f8cb..08a6b38165 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -446,7 +446,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { } if (extractor == null) { - logger.log(Level.INFO, "No text extractor found for file id:{0}, name: {1}, detected format: {2}", new Object[]{aFile.getId(), aFile.getName(), detectedFormat}); //NON-NLS + // No text extractor found. return false; } @@ -561,7 +561,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { return; } if (!extractTextAndIndex(aFile, fileType)) { - logger.log(Level.WARNING, "Text extractor not found for file. Extracting strings only. File: ''{0}'' (id:{1}).", new Object[]{aFile.getName(), aFile.getId()}); //NON-NLS + // Text extractor not found for file. Extract string only. putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT); } else { putIngestStatus(jobId, aFile.getId(), IngestStatus.TEXT_INGESTED); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java index ad4f6b6ca1..fcbace158d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java @@ -57,7 +57,6 @@ class TikaTextExtractor extends FileTextExtractor { @Override public void logWarning(final String msg, Exception ex) { KeywordSearch.getTikaLogger().log(Level.WARNING, msg, ex); - logger.log(Level.WARNING, msg, ex); //NON-NLS } } @Override From 368d74429984321210d99bcc56650cc5c606f38a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Tue, 2 May 2017 00:31:48 -0400 Subject: [PATCH 17/57] Using newer POI JARs. --- CoreLibs/ivy.xml | 4 ++-- CoreLibs/nbproject/project.xml | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index c0ca478735..9d85fdafda 100644 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -28,8 +28,8 @@ - - + + diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 358637d979..d902e24cde 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -708,8 +708,8 @@ release/modules/ext/jna-3.4.0.jar - ext/poi-ooxml-schemas-3.8.jar - release/modules/ext/poi-ooxml-schemas-3.8.jar + ext/poi-ooxml-schemas-3.10.1.jar + release/modules/ext/poi-ooxml-schemas-3.10.1.jar ext/gson-1.4.jar @@ -768,8 +768,8 @@ release/modules/ext/jcalendarbutton-1.4.6.jar - ext/poi-ooxml-3.8.jar - release/modules/ext/poi-ooxml-3.8.jar + ext/poi-ooxml-3.10.1.jar + release/modules/ext/poi-ooxml-3.10.1.jar ext/imageio-psd-3.2.jar @@ -784,8 +784,8 @@ release/modules/ext/servlet-api-2.5.jar - ext/poi-excelant-3.8.jar - release/modules/ext/poi-excelant-3.8.jar + ext/poi-excelant-3.10.1.jar + release/modules/ext/poi-excelant-3.10.1.jar ext/imageio-pcx-3.2.jar @@ -824,8 +824,8 @@ release/modules/ext/geronimo-jms_1.1_spec-1.0.jar - ext/poi-scratchpad-3.8.jar - release/modules/ext/poi-scratchpad-3.8.jar + ext/poi-scratchpad-3.10.1.jar + release/modules/ext/poi-scratchpad-3.10.1.jar ext/joda-time-2.4-sources.jar @@ -896,8 +896,8 @@ release/modules/ext/commons-io-2.4.jar - ext/poi-3.8.jar - release/modules/ext/poi-3.8.jar + ext/poi-3.10.1.jar + release/modules/ext/poi-3.10.1.jar ext/controlsfx-8.40.11.jar From 23babcd785d9093857dc118d8dd2d9e9ddf3d259 Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 2 May 2017 09:47:54 -0400 Subject: [PATCH 18/57] 2536: Review ChildFactory uses for long-running tasks Check the hashmap to ensure there isn't a node for the given key already, before creating the node. --- .../sleuthkit/autopsy/datamodel/HashsetHits.java | 9 +++++---- .../autopsy/datamodel/InterestingHits.java | 10 ++++++---- .../sleuthkit/autopsy/datamodel/KeywordHits.java | 14 ++++++++++---- .../timeline/explorernodes/EventRootNode.java | 12 ++++++++---- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index db409d7ec9..d07a0c3ba3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -379,10 +379,11 @@ public class HashsetHits implements AutopsyVisitableItem { hashsetResults.getArtifactIds(hashsetName).forEach((id) -> { try { - BlackboardArtifact art = skCase.getBlackboardArtifact(id); - - artifactHits.put(id, art); - list.add(id); + if (!artifactHits.containsKey(id)) { + BlackboardArtifact art = skCase.getBlackboardArtifact(id); + artifactHits.put(id, art); + list.add(id); + } } catch (TskException ex) { logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index e6a111a5d8..f089727da4 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -354,10 +354,12 @@ public class InterestingHits implements AutopsyVisitableItem { interestingResults.getArtifactIds(setName).forEach((id) -> { try { - BlackboardArtifact art = skCase.getBlackboardArtifact(id); - - artifactHits.put(id, art); - list.add(id); + if (!artifactHits.containsKey(id)) { + BlackboardArtifact art = skCase.getBlackboardArtifact(id); + + artifactHits.put(id, art); + list.add(id); + } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index bdc4944afe..19fb871e1d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -707,15 +707,19 @@ public class KeywordHits implements AutopsyVisitableItem { if ((instances.size() == 1) && (instances.get(0).equals(DEFAULT_INSTANCE_NAME))) { for (Long id : keywordResults.getArtifactIds(setName, keyword, DEFAULT_INSTANCE_NAME) ) { RegExpInstanceKey key = new RegExpInstanceKey(id); - nodesMap.put(key, createNode(key)); - list.add(key); + if (!nodesMap.containsKey(key)) { + nodesMap.put(key, createNode(key)); + list.add(key); + } } } else { for (String instance : instances) { RegExpInstanceKey key = new RegExpInstanceKey(instance); - nodesMap.put(key, createNode(key)); - list.add(key); + if (!nodesMap.containsKey(key)) { + nodesMap.put(key, createNode(key)); + list.add(key); + } } } @@ -898,9 +902,11 @@ public class KeywordHits implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { for (Long id : keywordResults.getArtifactIds(setName, keyword, instance) ) { + if (!nodesMap.containsKey(id)) { nodesMap.put(id, createBlackboardArtifactNode(id)); list.add(id); } + } return true; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index a2a00837c6..c699c6cac8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -100,12 +100,16 @@ public class EventRootNode extends DisplayableItemNode { */ if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) { for (Long eventId: eventIDs){ - nodesMap.put(eventId, createNode(eventId)); - toPopulate.add(eventId); + if (!nodesMap.containsKey(eventId)) { + nodesMap.put(eventId, createNode(eventId)); + toPopulate.add(eventId); + } } } else { - nodesMap.put(-1L, createNode(-1L)); - toPopulate.add(-1L); + if (!nodesMap.containsKey(-1L)) { + nodesMap.put(-1L, createNode(-1L)); + toPopulate.add(-1L); + } } return true; } From c27e986bc2e90ea3793a10f7b461665d400bd81b Mon Sep 17 00:00:00 2001 From: Jonathan Millman Date: Tue, 2 May 2017 17:49:27 +0200 Subject: [PATCH 19/57] fix typo --- .../org/sleuthkit/autopsy/keywordsearch/HighlightedText.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index 541b9a373c..ad09b66077 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -490,7 +490,7 @@ class HighlightedText implements IndexedText { // Advance the search offset past the keyword. searchOffset = hitOffset + escapedKeyword.length(); - //record the location of the hir, possibly merging it with other hits + //record the location of the hit, possibly merging it with other hits highlights.add(Range.closedOpen(hitOffset, searchOffset)); //look for next hit From ee43f711377247b48d1fc65a95f4d38cdc2f3377 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 2 May 2017 15:22:32 -0400 Subject: [PATCH 20/57] Fix assorted Case action bugs --- .../autopsy/casemodule/Bundle.properties | 10 +- .../autopsy/casemodule/Bundle_ja.properties | 8 - .../sleuthkit/autopsy/casemodule/Case.java | 1571 +++++++++-------- .../CaseActionCancelledException.java | 49 + .../casemodule/CaseActionException.java | 29 +- .../casemodule/CaseInformationPanel.java | 62 +- .../autopsy/casemodule/CaseMetadata.java | 130 +- .../autopsy/casemodule/CaseOpenAction.java | 16 +- .../casemodule/CasePropertiesAction.java | 9 +- .../casemodule/CasePropertiesPanel.java | 124 +- .../casemodule/NewCaseWizardAction.java | 16 +- .../casemodule/NewCaseWizardPanel1.java | 73 +- .../casemodule/NewCaseWizardPanel2.java | 74 +- .../casemodule/OpenRecentCasePanel.java | 14 +- .../autopsy/casemodule/RecentCases.java | 6 +- .../autopsy/casemodule/RecentItems.java | 17 +- .../casemodule/SingleUserCaseConverter.java | 4 +- .../framework/LoggingProgressIndicator.java | 6 +- .../ModalDialogProgressIndicator.java | 149 +- .../autopsy/framework/ProgressIndicator.java | 7 +- .../autopsy/framework/ProgressPanel.java | 2 +- .../framework/SilentProgressIndicator.java | 2 +- .../imagewriter/ImageWriterService.java | 77 +- .../autopsy/test/TestAutopsyService.java | 4 +- .../keywordsearch/SolrSearchService.java | 4 + 25 files changed, 1253 insertions(+), 1210 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index be0ddd285b..ca31fb4c75 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -96,10 +96,10 @@ Case.databaseConnectionInfo.error.msg=Error accessing database server connection Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the database with the following path has been made\:\n {0} Case.open.msgDlg.updated.title=Case Database Schema Update Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-user. -Case.checkImgExist.confDlg.doesntExist.msg={0} has detected that one of the images associated with \n\ +Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\ this case are missing. Would you like to search for them now?\n\ Previously, the image was located at\:\n\ -{1}\n\ +{0}\n\ Please note that you will still be able to browse directories and generate reports\n\ if you choose No, but you will not be able to view file content or run the ingest process. Case.checkImgExist.confDlg.doesntExist.title=Missing Image @@ -136,12 +136,6 @@ IntervalErrorReport.NewIssues=new issue(s) IntervalErrorReport.TotalIssues=total issue(s) IntervalErrorReport.ErrorText=Database Connection Error CasePropertiesAction.window.title=Case Properties -CasePropertiesForm.updateCaseName.msgDlg.empty.msg=The caseName cannot be empty. -CasePropertiesForm.updateCaseName.msgDlg.empty.title=Error -CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg=The Case Name cannot contain any of this following symbol\: \\ / \: * ? " < > | -CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title=Error -CasePropertiesForm.updateCaseName.confMsg.msg=Are you sure you want to update the case name from "{0}" to "{1}"? -CasePropertiesForm.updateCaseName.confMsg.title=Change Case Name CueBannerPanel.title.text=Open Recent Case GeneralFilter.rawImageDesc.text=Raw Images (*.img, *.dd, *.001, *.aa, *.raw, *.bin) GeneralFilter.encaseImageDesc.text=Encase Images (*.e01) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 989fd8a29f..60787ada8c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -73,10 +73,6 @@ Case.getCurCase.exception.noneOpen=\u4f5c\u696d\u4e2d\u306e\u30b1\u30fc\u30b9\u3 Case.open.msgDlg.updated.msg=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002\n\u6b21\u306e\u30d1\u30b9\u3092\u6301\u3064\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u30b3\u30d4\u30fc\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\uff1a\n\ {0} Case.open.msgDlg.updated.title=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0 -Case.checkImgExist.confDlg.doesntExist.msg={0} \u304c\u3053\u306e\u30b1\u30fc\u30b9\u306b\u95a2\u9023\u3059\u308b\u30a4\u30e1\u30fc\u30b8\u306e\u3046\u3061\uff11\u3064\u304c\u6b20\u843d\u3057\u3066\u3044\u308b\u306e\u3092\u691c\u51fa\u3057\u307e\u3057\u305f\u3002\u305d\u308c\u3092\u4eca\u304b\u3089\u691c\u7d22\u3057\u307e\u3059\u304b\uff1f\n\n\ -\u4ee5\u524d\u3001\u30a4\u30e1\u30fc\u30b8\u306f\u6b21\u306b\u3042\u308a\u307e\u3057\u305f\uff1a\n\ -{1}\n\ -\u3044\u3044\u3048\u3092\u9078\u629e\u3057\u3066\u3082\u3001\u4eca\u5f8c\u3082\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u95b2\u89a7\u3057\u3001\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u304c\u3067\u304d\u307e\u3059\u304c\u3001\n\u30d5\u30a1\u30a4\u30eb\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u8868\u793a\u307e\u305f\u306f\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30d7\u30ed\u30bb\u30b9\u306e\u5b9f\u884c\u304c\u3067\u304d\u306a\u304f\u306a\u308a\u307e\u3059\u3002 Case.checkImgExist.confDlg.doesntExist.title=\u6b20\u843d\u3057\u3066\u3044\u308b\u30a4\u30e1\u30fc\u30b8 Case.addImg.exception.msg=\u30b1\u30fc\u30b9\u306b\u30a4\u30e1\u30fc\u30b8\u3092\u8ffd\u52a0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f Case.updateCaseName.exception.msg=\u30b1\u30fc\u30b9\u540d\u3092\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 @@ -99,10 +95,6 @@ CaseDeleteAction.msgDlg.caseDelete.msg=\u30b1\u30fc\u30b9\u304c\u524a\u9664\u305 CaseOpenAction.autFilter.title={0} \u30b1\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb ( {1}) CaseOpenAction.msgDlg.cantOpenCase.title=\u30b1\u30fc\u30b9\u3092\u958b\u304f\u969b\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f CasePropertiesAction.window.title=\u30b1\u30fc\u30b9\u30d7\u30ed\u30d1\u30c6\u30a3 -CasePropertiesForm.updateCaseName.msgDlg.empty.msg=\u30b1\u30fc\u30b9\u540d\u306f\u7a7a\u767d\u3067\u306f\u3044\u3051\u307e\u305b\u3093\u3002 -CasePropertiesForm.updateCaseName.msgDlg.empty.title=\u30a8\u30e9\u30fc -CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg=\u30b1\u30fc\u30b9\u540d\u306b\u306f\u6b21\u306e\u8a18\u53f7\u3092\u542b\u3081\u307e\u305b\u3093\uff1a\\ / \: * ? " < > | -CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title=\u30a8\u30e9\u30fc CueBannerPanel.title.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f GeneralFilter.rawImageDesc.text=\u30ed\u30fc\u30a4\u30e1\u30fc\u30b8(*.img, *.dd, *.001, *.aa, *.raw, *.bin) GeneralFilter.encaseImageDesc.text=\u30a8\u30f3\u30b1\u30fc\u30b9\u30a4\u30e1\u30fc\u30b8(*.e01) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 08880d9b9b..5bd1618859 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -25,12 +25,12 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.SQLException; import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.Collection; @@ -48,11 +48,13 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openide.util.Lookup; @@ -98,6 +100,7 @@ import org.sleuthkit.autopsy.framework.ProgressIndicator; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; +import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.CaseDbConnectionInfo; @@ -124,45 +127,18 @@ public class Case { private static final String TEMP_FOLDER = "Temp"; //NON-NLS private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS + private static final long EXECUTOR_AWAIT_TIMEOUT_SECS = 5; + private static final String CASE_ACTION_THREAD_NAME = "%s-case-action"; + private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources"; private static final Logger logger = Logger.getLogger(Case.class.getName()); private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); - - /* - * The following fields are the mutable state associated with the current - * case concept. The currentCase field is guarded for writes by the - * currentCaseWriteLock. The field is also volatile to allow non-locking - * reads via the isCaseOpen and getCurrentCase methods. This is unfortunate, - * but Case clients that do not respond correctly to CURRENT_CASE closed - * events may call these methods and that would be a source of potential - * deadlock if the currentCaseWriteLock was used to guard read access. - * - * TODO (JIRA-2228): Throw CaseClosedException from Case instance methods. - */ - private static final Object currentCaseWriteLock = new Object(); - @GuardedBy("currentCaseWriteLock") + private static final Object caseActionSerializationLock = new Object(); + private static volatile Frame mainFrame; + private static volatile String appName; private static volatile Case currentCase; - - /* - * The application name, used to make the title of the main application - * window [application name] when there is no open case and [curent case - * display name] - [application name] when there is an open case. - * Initialized by getting the main window title before a case has been - * opened. A reference to the main window frame is obtained at the same time - * as a convenmient side effect for parenting dialogs. - * - * TODO (JIRA-2231): Make the application name a RuntimeProperties item. - */ - @GuardedBy("currentCaseWriteLock") - private static Frame mainFrame; - @GuardedBy("currentCaseWriteLock") - private static String appName; - - /* - * Case instance data. - */ - private CaseMetadata caseMetadata; + private final CaseMetadata metadata; + private volatile ExecutorService caseLockingExecutor; private CoordinationService.Lock caseDirLock; - private ExecutorService caseLockingExecutor; private SleuthkitCase caseDb; private SleuthkitErrorReporter sleuthkitErrorReporter; private CollaborationMonitor collaborationMonitor; @@ -319,9 +295,6 @@ public class Case { * interaction with a closed case, the case in the old value should be * used, and it should be done synchronously in the CURRENT_CASE event * handler. - * - * TODO (JIRA-2228): Throw CaseClosedException from Case instance - * methods. */ CURRENT_CASE, /** @@ -458,41 +431,22 @@ public class Case { * the empty string. * @param caseType The type of case (single-user or multi-user). * - * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @throws CaseActionException If there is a problem creating the + * case. + * @throws CaseActionCancelledException If creating the case is cancelled. */ @Messages({ - "# {0} - exception message", "Case.exceptionMessage.wrapperMessage={0}" + "Case.exceptionMessage.emptyCaseName=Must specify a case name.", + "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path." }) - public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { - synchronized (currentCaseWriteLock) { - if (RuntimeProperties.runningWithGUI()) { - getMainWindowAndAppName(); - } - - if (null != currentCase) { - String previousCaseDisplayName = currentCase.getDisplayName(); - String previousCaseName = currentCase.getName(); - String previousCaseDir = currentCase.getCaseDirectory(); - try { - closeCurrentCase(); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error closing the previous current case %s (%s) in %s", previousCaseDisplayName, previousCaseName, previousCaseDir), ex); //NON-NLS - } - } - - logger.log(Level.INFO, "Creating current case {0} in {1}", new Object[]{caseDisplayName, caseDir}); //NON-NLS - Case newCurrentCase = new Case(); - newCurrentCase.create(caseType, caseDir, caseDisplayName, caseNumber, examiner); - currentCase = newCurrentCase; - logger.log(Level.INFO, "Created currrent case {0} ({1}) in {2}", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS - if (RuntimeProperties.runningWithGUI()) { - updateGUIForCaseOpened(newCurrentCase); - } - eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, newCurrentCase)); + public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException { + if (caseDisplayName.isEmpty()) { + throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName()); } + if (caseDir.isEmpty()) { + throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir()); + } + openAsCurrentCase(new Case(caseType, caseDir, caseDisplayName, caseNumber, examiner), true); } /** @@ -503,40 +457,22 @@ public class Case { * * @param caseMetadataFilePath The path of the case metadata (.aut) file. * - * @throws CaseActionException if there is a problem opening the case. The + * @throws CaseActionException If there is a problem opening the case. The * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. */ @Messages({ - "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}", - "Case.progressIndicatorTitle.openingCase=Opening Case", - "Case.exceptionMessage.failedToReadMetadata=Failed to read metadata." + "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata." }) public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException { - synchronized (currentCaseWriteLock) { - if (RuntimeProperties.runningWithGUI()) { - getMainWindowAndAppName(); - } - - if (null != currentCase) { - try { - closeCurrentCase(); - } catch (CaseActionException ex) { - logger.log(Level.SEVERE, "Error closing the previous current case", ex); - } - } - - Case newCurrentCase = new Case(); - logger.log(Level.INFO, "Opening case with metadata file path {0} as current case", caseMetadataFilePath); //NON-NLS - newCurrentCase.open(Paths.get(caseMetadataFilePath)); - currentCase = newCurrentCase; - logger.log(Level.INFO, "Opened case with metadata file path {0} as current case", caseMetadataFilePath); //NON-NLS - if (RuntimeProperties.runningWithGUI()) { - updateGUIForCaseOpened(newCurrentCase); - } - eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); + CaseMetadata metadata; + try { + metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex); } + openAsCurrentCase(new Case(metadata), false); } /** @@ -556,48 +492,66 @@ public class Case { * @throws IllegalStateException if there is no current case. */ public static Case getCurrentCase() { - Case caseToReturn = currentCase; - if (null != caseToReturn) { - return caseToReturn; + /* + * Throwing an unchecked exception is a bad idea here. + * + * TODO (JIRA-2229): Case.getCurrentCase() method throws unchecked + * IllegalStateException; change to throw checked exception or return + * null + */ + if (null != currentCase) { + return currentCase; } else { throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen")); } } /** - * Closes the current case if there is a current case. + * Closes the current case. * - * IMPORTANT: This method should not be called in the event dispatch thread - * (EDT). - * - * @throws CaseActionException + * @throws CaseActionException If there is a problem closing the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}", "Case.progressIndicatorTitle.closingCase=Closing Case" }) public static void closeCurrentCase() throws CaseActionException { - synchronized (currentCaseWriteLock) { + synchronized (caseActionSerializationLock) { if (null == currentCase) { return; } - String caseName = currentCase.getName(); - String caseDir = currentCase.getCaseDirectory(); + Case closedCase = currentCase; try { - Case closedCase = currentCase; eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null)); - logger.log(Level.INFO, "Closing current case {0} in {1}", new Object[]{caseName, caseDir}); //NON-NLS + logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS currentCase = null; closedCase.close(); + logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS + throw ex; } finally { - /* - * The case is no longer the current case, even if an exception - * was thrown. - */ - logger.log(Level.INFO, "Closed current case {0} in {1}", new Object[]{caseName, caseDir}); //NON-NLS if (RuntimeProperties.runningWithGUI()) { updateGUIForCaseClosed(); } + + /* + * This is an undocumented, legacy hack. Empirically, it seems + * to be necessary due to problems with finalizers in the + * SleuthKit Java bindings data model calling native methods + * that read garbage from freed memory, leading to access + * violations otherwise. Why the garbage collector is called + * twice is not known, but it appears to be intended to try to + * force the garbage collection to occur. + * + * TODO (JIRA-2611): Make JNI code more robust when handling + * file closure + */ + System.gc(); + System.gc(); } } } @@ -605,20 +559,17 @@ public class Case { /** * Deletes the current case. * - * IMPORTANT: This method should not be called in the event dispatch thread - * (EDT). - * - * @throws CaseActionException if there is a problem deleting the case. The + * @throws CaseActionException If there is a problem deleting the case. The * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. */ public static void deleteCurrentCase() throws CaseActionException { - synchronized (currentCaseWriteLock) { + synchronized (caseActionSerializationLock) { if (null == currentCase) { return; } - CaseMetadata metadata = currentCase.getCaseMetadata(); + CaseMetadata metadata = currentCase.getMetadata(); closeCurrentCase(); deleteCase(metadata); } @@ -638,38 +589,31 @@ public class Case { @Messages({ "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}", "Case.progressIndicatorTitle.deletingCase=Deleting Case", - "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first", + "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.", "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", - "Case.progressMessage.deletingTextIndex=Deleting text index...", - "Case.progressMessage.deletingCaseDatabase=Deleting case database...", - "Case.exceptionMessage.cancelled=Cancelled by user" - }) + "Case.exceptionMessage.cannotDeleteCaseOpenForOtherUser=Cannot delete the case, it is open for another user.",}) public static void deleteCase(CaseMetadata metadata) throws CaseActionException { - synchronized (currentCaseWriteLock) { + synchronized (caseActionSerializationLock) { if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(metadata.getCaseDirectory())) { throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase())); } } /* - * Set up either a GUI progress indicator or a logging progress - * indicator. + * Set up either a GUI progress indicator without a cancel button (can't + * cancel deleting a case) or a logging progress indicator. */ ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator( - mainFrame, - Bundle.Case_progressIndicatorTitle_deletingCase()); + progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase()); } else { progressIndicator = new LoggingProgressIndicator(); } progressIndicator.start(Bundle.Case_progressMessage_preparing()); - logger.log(Level.INFO, "Deleting case with metadata file path {0}", metadata.getFilePath()); //NON-NLS - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit(() -> { + try { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - cleanupDeletedCase(metadata, progressIndicator); + deleteCase(metadata, progressIndicator); } else { /* * First, acquire an exclusive case directory lock. The case @@ -678,48 +622,79 @@ public class Case { progressIndicator.start(Bundle.Case_progressMessage_checkingForOtherUser()); try (CoordinationService.Lock dirLock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { assert (null != dirLock); - - /* - * Delete the text index. - */ - progressIndicator.start(Bundle.Case_progressMessage_deletingTextIndex()); - for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) { - searchService.deleteTextIndex(metadata); - } - - if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { - /* - * Delete the case database from the database server. - * The case database for a single-user case is in the - * case directory and will be deleted whe it is deleted. - */ - progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDatabase()); - CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); - Class.forName("org.postgresql.Driver"); //NON-NLS - try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS - Statement statement = connection.createStatement();) { - String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS - statement.execute(deleteCommand); - } - } - - cleanupDeletedCase(metadata, progressIndicator); + deleteCase(metadata, progressIndicator); + } catch (CoordinationServiceException ex) { } } - return null; - }); - - try { - future.get(); - logger.log(Level.INFO, "Deleted case with metadata file path {0}", metadata.getFilePath()); //NON-NLS - } catch (InterruptedException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex); - } catch (ExecutionException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex); - } catch (CancellationException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex); } finally { - executor.shutdown(); + progressIndicator.finish(); + } + } + + /** + * Opens a new or existing case as the current case. + * + * @param newCurrentCase The case. + * @param isNewCase True for a new case, false otherwise. + * + * @throws CaseActionException If there is a problem creating the + * case. + * @throws CaseActionCancelledException If creating the case is cancelled. + */ + @Messages({ + "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window" + }) + private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException { + if (RuntimeProperties.runningWithGUI() && null == mainFrame) { + /* + * Get a reference to the main window of the desktop application to + * use to parent pop up dialogs and initialize the application name + * for use in changing the main window title. This is tricky and + * fragile. The application name aspect can be resolved thus: + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties + * item set by Installers. + * + * And the getting of the main frame should be resolved when the + * code is refactored to separate the presentation layer fomr the + * business layer. + * + * TODO (JIRA-multiple): Make it possible to run "headless." + */ + assert (!SwingUtilities.isEventDispatchThread()); + SwingUtilities.invokeLater(() -> { + mainFrame = WindowManager.getDefault().getMainWindow(); + appName = mainFrame.getTitle(); + }); + } + synchronized (caseActionSerializationLock) { + if (null != currentCase) { + try { + closeCurrentCase(); + } catch (CaseActionException ex) { + /* + * Notify the user and continue (the error has already been + * logged in closeCurrentCase. + */ + MessageNotifyUtil.Message.error(ex.getLocalizedMessage()); + } + } + try { + logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS + newCurrentCase.open(isNewCase); + currentCase = newCurrentCase; + logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseOpened(newCurrentCase); + } + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); + } catch (CaseActionCancelledException ex) { + logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS + throw ex; + } catch (CaseActionException ex) { + logger.log(Level.SEVERE, String.format("Error opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()), ex); //NON-NLS + throw ex; + } } } @@ -857,56 +832,69 @@ public class Case { } /** - * Gets a reference to the main window of the desktop application to use to - * parent pop ups and initializes the application name for use in changing - * the main window title. MUST be called BEFORE any case is opened or - * created. * - * @throws CaseActionException - */ - @Messages({ - "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window" - }) - private static void getMainWindowAndAppName() throws CaseActionException { - if (RuntimeProperties.runningWithGUI() && null == mainFrame) { - try { - SwingUtilities.invokeAndWait(() -> { - mainFrame = WindowManager.getDefault().getMainWindow(); - /* - * This is tricky and fragile. What looks like lazy - * initialization of the appName field is actually getting - * the application name from the main window title BEFORE a - * case has been opened and a case name has been included in - * the title. It is also very specific to the desktop GUI. - * - * TODO (JIRA-2231): Make the application name a - * RuntimeProperties item set by Installers. - */ - appName = mainFrame.getTitle(); - }); - } catch (InterruptedException | InvocationTargetException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(Bundle.Case_exceptionMessage_cannotLocateMainWindow()), ex); - } - } - } - - /** * Deletes the case directory of a deleted case and removes the case form * the Recent Cases menu. * * @param metadata The case metadata. * @param progressIndicator A progress indicator. + * + * @throws UserPreferencesException if there is a problem getting the case + * databse connection info for a multi-user + * case. + * @throws ClassNotFoundException if there is a problem loading the JDBC + * driver for PostgreSQL for a multi-user + * case. + * @throws SQLException If there is a problem */ @Messages({ - "Case.progressMessage.deletingCaseDirectory=Deleting case directory..." + "Case.progressMessage.deletingTextIndex=Deleting text index...", + "Case.progressMessage.deletingCaseDatabase=Deleting case database...", + "Case.progressMessage.deletingCaseDirectory=Deleting case directory...", + "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details" }) - private static void cleanupDeletedCase(CaseMetadata metadata, ProgressIndicator progressIndicator) { + private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + boolean errorsOccurred = false; + if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { + /* + * Delete the case database from the database server. + */ + try { + progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDatabase()); + CaseDbConnectionInfo db; + db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS + statement.execute(deleteCommand); + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + logger.log(Level.SEVERE, String.format("Failed to delete case database %s for %s (%s) in %s", metadata.getCaseDatabaseName(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + errorsOccurred = true; + } + } + + /* + * Delete the text index. + */ + progressIndicator.start(Bundle.Case_progressMessage_deletingTextIndex()); + for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) { + try { + searchService.deleteTextIndex(metadata); + } catch (KeywordSearchServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); + errorsOccurred = true; + } + } + /* * Delete the case directory. */ progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDirectory()); if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { - logger.log(Level.SEVERE, "Failed to fully delete case directory {0}", metadata.getCaseDirectory()); + logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); + errorsOccurred = true; } /* @@ -917,6 +905,10 @@ public class Case { RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString()); }); } + + if (errorsOccurred) { + throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase()); + } } /** @@ -938,8 +930,9 @@ public class Case { throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock()); } return lock; - - } catch (InterruptedException | CoordinationServiceException ex) { + } catch (InterruptedException ex) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } catch (CoordinationServiceException ex) { throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex); } } @@ -948,149 +941,139 @@ public class Case { * Update the GUI to to reflect the current case. */ private static void updateGUIForCaseOpened(Case newCurrentCase) { - SwingUtilities.invokeLater(() -> { - /* - * If the case database was upgraded for a new schema and a backup - * database was created, notify the user. - */ - SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase(); - String backupDbPath = caseDb.getBackupDatabasePath(); - if (null != backupDbPath) { - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), - JOptionPane.INFORMATION_MESSAGE); - } + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> { + /* + * If the case database was upgraded for a new schema and a + * backup database was created, notify the user. + */ + SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase(); + String backupDbPath = caseDb.getBackupDatabasePath(); + if (null != backupDbPath) { + JOptionPane.showMessageDialog( + mainFrame, + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), + JOptionPane.INFORMATION_MESSAGE); + } - /* - * Look for the files for the data sources listed in the case - * database and give the user the opportunity to locate any that are - * missing. - */ - Map imgPaths = getImagePaths(caseDb); - for (Map.Entry entry : imgPaths.entrySet()) { - long obj_id = entry.getKey(); - String path = entry.getValue(); - boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); - - if (!fileExists) { - int response = JOptionPane.showConfirmDialog( - WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", appName, path), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), - JOptionPane.YES_NO_OPTION); - if (response == JOptionPane.YES_OPTION) { - MissingImageDialog.makeDialog(obj_id, caseDb); - } else { - logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS + /* + * Look for the files for the data sources listed in the case + * database and give the user the opportunity to locate any that + * are missing. + */ + Map imgPaths = getImagePaths(caseDb); + for (Map.Entry entry : imgPaths.entrySet()) { + long obj_id = entry.getKey(); + String path = entry.getValue(); + boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); + if (!fileExists) { + int response = JOptionPane.showConfirmDialog( + mainFrame, + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), + JOptionPane.YES_NO_OPTION); + if (response == JOptionPane.YES_OPTION) { + MissingImageDialog.makeDialog(obj_id, caseDb); + } else { + logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS + } } } - } - /* - * Enable the case-specific actions. - */ - CallableSystemAction.get(AddImageAction.class - ).setEnabled(true); - CallableSystemAction - .get(CaseCloseAction.class - ).setEnabled(true); - CallableSystemAction - .get(CasePropertiesAction.class - ).setEnabled(true); - CallableSystemAction - .get(CaseDeleteAction.class - ).setEnabled(true); - CallableSystemAction - .get(OpenTimelineAction.class - ).setEnabled(true); - CallableSystemAction - .get(OpenOutputFolderAction.class - ).setEnabled(false); + /* + * Enable the case-specific actions. + */ + CallableSystemAction.get(AddImageAction.class + ).setEnabled(true); + CallableSystemAction + .get(CaseCloseAction.class + ).setEnabled(true); + CallableSystemAction + .get(CasePropertiesAction.class + ).setEnabled(true); + CallableSystemAction + .get(CaseDeleteAction.class + ).setEnabled(true); + CallableSystemAction + .get(OpenTimelineAction.class + ).setEnabled(true); + CallableSystemAction + .get(OpenOutputFolderAction.class + ).setEnabled(false); - /* - * Add the case to the recent cases tracker that supplies a list of - * recent cases to the recent cases menu item and the open/create - * case dialog. - */ - RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getCaseMetadata().getFilePath().toString()); + /* + * Add the case to the recent cases tracker that supplies a list + * of recent cases to the recent cases menu item and the + * open/create case dialog. + */ + RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString()); - /* - * Open the top components (windows within the main application - * window). - */ - if (newCurrentCase.hasData()) { - CoreComponentControl.openCoreWindows(); - } + /* + * Open the top components (windows within the main application + * window). + */ + if (newCurrentCase.hasData()) { + CoreComponentControl.openCoreWindows(); + } - /* - * Reset the main window title to be [curent case display name] - - * [application name], instead of just the application name. - */ - addCaseNameToMainWindowTitle(newCurrentCase.getDisplayName()); - }); + /* + * Reset the main window title to: + * + * [curent case display name] - [application name]. + */ + mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + appName); + }); + } } /* * Update the GUI to to reflect the lack of a current case. */ private static void updateGUIForCaseClosed() { - SwingUtilities.invokeLater(() -> { + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> { + /* + * Close the top components (windows within the main application + * window). + */ + CoreComponentControl.closeCoreWindows(); - /* - * Close the top components (windows within the main application - * window). - */ - CoreComponentControl.closeCoreWindows(); + /* + * Disable the case-specific menu items. + */ + CallableSystemAction + .get(AddImageAction.class + ).setEnabled(false); + CallableSystemAction + .get(CaseCloseAction.class + ).setEnabled(false); + CallableSystemAction + .get(CasePropertiesAction.class + ).setEnabled(false); + CallableSystemAction + .get(CaseDeleteAction.class + ).setEnabled(false); + CallableSystemAction + .get(OpenTimelineAction.class + ).setEnabled(false); + CallableSystemAction + .get(OpenOutputFolderAction.class + ).setEnabled(false); - /* - * Disable the case-specific menu items. - */ - CallableSystemAction - .get(AddImageAction.class - ).setEnabled(false); - CallableSystemAction - .get(CaseCloseAction.class - ).setEnabled(false); - CallableSystemAction - .get(CasePropertiesAction.class - ).setEnabled(false); - CallableSystemAction - .get(CaseDeleteAction.class - ).setEnabled(false); - CallableSystemAction - .get(OpenTimelineAction.class - ).setEnabled(false); - CallableSystemAction - .get(OpenOutputFolderAction.class - ).setEnabled(false); + /* + * Clear the notifications in the notfier component in the lower + * right hand corner of the main application window. + */ + MessageNotifyUtil.Notify.clear(); - /* - * Clear the notifications in the notfier component in the lower - * right hand corner of the main application window. - */ - MessageNotifyUtil.Notify.clear(); - - /* - * Reset the main window title to be just the application name, - * instead of [curent case display name] - [application name]. - */ - Frame mainWindow = WindowManager.getDefault().getMainWindow(); - mainWindow.setTitle(appName); - }); - } - - /** - * Changes the title of the main window to include the case name. - * - * @param caseName The name of the case. - */ - private static void addCaseNameToMainWindowTitle(String caseName) { - if (!caseName.isEmpty()) { - Frame frame = WindowManager.getDefault().getMainWindow(); - frame.setTitle(caseName + " - " + appName); + /* + * Reset the main window title to be just the application name, + * instead of [curent case display name] - [application name]. + */ + mainFrame.setTitle(appName); + }); } } @@ -1137,7 +1120,7 @@ public class Case { * @return The case type. */ public CaseType getCaseType() { - return getCaseMetadata().getCaseType(); + return metadata.getCaseType(); } /** @@ -1146,7 +1129,7 @@ public class Case { * @return case The case create date. */ public String getCreatedDate() { - return getCaseMetadata().getCreatedDate(); + return metadata.getCreatedDate(); } /** @@ -1155,7 +1138,7 @@ public class Case { * @return The case name. */ public String getName() { - return getCaseMetadata().getCaseName(); + return metadata.getCaseName(); } /** @@ -1164,7 +1147,7 @@ public class Case { * @return The case display name. */ public String getDisplayName() { - return getCaseMetadata().getCaseDisplayName(); + return metadata.getCaseDisplayName(); } /** @@ -1173,7 +1156,7 @@ public class Case { * @return The case number */ public String getNumber() { - return caseMetadata.getCaseNumber(); + return metadata.getCaseNumber(); } /** @@ -1182,7 +1165,7 @@ public class Case { * @return The examiner name. */ public String getExaminer() { - return caseMetadata.getExaminer(); + return metadata.getExaminer(); } /** @@ -1191,7 +1174,7 @@ public class Case { * @return The top-level case directory path. */ public String getCaseDirectory() { - return caseMetadata.getCaseDirectory(); + return metadata.getCaseDirectory(); } /** @@ -1205,7 +1188,7 @@ public class Case { public String getOutputDirectory() { String caseDirectory = getCaseDirectory(); Path hostPath; - if (getCaseMetadata().getCaseType() == CaseType.MULTI_USER_CASE) { + if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName()); } else { hostPath = Paths.get(caseDirectory); @@ -1329,23 +1312,13 @@ public class Case { } /** - * Sets the name of the keyword search index for the case. - * - * @param textIndexName The text index name. - * - * @throws CaseMetadataException - */ - public void setTextIndexName(String textIndexName) throws CaseMetadataException { - getCaseMetadata().setTextIndexName(textIndexName); - } - - /** - * Gets the name of the keyword search index for the case. + * Gets the name of the legacy keyword search index for the case. Not for + * general use. * * @return The index name. */ public String getTextIndexName() { - return getCaseMetadata().getTextIndexName(); + return getMetadata().getTextIndexName(); } /** @@ -1507,44 +1480,39 @@ public class Case { * * @return A CaseMetaData object. */ - CaseMetadata getCaseMetadata() { - return caseMetadata; + CaseMetadata getMetadata() { + return metadata; } /** * Updates the case display name name. - * - * @param oldCaseName The old case name. - * @param oldPath The old path name. - * @param newCaseName The new case name. - * @param newPath The new case path. */ - void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException { + @Messages({ + "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata, cannot change case display name." + }) + void updateDisplayName(String newDisplayName) throws CaseActionException { + String oldDisplayName = metadata.getCaseDisplayName(); try { - caseMetadata.setCaseDisplayName(newCaseName); - eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName)); + metadata.setCaseDisplayName(newDisplayName); + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError()); + } + eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldDisplayName, newDisplayName)); + if (RuntimeProperties.runningWithGUI()) { SwingUtilities.invokeLater(() -> { + mainFrame.setTitle(newDisplayName + " - " + appName); try { - RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); - addCaseNameToMainWindowTitle(newCaseName); - + RecentCases.getInstance().updateRecentCase(oldDisplayName, metadata.getFilePath().toString(), newDisplayName, metadata.getFilePath().toString()); } catch (Exception ex) { - Logger.getLogger(Case.class - .getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS + logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS } }); - } catch (CaseMetadataException ex) { - throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex); } } /** - * Constructs an Autopsy case. - */ - private Case() { - } - - /** + * Constructs a Case object for a new Autopsy case. + * * @param caseType The type of case (single-user or multi-user). * @param caseDir The full path of the case directory. The directory * will be created if it doesn't already exist; if it @@ -1555,123 +1523,158 @@ public class Case { * @param caseNumber The case number, can be the empty string. * @param examiner The examiner to associate with the case, can be * the empty string. + */ + private Case(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner) { + metadata = new CaseMetadata(caseDir, caseType, displayNameToUniqueName(caseDisplayName), caseDisplayName, caseNumber, examiner); + } + + /** + * Constructs a Case object for an existing Autopsy case. * - * @throws CaseActionException if there is a problem creating the case. The + * @param caseMetaData The metadata for the case. + */ + private Case(CaseMetadata caseMetaData) { + metadata = caseMetaData; + } + + /** + * Opens this case by creating a task running in the same non-UI thread that + * will be used to close the case. If the case is a single-user case, this + * supports cancelling creation of the case by cancelling the task. If the + * case is a multi-user case, this ensures ensures that case directory lock + * held as long as the case is open is released in the same thread in which + * it was acquired, as is required by the coordination service. + * + * @param isNewCase True for a new case, false otherwise. + * + * @throws CaseActionException If there is a problem creating the case. The * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. */ @Messages({ "Case.progressIndicatorTitle.creatingCase=Creating Case", + "Case.progressIndicatorTitle.openingCase=Opening Case", "Case.progressIndicatorCancelButton.label=Cancel", "Case.progressMessage.preparing=Preparing...", - "Case.progressMessage.openingCaseResources=Preparing to open case resources.
This may take time if another user is upgrading the case." + "Case.progressMessage.preparingToOpenCaseResources=Preparing to open case resources.
This may take time if another user is upgrading the case.", + "Case.progressMessage.cancelling=Cancelling...", + "Case.exceptionMessage.cancelledByUser=Cancelled by user.", + "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}" }) - private void create(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException { + private void open(boolean isNewCase) throws CaseActionException { /* - * Set up either a GUI progress indicator or a logging progress - * indicator. + * Create and start either a GUI progress indicator with a Cancel button + * or a logging progress indicator. */ - final CancelButtonListener listener = new CancelButtonListener(); + CancelButtonListener cancelButtonListener = null; ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { + cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling()); + String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase(); progressIndicator = new ModalDialogProgressIndicator( mainFrame, - Bundle.Case_progressIndicatorTitle_creatingCase(), + progressIndicatorTitle, new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), - listener); + cancelButtonListener); } else { progressIndicator = new LoggingProgressIndicator(); } progressIndicator.start(Bundle.Case_progressMessage_preparing()); /* - * Creating a case is always done in the same non-UI thread that will be - * used later to close the case. If the case is a multi-user case, this - * ensures that case directory lock that is held as long as the case is - * open is released in the same thread in which it was acquired, as is - * required by the coordination service. + * Creating/opening a case is always done by creating a task running in + * the same non-UI thread that will be used to close the case, so a + * single-threaded executor service is created here and saved as case + * state (must be volatile for cancellation to work). + * + * --- If the case is a single-user case, this supports cancelling + * opening of the case by cancelling the task. + * + * --- If the case is a multi-user case, this still supports + * cancellation, but it also makes it possible for the shared case + * directory lock held as long as the case is open to be released in the + * same thread in which it was acquired, as is required by the + * coordination service. */ - caseLockingExecutor = Executors.newSingleThreadExecutor(); + TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName())); + caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory); Future future = caseLockingExecutor.submit(() -> { - if (CaseType.SINGLE_USER_CASE == caseType) { - create(caseType, caseDir, caseDisplayName, caseNumber, examiner, progressIndicator); + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { + open(isNewCase, progressIndicator); } else { /* - * Acquire a shared case directory lock that will be held as - * long as this node has this case open. This will prevent - * deletion of the case by another node. + * First, acquire a shared case directory lock that will be held + * as long as this node has this case open. This will prevent + * deletion of the case by another node. Next, acquire an + * exclusive case resources lock to ensure only one node at a + * time can create/open/upgrade/close the case resources. */ - progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources()); - acquireSharedCaseDirLock(caseDir); - - /* - * Acquire an exclusive case resources lock to ensure only one - * node at a time can create/open/upgrade/close the case - * resources. - */ - try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseDir)) { + progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources()); + acquireSharedCaseDirLock(metadata.getCaseDirectory()); + try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) { assert (null != resourcesLock); - try { - create(caseType, caseDir, caseDisplayName, caseNumber, examiner, progressIndicator); - } catch (CaseActionException ex) { - /* - * Release the case directory lock immediately if there - * was a problem opening the case. - */ - if (CaseType.MULTI_USER_CASE == caseType) { - releaseSharedCaseDirLock(caseDir); - } - throw ex; - } + open(isNewCase, progressIndicator); + } catch (CaseActionException ex) { + releaseSharedCaseDirLock(getMetadata().getCaseDirectory()); } } return null; }); - - /* - * If running with a GUI, give the future for the case creation task to - * the cancel button listener for the GUI progress indicator and make - * the progress indicator visible to the user. - */ - if (RuntimeProperties.runningWithGUI()) { - listener.setCaseActionFuture(future); - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + if (null != cancelButtonListener) { + cancelButtonListener.setCaseActionFuture(future); } /* - * Wait for the case creation task to finish. + * Wait for the case creation/opening task to finish. */ try { future.get(); - } catch (InterruptedException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex); - } catch (ExecutionException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex); - } catch (CancellationException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex); - } finally { - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); + } catch (InterruptedException discarded) { + /* + * The thread this method is running in has been interrupted. Cancel + * the create/open task, wait for it to finish, and shut down the + * executor. This can be done safely because if the task is + * completed with a cancellation condition, the case will have been + * closed and the case directory lock released will have been + * released. + */ + if (null != cancelButtonListener) { + cancelButtonListener.actionPerformed(null); + } else { + future.cancel(true); } + Case.shutDownTaskExecutor(caseLockingExecutor); + } catch (CancellationException discarded) { + /* + * The create/open task has been cancelled. Wait for it to finish, + * and shut down the executor. This can be done safely because if + * the task is completed with a cancellation condition, the case + * will have been closed and the case directory lock released will + * have been released. + */ + Case.shutDownTaskExecutor(caseLockingExecutor); + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } catch (ExecutionException ex) { + /* + * The create/open task has thrown an exception. Wait for it to + * finish, and shut down the executor. This can be done safely + * because if the task is completed with an execution condition, the + * case will have been closed and the case directory lock released + * will have been released. + */ + Case.shutDownTaskExecutor(caseLockingExecutor); + throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex); + } finally { + progressIndicator.finish(); } } /** - * Creates a new case. + * Opens the case database and services for this case. * - * @param caseType The type of case (single-user or multi-user). - * @param caseDir The full path of the case directory. The - * directory will be created if it doesn't already - * exist; if it exists, it is ASSUMED it was - * created by calling createCaseDirectory. - * @param caseName The case name. - * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be - * the empty string. + * @param isNewCase True for a new case, false otherwise. * @param progressIndicator A progress indicator. * * @throws CaseActionException if there is a problem creating the case. The @@ -1679,204 +1682,100 @@ public class Case { * and may be a wrapper for a lower-level * exception. */ + private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException { + try { + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + + if (isNewCase) { + createCaseData(progressIndicator); + } else { + openCaseData(progressIndicator); + } + + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + + openServices(progressIndicator); + + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + } catch (CaseActionException ex) { + /* + * Cancellation or failure. Clean up. The sleep is a little hack to + * clear the interrupted flag for this thread if this is a + * cancellation scenario, so that the clean up can run to completion + * in this thread. + */ + try { + Thread.sleep(1); + } catch (InterruptedException discarded) { + } + close(progressIndicator); + throw ex; + } + } + + /** + * Creates the case directory, case database, and case metadata file. + * + * @param progressIndicator A progress indicartor. + * + * @throws CaseActionException If there is a problem creating the case + * database. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ @Messages({ - "Case.exceptionMessage.emptyCaseName=Case name is empty.", "Case.progressMessage.creatingCaseDirectory=Creating case directory...", "Case.progressMessage.creatingCaseDatabase=Creating case database...", - "Case.exceptionMessage.couldNotCreateCaseDatabaseName=Failed to create case database name from case name.", - "Case.progressMessage.creatingCaseMetadataFile=Creating case metadata file...", - "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file.", - "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database." + "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database.", + "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file." }) - private void create(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner, ProgressIndicator progressIndicator) throws CaseActionException { - /* - * Create a unique and immutable case name from the case display name. - */ - if (caseDisplayName.isEmpty()) { - throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName()); - } - String caseName = displayNameToUniqueName(caseDisplayName); - + private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException { /* * Create the case directory, if it does not already exist. * - * TODO (JIRA-2180): The reason for this check for the existence of the - * case directory is not at all obvious. It reflects the assumption that - * if the case directory already exists, it is because the case is being - * created using the the "New Case" wizard, which separates the creation - * of the case directory from the creation of the case, with the idea - * that if the case directory cannot be created, the user can be asked - * to supply a different case directory path. This of course creates - * subtle and undetectable coupling between this code and the wizard - * code. The desired effect could be accomplished more simply and safely - * by having this method throw a specific exception to indicate that the - * case directory could not be created. In fact, a FEW specific - * exception types would in turn allow us to put localized, - * user-friendly messages in the GUI instead of putting user-friendly, - * localized messages in the exceptions, which causes them to appear in - * the application log, where it would be better to use English for - * readability by the broadest group of developers. + * TODO (JIRA-2180): Always create the case directory as part of the + * case creation process. */ - progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); - if (new File(caseDir).exists() == false) { - Case.createCaseDirectory(caseDir, caseType); + if (new File(metadata.getCaseDirectory()).exists() == false) { + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); + Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType()); } /* * Create the case database. */ progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase()); - String dbName = null; try { - if (CaseType.SINGLE_USER_CASE == caseType) { + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { /* * For single-user cases, the case database is a SQLite database * with a standard name, physically located in the root of the * case directory. */ - dbName = SINGLE_USER_CASE_DB_NAME; - this.caseDb = SleuthkitCase.newCase(Paths.get(caseDir, SINGLE_USER_CASE_DB_NAME).toString()); - } else if (CaseType.MULTI_USER_CASE == caseType) { + caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString()); + metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME); + } else { /* * For multi-user cases, the case database is a PostgreSQL * database with a name derived from the case display name, - * physically located on the database server. + * physically located on a database server. */ - this.caseDb = SleuthkitCase.newCase(caseDisplayName, UserPreferences.getDatabaseConnectionInfo(), caseDir); - dbName = this.caseDb.getDatabaseName(); + caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); + metadata.setCaseDatabaseName(caseDb.getDatabaseName()); } } catch (TskCoreException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(), ex); } catch (UserPreferencesException ex) { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); - } - - /* - * Create the case metadata (.aut) file. - */ - progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseMetadataFile()); - try { - this.caseMetadata = new CaseMetadata(caseDir, caseType, caseName, caseDisplayName, caseNumber, examiner, dbName); } catch (CaseMetadataException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex); } - - openServices(progressIndicator); - } - - /** - * Opens an existing case. - * - * @param caseMetadataFilePath The path to the case metadata file. - * - * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. - */ - private void open(Path caseMetadataFilePath) throws CaseActionException { - /* - * Read the contents of the case metadata file. - */ - try { - caseMetadata = new CaseMetadata(caseMetadataFilePath); - } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_failedToReadMetadata()), ex); - } - - /* - * Set up either a GUI progress indicator or a logging progress - * indicator. - */ - CancelButtonListener listener = new CancelButtonListener(); - ProgressIndicator progressIndicator; - if (RuntimeProperties.runningWithGUI()) { - progressIndicator = new ModalDialogProgressIndicator( - mainFrame, - Bundle.Case_progressIndicatorTitle_openingCase(), - new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, - Bundle.Case_progressIndicatorCancelButton_label(), - listener); - } else { - progressIndicator = new LoggingProgressIndicator(); - } - progressIndicator.start(Bundle.Case_progressMessage_preparing()); - - /* - * Opening the case in the same thread that will be used later to close - * the case. If the case is a multi-user case, this ensures that case - * directory lock that is held as long as the case is open is released - * in the same thread in which it was acquired, as is required by the - * coordination service. - */ - CaseType caseType = caseMetadata.getCaseType(); - String caseName = caseMetadata.getCaseName(); - progressIndicator.start(Bundle.Case_progressMessage_preparing()); - caseLockingExecutor = Executors.newSingleThreadExecutor(); - Future future = caseLockingExecutor.submit(() -> { - if (CaseType.SINGLE_USER_CASE == caseType) { - openCaseDatabase(progressIndicator); - openServices(progressIndicator); - } else { - /* - * First, acquire a shared case directory lock that will be held - * as long as this node has this case open, in order to prevent - * deletion of the case by another node. - */ - progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources()); - acquireSharedCaseDirLock(caseMetadata.getCaseDirectory()); - /* - * Next, acquire an exclusive case resources lock to ensure only - * one node at a time can create/open/upgrade/close case - * resources. - */ - try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseDirectory())) { - assert (null != resourcesLock); - try { - openCaseDatabase(progressIndicator); - openServices(progressIndicator); - } catch (CaseActionException ex) { - /* - * Release the case directory lock immediately if there - * was a problem opening the case. - */ - if (CaseType.MULTI_USER_CASE == caseType) { - releaseSharedCaseDirLock(caseName); - } - throw ex; - } - } - } - return null; - }); - - /* - * If running with a GUI, give the future for the case opening task to - * the cancel button listener for the GUI progress indicator and make - * the progress indicator visible to the user. - */ - if (RuntimeProperties.runningWithGUI()) { - listener.setCaseActionFuture(future); - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); - } - - /* - * Wait for the case opening task to finish. - */ - try { - future.get(); - - } catch (InterruptedException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex); - } catch (ExecutionException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex); - } catch (CancellationException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex); - } finally { - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); - } - } } /** @@ -1893,24 +1792,24 @@ public class Case { "Case.progressMessage.openingCaseDatabase=Opening case database...", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database." }) - private void openCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException { - /* - * Open the case database. - */ + private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException { try { progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); - if (CaseType.SINGLE_USER_CASE == caseMetadata.getCaseType()) { - this.caseDb = SleuthkitCase.openCase(Paths.get(caseMetadata.getCaseDirectory(), caseMetadata.getCaseDatabaseName()).toString()); + String databaseName = metadata.getCaseDatabaseName(); + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { + caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString()); } else if (UserPreferences.getIsMultiUserModeEnabled()) { try { - this.caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory()); + caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); } catch (UserPreferencesException ex) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + throw new CaseActionException(NbBundle.getMessage(Case.class, + "Case.databaseConnectionInfo.error.msg"), ex); } } else { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled")); + throw new CaseActionException(NbBundle.getMessage(Case.class, + "Case.open.exception.multiUserCaseNotEnabled")); } } catch (TskCoreException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex); @@ -1928,6 +1827,7 @@ public class Case { @Messages({ "Case.progressMessage.switchingLogDirectory=Switching log directory...", "Case.progressMessage.settingUpTskErrorReporting=Setting up SleuthKit error reporting...", + "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", "Case.progressMessage.openingCaseLevelServices=Opening case-level services...", "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...", "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",}) @@ -1938,25 +1838,39 @@ public class Case { */ progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); Logger.setLogDirectory(getLogDirectoryPath()); + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } /* * Hook up a SleuthKit layer error reporter. */ progressIndicator.progress(Bundle.Case_progressMessage_settingUpTskErrorReporting()); - this.sleuthkitErrorReporter = new SleuthkitErrorReporter(MIN_SECS_BETWEEN_TSK_ERROR_REPORTS, NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText")); - this.caseDb.addErrorObserver(this.sleuthkitErrorReporter); + sleuthkitErrorReporter + = new SleuthkitErrorReporter(MIN_SECS_BETWEEN_TSK_ERROR_REPORTS, NbBundle.getMessage(Case.class, + "IntervalErrorReport.ErrorText")); + caseDb.addErrorObserver(this.sleuthkitErrorReporter); + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } /* * Clear the temp subdirectory of the case directory. */ progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory()); Case.clearTempSubDir(this.getTempDirectory()); + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } /* * Open the case-level services. */ progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices()); - this.caseServices = new Services(this.caseDb); + this.caseServices = new Services(caseDb); + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } /* * Allow any registered application services to open any resources @@ -1964,16 +1878,22 @@ public class Case { */ progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); openAppServiceCaseResources(); + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } /* * If this case is a multi-user case, set up for communication with * other nodes. */ - if (CaseType.MULTI_USER_CASE == this.caseMetadata.getCaseType()) { + if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications()); try { - eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, this.getName())); - collaborationMonitor = new CollaborationMonitor(this.getName()); + eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName())); + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + collaborationMonitor = new CollaborationMonitor(metadata.getCaseName()); } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) { /* * The collaboration monitor and event channel are not @@ -1981,99 +1901,116 @@ public class Case { * throw. */ logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"))); + SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error( + NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), + NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"))); } } } } /** - * Allows any registered application-level services to open any resources + * Allows any registered application-level services to open resources * specific to this case. - * - * @throws CaseActionException */ @NbBundle.Messages({ "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources", - "# {0} - service name", "Case.servicesException.notificationTitle={0} Error", - "# {0} - service name", "Case.servicesException.serviceResourcesOpenCancelled=Opening case resources for {0} cancelled", - "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesOpenError=Could not open case resources for {0} service: {1}" + "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...", + "# {0} - service name", "Case.servicesException.notificationTitle={0} Error" }) private void openAppServiceCaseResources() throws CaseActionException { /* - * Each service gets its own independently cancellable task, and thus - * its own task progress indicator. + * Each service gets its own independently cancellable/interruptible + * task, running in a named thread managed by an executor service, with + * its own progress indicator. This allows for cancellation of the + * opening of case resources for individual services. It also makes it + * possible to ensure that each service task completes before the next + * one starts by awaiting termination of the executor service. */ - ExecutorService executor = Executors.newSingleThreadExecutor(); - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { - CancelButtonListener buttonListener = new CancelButtonListener(); + /* + * Create a progress indicator for the task and start the task. If + * running with a GUI, the progress indicator will be a dialog box + * with a Cancel button. + */ + CancelButtonListener cancelButtonListener = null; ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { + cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName())); progressIndicator = new ModalDialogProgressIndicator( mainFrame, Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()), new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), - buttonListener); + cancelButtonListener); } else { progressIndicator = new LoggingProgressIndicator(); } progressIndicator.start(Bundle.Case_progressMessage_preparing()); - AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator); - if (RuntimeProperties.runningWithGUI()) { - buttonListener.setCaseContext(context); - } + String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS + threadNameSuffix = threadNameSuffix.toLowerCase(); + TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix)); + ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory); Future future = executor.submit(() -> { service.openCaseResources(context); return null; }); - if (RuntimeProperties.runningWithGUI()) { - buttonListener.setCaseActionFuture(future); - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + if (null != cancelButtonListener) { + cancelButtonListener.setCaseContext(context); + cancelButtonListener.setCaseActionFuture(future); } + + /* + * Wait for the task to either be completed or + * cancelled/interrupted, or for the opening of the case to be + * cancelled. + */ try { future.get(); - } catch (InterruptedException ex) { - Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to open case resources", service.getServiceName()), ex); - - } catch (CancellationException ex) { + } catch (InterruptedException discarded) { /* - * The case-specific application service resources are not - * essential. Log an error and notify the user if running the - * desktop GUI, but do not throw. + * The parent create/open case task has been cancelled. */ - Case.logger.log(Level.WARNING, String.format("%s service opening of case resources cancelled", service.getServiceName())); - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.warn( - Bundle.Case_servicesException_notificationTitle(service.getServiceName()), - Bundle.Case_servicesException_serviceResourcesOpenCancelled(service.getServiceName()))); - } + Case.logger.log(Level.WARNING, String.format("Opening of %s (%s) in %s cancelled during opening of case resources by %s", getDisplayName(), getName(), getCaseDirectory(), service.getServiceName())); + future.cancel(true); + } catch (CancellationException discarded) { + /* + * The opening of case resources by the application service has + * been cancelled, so the executor service has thrown. Note that + * there is no guarantee the task itself has responded to the + * cancellation request yet. + */ + Case.logger.log(Level.WARNING, String.format("Opening of case resources by %s for %s (%s) in %s cancelled", service.getServiceName(), getDisplayName(), getName(), getCaseDirectory(), service.getServiceName())); } catch (ExecutionException ex) { /* - * The case-specific application service resources are not + * An exception was thrown while executing the task. The + * case-specific application service resources are not * essential. Log an error and notify the user if running the * desktop GUI, but do not throw. */ - Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex); + Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex); if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error( - Bundle.Case_servicesException_notificationTitle(service.getServiceName()), - Bundle.Case_servicesException_serviceResourcesOpenError(service.getServiceName(), ex.getLocalizedMessage()))); + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage()); + }); } } finally { - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); - } + /* + * Shut down the executor service and wait for it to finish. + * This ensures that the task has finished. Without this, it + * would be possible to start the next task before the current + * task responded to a cancellation request. + */ + shutDownTaskExecutor(executor); + progressIndicator.finish(); + } + + if (Thread.currentThread().isInterrupted()) { + return; } } - /* - * No tasks left, simply shut down the executor. - */ - executor.shutdown(); } /** @@ -2081,25 +2018,15 @@ public class Case { * * @param progressIndicator A progress indicator. */ - @Messages({ - "Case.progressMessage.closingCaseResources=Preparing to close case resources.
This may take time if another user is upgrading the case.", - "Case.progressMessage.notifyingCaseEventSubscribers=Notifying case event subscribers...", - "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", - "Case.progressMessage.closingCaseLevelServices=Closing case-level services...", - "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...", - "Case.progressMessage.tearingDownNetworkCommunications=Tearing down network communications...", - "Case.progressMessage.closingCaseDatabase=Closing case database...", - "Case.progressMessage.tearingDownTskErrorReporting=Tearing down SleuthKit error reporting..." - }) private void close() throws CaseActionException { /* - * Set up either a GUI progress indicator or a logging progress - * indicator. + * Set up either a GUI progress indicator without a Cancel button or a + * logging progress indicator. */ ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { progressIndicator = new ModalDialogProgressIndicator( - Case.mainFrame, + mainFrame, Bundle.Case_progressIndicatorTitle_closingCase()); } else { progressIndicator = new LoggingProgressIndicator(); @@ -2114,17 +2041,16 @@ public class Case { * required by the coordination service. */ Future future = caseLockingExecutor.submit(() -> { - if (CaseType.SINGLE_USER_CASE == caseMetadata.getCaseType()) { + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { close(progressIndicator); } else { - String caseName = caseMetadata.getCaseName(); /* * Acquire an exclusive case resources lock to ensure only one * node at a time can create/open/upgrade/close the case * resources. */ - progressIndicator.start(Bundle.Case_progressMessage_closingCaseResources()); - try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseDirectory())) { + progressIndicator.progress(Bundle.Case_progressMessage_preparing()); + try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) { assert (null != resourcesLock); close(progressIndicator); } finally { @@ -2132,38 +2058,40 @@ public class Case { * Always release the case directory lock that was acquired * when the case was opened. */ - releaseSharedCaseDirLock(caseName); + releaseSharedCaseDirLock(metadata.getCaseName()); } } return null; }); - /* - * If running with a GUI, give the future for the case closing task to - * the cancel button listener for the GUI progress indicator and make - * the progress indicator visible to the user. - */ - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); - } - try { - logger.log(Level.INFO, "Closing case with metadata file path {0}", getCaseMetadata().getFilePath()); //NON-NLS future.get(); - } catch (InterruptedException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex); + } catch (InterruptedException | CancellationException unused) { + /* + * The wait has been interrupted by interrupting the thread running + * this method. Not allowing cancellation of case closing, so ignore + * the interrupt. Likewsie, cancellation of the case closing task is + * not supported. + */ } catch (ExecutionException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex); - } catch (CancellationException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex); } finally { - caseLockingExecutor.shutdown(); - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); - } + shutDownTaskExecutor(caseLockingExecutor); + progressIndicator.finish(); } } + /** + * Closes the case. + * + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...", + "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...", + "Case.progressMessage.closingCaseLevelServices=Closing case-level services...", + "Case.progressMessage.closingCaseDatabase=Closing case database...", + "Case.progressMessage.shuttingDownTskErrorReporting=Shutting down SleuthKit error reporting..."}) private void close(ProgressIndicator progressIndicator) { IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); @@ -2171,8 +2099,8 @@ public class Case { * Stop sending/receiving case events to and from other nodes if this is * a multi-user case. */ - if (CaseType.MULTI_USER_CASE == caseMetadata.getCaseType()) { - progressIndicator.progress(Bundle.Case_progressMessage_tearingDownNetworkCommunications()); + if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { + progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications()); if (null != collaborationMonitor) { collaborationMonitor.shutdown(); } @@ -2189,24 +2117,26 @@ public class Case { /* * Close the case-level services. */ - progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices()); - try { - this.caseServices.close(); - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex); + if (null != caseServices) { + progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices()); + try { + this.caseServices.close(); + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex); + } } /* * Close the case database */ - progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase()); - caseDb.close(); - - /* - * Disconnect the SleuthKit layer error reporter. - */ - progressIndicator.progress(Bundle.Case_progressMessage_tearingDownTskErrorReporting()); - caseDb.removeErrorObserver(sleuthkitErrorReporter); + if (null != caseDb) { + progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase()); + caseDb.close(); + if (null != sleuthkitErrorReporter) { + progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownTskErrorReporting()); + caseDb.removeErrorObserver(sleuthkitErrorReporter); + } + } /* * Switch the log directory. @@ -2228,10 +2158,7 @@ public class Case { * Each service gets its own independently cancellable task, and thus * its own task progress indicator. */ - ExecutorService executor = Executors.newSingleThreadExecutor(); - - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class - )) { + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { progressIndicator = new ModalDialogProgressIndicator( @@ -2241,23 +2168,21 @@ public class Case { progressIndicator = new LoggingProgressIndicator(); } progressIndicator.start(Bundle.Case_progressMessage_preparing()); - AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator); + String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS + threadNameSuffix = threadNameSuffix.toLowerCase(); + TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix)); + ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory); Future future = executor.submit(() -> { service.closeCaseResources(context); return null; }); - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); - } try { future.get(); } catch (InterruptedException ex) { Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex); - } catch (CancellationException ex) { Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex); - } catch (ExecutionException ex) { Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex); if (RuntimeProperties.runningWithGUI()) { @@ -2266,15 +2191,10 @@ public class Case { Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage()))); } } finally { - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); - } + shutDownTaskExecutor(executor); + progressIndicator.finish(); } } - /* - * No tasks left, simply shut down the executor. - */ - executor.shutdown(); } /** @@ -2328,28 +2248,159 @@ public class Case { } + /** + * Shuts down a task executor service, waiting until all tasks are + * terminated. The current policy is to wait for the tasks to finish so that + * the case for which the executor is running can be left in a consistent + * state. + * + * @param executor The executor. + */ + private static void shutDownTaskExecutor(ExecutorService executor) { + executor.shutdown(); + boolean taskCompleted = false; + while (!taskCompleted) { + try { + taskCompleted = executor.awaitTermination(EXECUTOR_AWAIT_TIMEOUT_SECS, TimeUnit.SECONDS); + } catch (InterruptedException ignored) { + /* + * The current policy is to wait for the task to finish so that + * the case can be left in a consistent state. + * + * For a specific example of the motivation for this policy, + * note that a application service (Solr search service) + * experienced an error condition when opening case resources + * that left the service blocked uninterruptibly on a socket + * read. This eventually led to a mysterious "freeze" as the + * user-cancelled service task continued to run holdiong a lock + * that a UI thread soon tried to acquire. Thus it has been + * deemed better to make the "freeze" happen in a more + * informative way, i.e., with the progress indicator for the + * unfinished task on the screen, if a similar error condition + * arises again. + */ + } + } + } + + /** + * A case operation Cancel button listener for use with a + * ModalDialogProgressIndicator when running with a GUI. + */ + @ThreadSafe private final static class CancelButtonListener implements ActionListener { - private Future caseActionFuture; + private final String cancellationMessage; + @GuardedBy("this") + private boolean cancelRequested; + @GuardedBy("this") private CaseContext caseContext; + @GuardedBy("this") + private Future caseActionFuture; - private void setCaseActionFuture(Future caseActionFuture) { - this.caseActionFuture = caseActionFuture; + /** + * Constructs a case operation Cancel button listener for use with a + * ModalDialogProgressIndicator when running with a GUI. + * + * @param cancellationMessage The message to display in the + * ModalDialogProgressIndicator when the + * cancel button is pressed. + */ + private CancelButtonListener(String cancellationMessage) { + this.cancellationMessage = cancellationMessage; } - private void setCaseContext(CaseContext caseContext) { + /** + * Sets a case context for this listener. + * + * @param caseContext A case context object. + */ + private synchronized void setCaseContext(CaseContext caseContext) { this.caseContext = caseContext; + /* + * If the cancel button has already been pressed, pass the + * cancellation on to the case context. + */ + if (cancelRequested) { + cancel(); + } } + /** + * Sets a Future object for a task associated with this listener. + * + * @param caseActionFuture A task Future object. + */ + private synchronized void setCaseActionFuture(Future caseActionFuture) { + this.caseActionFuture = caseActionFuture; + /* + * If the cancel button has already been pressed, cancel the Future + * of the task. + */ + if (cancelRequested) { + cancel(); + } + } + + /** + * The event handler for Cancel button pushes. + * + * @param event The button event, ignored, can be null. + */ @Override - public void actionPerformed(ActionEvent e) { + public synchronized void actionPerformed(ActionEvent event) { + cancel(); + } + + /** + * Handles a cancellation request. + */ + private void cancel() { + /* + * At a minimum, set the cancellation requested flag of this + * listener. + */ + this.cancelRequested = true; if (null != this.caseContext) { + /* + * Set the cancellation request flag and display the + * cancellation message in the progress indicator for the case + * context associated with this listener. + */ + if (RuntimeProperties.runningWithGUI()) { + ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator(); + if (progressIndicator instanceof ModalDialogProgressIndicator) { + ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage); + } + } this.caseContext.requestCancel(); } if (null != this.caseActionFuture) { + /* + * Cancel the Future of the task associated with this listener. + * Note that the task thread will be interrupted if the task is + * blocked. + */ this.caseActionFuture.cancel(true); } } + } + + /** + * A thread factory that provides named threads. + */ + private static class TaskThreadFactory implements ThreadFactory { + + private final String threadName; + + private TaskThreadFactory(String threadName) { + this.threadName = threadName; + } + + @Override + public Thread newThread(Runnable task) { + return new Thread(task, threadName); + } } @@ -2594,4 +2645,16 @@ public class Case { deleteReports(reports); } + /** + * Sets the name of the keyword search index for the case. + * + * @param textIndexName The text index name. + * + * @throws CaseMetadataException + * @dprecated Do not use. + */ + @Deprecated + public void setTextIndexName(String textIndexName) throws CaseMetadataException { + } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java new file mode 100755 index 0000000000..042bfe912e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java @@ -0,0 +1,49 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 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.casemodule; + +/** + * Exception thrown when a case action (e.g., create, open, close, delete) is + * cancelled before it is completed. + */ +class CaseActionCancelledException extends CaseActionException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an exception thrown when a case action (e.g., create, open, + * close, delete) is cancelled before it is completed. + * + * @param message An error message. + */ + CaseActionCancelledException(String message) { + super(message); + } + + /** + * Constructs an exception thrown when a case action (e.g., create, open, + * close, delete) is cancelled before it is completed. + * + * @param message An error message. + * @param cause An excception that caused this exception to be thrown. + */ + CaseActionCancelledException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionException.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionException.java index 01bad4a628..3cbcc6fedf 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionException.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionException.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2012 Basis Technology Corp. + * + * Copyright 2011-2017 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. @@ -19,15 +19,30 @@ package org.sleuthkit.autopsy.casemodule; /** - * Exception thrown when case action (such as open, close, create) resulted in - * an error + * Exception thrown when a case action (e.g., create, open, close, delete) + * experiences an error condition. */ public class CaseActionException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Constructs an exception thrown when a case action (e.g., create, open, + * close, delete) experiences an error condition. + * + * @param message An error message. + */ public CaseActionException(String message) { super(message); } + /** + * Constructs an exception thrown when a case action (e.g., create, open, + * close, delete) experiences an error condition. + * + * @param message An error message. + * @param cause An excception that caused this exception to be thrown. + */ public CaseActionException(String message, Throwable cause) { super(message, cause); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index cc81d70a30..b51f0d8a63 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011-2016 Basis Technology Corp. + * + * Copyright 2011-2017 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. @@ -19,10 +19,6 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.event.ActionListener; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.JOptionPane; import javax.swing.event.ChangeEvent; import org.openide.util.NbBundle.Messages; @@ -32,44 +28,40 @@ import org.openide.util.NbBundle.Messages; */ class CaseInformationPanel extends javax.swing.JPanel { - private static final Logger logger = Logger.getLogger(CaseInformationPanel.class.getName()); + private static final long serialVersionUID = 1L; /** - * Creates new form CaseInformationPanel + * Constructs a panel for displaying the case information, including both + * case details and ingest job history. */ CaseInformationPanel() { initComponents(); customizeComponents(); } - @Messages({"CaseInformationPanel.caseDetails.header=Case Details", - "CaseInformationPanel.ingestJobInfo.header=Ingest History", - "CaseInformationPanel.loadMetadataFail.message=Failed to load case metadata.", - "CaseInformationPanel.loadMetadataFail.title=Metadata load failure",}) + @Messages({ + "CaseInformationPanel.caseDetails.header=Case Details", + "CaseInformationPanel.ingestJobInfo.header=Ingest History" + }) private void customizeComponents() { - try { - Case currentCase = Case.getCurrentCase(); - String crDate = currentCase.getCreatedDate(); - String caseDir = currentCase.getCaseDirectory(); - - // put the image paths information into hashmap - Map imgPaths = Case.getImagePaths(currentCase.getSleuthkitCase()); - CasePropertiesPanel cpf = new CasePropertiesPanel(currentCase, crDate, caseDir, imgPaths); - cpf.setSize(cpf.getPreferredSize()); - this.tabbedPane.addTab(Bundle.CaseInformationPanel_caseDetails_header(), cpf); - this.tabbedPane.addTab(Bundle.CaseInformationPanel_ingestJobInfo_header(), new IngestJobInfoPanel()); - this.tabbedPane.addChangeListener((ChangeEvent e) -> { - tabbedPane.getSelectedComponent().setSize(tabbedPane.getSelectedComponent().getPreferredSize()); - }); - } catch (CaseMetadata.CaseMetadataException ex) { - logger.log(Level.SEVERE, "Failed to load case metadata.", ex); - JOptionPane.showMessageDialog(null, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); - } + CasePropertiesPanel propertiesPanel = new CasePropertiesPanel(Case.getCurrentCase()); + propertiesPanel.setSize(propertiesPanel.getPreferredSize()); + this.tabbedPane.addTab(Bundle.CaseInformationPanel_caseDetails_header(), propertiesPanel); + this.tabbedPane.addTab(Bundle.CaseInformationPanel_ingestJobInfo_header(), new IngestJobInfoPanel()); + this.tabbedPane.addChangeListener((ChangeEvent e) -> { + tabbedPane.getSelectedComponent().setSize(tabbedPane.getSelectedComponent().getPreferredSize()); + }); } - + + /** + * Adds an action listener to the Close button of the panel. + * + * @param action + */ void addCloseButtonAction(ActionListener action) { this.closeButton.addActionListener(action); } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -127,7 +119,7 @@ class CaseInformationPanel extends javax.swing.JPanel { }// //GEN-END:initComponents private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed - // TODO add your handling code here: + // Used by CasePropertiesAction }//GEN-LAST:event_closeButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 9a870e25a2..1f4c5b7912 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -54,7 +54,9 @@ public final class CaseMetadata { private static final String FILE_EXTENSION = ".aut"; private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)"); - //fields from schema version 1 + /* + * Fields from schema version 1 + */ private static final String SCHEMA_VERSION_ONE = "1.0"; private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS @@ -68,22 +70,28 @@ public final class CaseMetadata { private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS - //fields from schema version 2 + /* + * Fields from schema version 2 + */ private static final String SCHEMA_VERSION_TWO = "2.0"; private final static String AUTOPSY_CREATED_BY_ELEMENT_NAME = "CreatedByAutopsyVersion"; //NON-NLS - private final static String CASE_DATABASE_ABSOLUTE_PATH_ELEMENT_NAME = "Database"; //NON-NLS + private final static String CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME = "Database"; //NON-NLS private final static String TEXT_INDEX_ELEMENT = "TextIndex"; //NON-NLS - //fields from schema version 3 + /* + * Fields from schema version 3 + */ private static final String SCHEMA_VERSION_THREE = "3.0"; private final static String CASE_DISPLAY_NAME_ELEMENT_NAME = "DisplayName"; //NON-NLS - private final static String CASE_DATABASE_NAME_RELATIVE_ELEMENT_NAME = "CaseDatabase"; //NON-NLS + private final static String CASE_DB_NAME_RELATIVE_ELEMENT_NAME = "CaseDatabase"; //NON-NLS - //unread fields, these are regenerated on save + /* + * Unread fields, regenerated on save. + */ private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS - private static final String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_THREE; + private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_THREE; private final Path metadataFilePath; private Case.CaseType caseType; @@ -92,8 +100,8 @@ public final class CaseMetadata { private String caseNumber; private String examiner; private String caseDatabaseName; - private String caseDatabasePath; - private String textIndexName; + private String caseDatabasePath; // Legacy + private String textIndexName; // Legacy private String createdDate; private String createdByVersion; @@ -107,8 +115,9 @@ public final class CaseMetadata { } /** - * Constructs an object that provides access to the case metadata stored in - * a new case metadata file that is created using the supplied metadata. + * Constructs a CaseMetadata object for a new case. The metadata is not + * persisted to the case metadata file until writeFile or a setX method is + * called. * * @param caseDirectory The case directory. * @param caseType The type of case. @@ -117,29 +126,24 @@ public final class CaseMetadata { * user. * @param caseNumber The case number. * @param examiner The name of the case examiner. - * @param caseDatabase For a single-user case, the full path to the case - * database file. For a multi-user case, the case - * database name. - * - * @throws CaseMetadataException If the new case metadata file cannot be - * created. */ - CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseDisplayName, String caseNumber, String examiner, String caseDatabase) throws CaseMetadataException { + CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseDisplayName, String caseNumber, String examiner) { metadataFilePath = Paths.get(caseDirectory, caseDisplayName + FILE_EXTENSION); this.caseType = caseType; this.caseName = caseName; this.caseDisplayName = caseDisplayName; this.caseNumber = caseNumber; this.examiner = examiner; - this.caseDatabaseName = caseDatabase; + caseDatabaseName = ""; + caseDatabasePath = ""; + textIndexName = ""; createdByVersion = Version.getVersion(); createdDate = CaseMetadata.DATE_FORMAT.format(new Date()); - writeToFile(); } /** - * Constructs an object that provides access to the case metadata stored in - * an existing case metadata file. + * Constructs a CaseMetadata object for an existing case. The metadata is + * read from an existing case metadata file. * * @param metadataFilePath The full path to the case metadata file. * @@ -179,7 +183,7 @@ public final class CaseMetadata { } /** - * Gets the immutable case name, set at case creation. + * Gets the unique and immutable case name. * * @return The case display name. */ @@ -193,22 +197,23 @@ public final class CaseMetadata { * @return The case display name. */ public String getCaseDisplayName() { - return this.caseDisplayName; + return caseDisplayName; } /** - * Sets the case display name. This does not change the name of the case - * directory, the case database, or the text index name. + * Sets the case display name. * - * @param caseName A case display name. + * @param caseDisplayName A case display name. + * + * @throws CaseMetadataException If the operation fails. */ - void setCaseDisplayName(String caseName) throws CaseMetadataException { - String oldCaseName = caseName; - this.caseDisplayName = caseName; + void setCaseDisplayName(String caseDisplayName) throws CaseMetadataException { + String oldCaseDisplayName = this.caseDisplayName; + this.caseDisplayName = caseDisplayName; try { writeToFile(); } catch (CaseMetadataException ex) { - this.caseDisplayName = oldCaseName; + this.caseDisplayName = oldCaseDisplayName; throw ex; } } @@ -234,32 +239,35 @@ public final class CaseMetadata { /** * Gets the name of the case database. * - * @return The case database name. + * @return The case database name, may be empty. */ public String getCaseDatabaseName() { return caseDatabaseName; } /** - * Sets the text index name. + * Sets the name of the case database. * - * @param caseTextIndexName The text index name. + * @param caseDatabaseName The case database name. + * + * @throws CaseMetadataException If the operation fails. */ - void setTextIndexName(String caseTextIndexName) throws CaseMetadataException { - String oldIndexName = caseTextIndexName; - this.textIndexName = caseTextIndexName; + void setCaseDatabaseName(String caseDatabaseName) throws CaseMetadataException { + String oldCaseDatabaseName = this.caseDatabaseName; + this.caseDatabaseName = caseDatabaseName; try { writeToFile(); } catch (CaseMetadataException ex) { - this.textIndexName = oldIndexName; + this.caseDatabaseName = oldCaseDatabaseName; throw ex; } } /** - * Gets the text index name. + * Gets the text index name. This is a legacy field and will be empty for + * cases created with Autopsy 4.4.0 and above. * - * @return The name of the text index for the case. + * @return The name of the text index for the case, may be empty. */ public String getTextIndexName() { return textIndexName; @@ -268,7 +276,7 @@ public final class CaseMetadata { /** * Gets the date the case was created. * - * @return The date this case was created as a string + * @return The date this case was created, as a string. */ String getCreatedDate() { return createdDate; @@ -278,7 +286,9 @@ public final class CaseMetadata { * Sets the date the case was created. Used for preserving the case creation * date during single-user to multi-user case conversion. * - * @param createdDate The date the case was created as a string. + * @param createdDate The date the case was created, as a string. + * + * @throws CaseMetadataException If the operation fails. */ void setCreatedDate(String createdDate) throws CaseMetadataException { String oldCreatedDate = createdDate; @@ -304,13 +314,15 @@ public final class CaseMetadata { * Sets the Autopsy version that created the case. Used for preserving this * metadata during single-user to multi-user case conversion. * - * @param buildVersion An build version identifier. + * @param buildVersion A build version identifier. + * + * @throws CaseMetadataException If the operation fails. */ void setCreatedByVersion(String buildVersion) throws CaseMetadataException { String oldCreatedByVersion = this.createdByVersion; this.createdByVersion = buildVersion; try { - this.writeToFile(); + writeToFile(); } catch (CaseMetadataException ex) { this.createdByVersion = oldCreatedByVersion; throw ex; @@ -381,8 +393,8 @@ public final class CaseMetadata { createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseNumber); createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, examiner); createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString()); - createChildElement(doc, caseElement, CASE_DATABASE_ABSOLUTE_PATH_ELEMENT_NAME, caseDatabasePath); - createChildElement(doc, caseElement, CASE_DATABASE_NAME_RELATIVE_ELEMENT_NAME, caseDatabaseName); + createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, caseDatabasePath); + createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, caseDatabaseName); createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName); } @@ -451,15 +463,19 @@ public final class CaseMetadata { if (null == this.caseType) { throw new CaseMetadataException("Case metadata file corrupted"); } - if (schemaVersion.equals(SCHEMA_VERSION_ONE)) { - this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true); - this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true); - } else if (schemaVersion.equals(SCHEMA_VERSION_TWO)) { - this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_ABSOLUTE_PATH_ELEMENT_NAME, true); - this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false); - } else { - this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_RELATIVE_ELEMENT_NAME, true); - this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false); + switch (schemaVersion) { + case SCHEMA_VERSION_ONE: + this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true); + this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true); + break; + case SCHEMA_VERSION_TWO: + this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, true); + this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false); + break; + default: + this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, true); + this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false); + break; } /* @@ -489,7 +505,7 @@ public final class CaseMetadata { * @param elementName The element name. * @param contentIsRequired Whether or not the content is required. * - * @return The text content, may be empty if not required. + * @return The text content, may be empty If not required. * * @throws CaseMetadataException If the element is missing or content is * required and it is empty. @@ -530,7 +546,7 @@ public final class CaseMetadata { * @return The full path to the case database file for a single-user case. * * @throws UnsupportedOperationException If called for a multi-user case. - * @deprecated + * @deprecated Do not use. */ @Deprecated public String getCaseDatabasePath() throws UnsupportedOperationException { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 26a7fb9c26..983cbd9149 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -125,13 +125,15 @@ public final class CaseOpenAction extends CallableSystemAction implements Action JOptionPane.ERROR_MESSAGE); StartupWindowProvider.getInstance().open(); } catch (ExecutionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getCause().getMessage(), //get the message of the wrapped exception - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getCause().getMessage(), //get the message of the wrapped exception + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + } StartupWindowProvider.getInstance().open(); } WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java index 3f80544101..1c0e021999 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java @@ -22,7 +22,6 @@ import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import javax.swing.Action; import javax.swing.JDialog; import javax.swing.SwingUtilities; @@ -43,11 +42,11 @@ final class CasePropertiesAction extends CallableSystemAction { CasePropertiesAction() { putValue(Action.NAME, NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction")); this.setEnabled(false); - Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - setEnabled(null != evt.getNewValue()); + Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), (PropertyChangeEvent evt) -> { + if (null == evt.getNewValue()) { + casePropertiesDialog = null; } + setEnabled(null != evt.getNewValue()); }); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java index 753033fe51..b6b11349ab 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java @@ -16,63 +16,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.casemodule; import java.nio.file.Paths; -import java.util.Map; import java.util.logging.Level; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** - * A panel that allows the user to view various properties of the current case - * and change the display name of the case. + * A panel that allows the user to view various properties of a case and change + * the display name of the case. */ class CasePropertiesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private Case current = null; - private static JPanel caller; // panel for error + private static final Logger LOGGER = Logger.getLogger(CasePropertiesPanel.class.getName()); + private final Case theCase; - CasePropertiesPanel(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException { + /** + * Constructs a panel that allows the user to view various properties of the + * current case and change the display name of the case. + * + * @param aCase A case. + */ + CasePropertiesPanel(Case aCase) { initComponents(); - caseNameTextField.setText(currentCase.getDisplayName()); - String caseNumber = currentCase.getNumber(); + theCase = aCase; + caseNameTextField.setText(theCase.getDisplayName()); + String caseNumber = theCase.getNumber(); if (!caseNumber.isEmpty()) { caseNumberField.setText(caseNumber); } else { caseNumberField.setText("N/A"); } - String examiner = currentCase.getExaminer(); + String examiner = theCase.getExaminer(); if (!examiner.isEmpty()) { examinerField.setText(examiner); } else { examinerField.setText("N/A"); } - crDateField.setText(crDate); - caseDirField.setText(caseDir); - current = currentCase; - - CaseMetadata caseMetadata = currentCase.getCaseMetadata(); - if (caseMetadata.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { - dbNameField.setText(Paths.get(caseMetadata.getCaseDirectory(), caseMetadata.getCaseDatabaseName()).toString()); + crDateField.setText(theCase.getCreatedDate()); + caseDirField.setText(theCase.getCaseDirectory()); + if (Case.CaseType.SINGLE_USER_CASE == theCase.getCaseType()) { + dbNameField.setText(Paths.get(theCase.getCaseDirectory(), theCase.getMetadata().getCaseDatabaseName()).toString()); } else { - dbNameField.setText(caseMetadata.getCaseDatabaseName()); - } - Case.CaseType caseType = caseMetadata.getCaseType(); - caseTypeField.setText(caseType.getLocalizedDisplayName()); - if (caseType == Case.CaseType.SINGLE_USER_CASE) { - deleteCaseButton.setEnabled(true); - } else { - deleteCaseButton.setEnabled(false); + dbNameField.setText(theCase.getMetadata().getCaseDatabaseName()); } + Case.CaseType caseType = theCase.getCaseType(); + caseTypeField.setText(caseType.getLocalizedDisplayName()); + deleteCaseButton.setEnabled(Case.CaseType.SINGLE_USER_CASE == caseType); } /** @@ -259,59 +253,35 @@ class CasePropertiesPanel extends javax.swing.JPanel { }// //GEN-END:initComponents /** - * Updates the case name. + * Updates the case display name. * * @param evt The action event */ + @NbBundle.Messages({ + "CasePropertiesPanel.errorDialog.emptyCaseNameMessage=No case name entered.", + "CasePropertiesPanel.errorDialog.invalidCaseNameMessage=Case names cannot include the following symbols: \\, /, :, *, ?, \", <, >, |" + }) private void updateCaseNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateCaseNameButtonActionPerformed - String oldCaseName = Case.getCurrentCase().getDisplayName(); - String newCaseName = caseNameTextField.getText(); - // check if the old and new case name is not equal - if (!oldCaseName.equals(newCaseName)) { + String newCaseDisplayName = caseNameTextField.getText(); + if (newCaseDisplayName.equals(theCase.getDisplayName())) { + return; + } - // check if the case name is empty - if (newCaseName.trim().isEmpty()) { - JOptionPane.showMessageDialog(caller, - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.msgDlg.empty.msg"), - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.msgDlg.empty.title"), - JOptionPane.ERROR_MESSAGE); - } else // check if case Name contain one of this following symbol: - // \ / : * ? " < > | - { - if (newCaseName.contains("\\") || newCaseName.contains("/") || newCaseName.contains(":") - || newCaseName.contains("*") || newCaseName.contains("?") || newCaseName.contains("\"") - || newCaseName.contains("<") || newCaseName.contains(">") || newCaseName.contains("|")) { - String errorMsg = NbBundle - .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg"); - JOptionPane.showMessageDialog(caller, errorMsg, - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title"), - JOptionPane.ERROR_MESSAGE); - } else { - // ask for the confirmation first - String confMsg = NbBundle - .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.confMsg.msg", oldCaseName, - newCaseName); - NotifyDescriptor d = new NotifyDescriptor.Confirmation(confMsg, - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.confMsg.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - d.setValue(NotifyDescriptor.NO_OPTION); + if (newCaseDisplayName.trim().isEmpty()) { + MessageNotifyUtil.Message.error(Bundle.CasePropertiesPanel_errorDialog_emptyCaseNameMessage()); + return; + } - Object res = DialogDisplayer.getDefault().notify(d); - if (res != null && res == DialogDescriptor.YES_OPTION) { - // if user select "Yes" - String oldPath = current.getCaseMetadata().getFilePath().toString(); - try { - current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath); - } catch (CaseActionException ex) { - Logger.getLogger(CasePropertiesPanel.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS - } - } - } - } + if (!Case.isValidName(newCaseDisplayName)) { + MessageNotifyUtil.Message.error(Bundle.CasePropertiesPanel_errorDialog_invalidCaseNameMessage()); + return; + } + + try { + theCase.updateDisplayName(newCaseDisplayName); + } catch (CaseActionException ex) { + MessageNotifyUtil.Message.error(ex.getLocalizedMessage()); + LOGGER.log(Level.SEVERE, "Failed to update case display name", ex); //NON-NLS } }//GEN-LAST:event_updateCaseNameButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 128ea4a8c3..3679587491 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -94,15 +94,17 @@ final class NewCaseWizardAction extends CallableSystemAction { AddImageAction addImageAction = SystemAction.get(AddImageAction.class); addImageAction.actionPerformed(null); } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, String.format("Error creating case %s", wizardDescriptor.getProperty("caseName")), ex); //NON-NLS - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - (ex instanceof ExecutionException ? ex.getCause().getMessage() : ex.getMessage()), - NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS - JOptionPane.ERROR_MESSAGE); + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error creating case %s", wizardDescriptor.getProperty("caseName")), ex); //NON-NLS + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + (ex instanceof ExecutionException ? ex.getCause().getMessage() : ex.getMessage()), + NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + } + doFailedCaseCleanup(wizardDescriptor); StartupWindowProvider.getInstance().close(); StartupWindowProvider.getInstance().open(); - doFailedCaseCleanup(wizardDescriptor); } finally { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java index dcfaab449c..1660381152 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java @@ -38,29 +38,21 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** - * The "New Case" wizard panel with a component on it. This class represents - * data of wizard step. It defers creation and initialization of UI component of - * wizard panel into getComponent() method. + * The first panel of the New Case wizard. */ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel { - /** - * The visual component that displays this panel. If you need to access the - * component from this class, just use getComponent(). - */ - private NewCaseVisualPanel1 component; - private Boolean isFinish = false; - private static String createdDirectory; - private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS private static final Logger logger = Logger.getLogger(NewCaseWizardPanel1.class.getName()); + private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS + private static String createdDirectory; + private final Set listeners = new HashSet<>(1); + private NewCaseVisualPanel1 component; + private boolean isFinish; /** - * Get the visual component for the panel. In this template, the component - * is kept separate. This can be more efficient: if the wizard is created - * but never displayed, or not all panels are displayed, it is better to - * create only those which really need to be visible. + * Get the visual component for the panel. * - * @return component the UI component of this wizard panel + * @return The UI component of this wizard panel */ @Override public NewCaseVisualPanel1 getComponent() { @@ -71,65 +63,57 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 /** - * Adds a listener to changes of the panel's validity. + * Adds a change listener to this panel. * - * @param l the change listener to add + * @param listener The change listener to add. */ @Override - public final void addChangeListener(ChangeListener l) { + public final void addChangeListener(ChangeListener listener) { synchronized (listeners) { - listeners.add(l); + listeners.add(listener); } } /** - * Removes a listener to changes of the panel's validity. + * Removes a change listener from this panel. * - * @param l the change listener to move + * @param listener The change listener to remove. */ @Override - public final void removeChangeListener(ChangeListener l) { + public final void removeChangeListener(ChangeListener listener) { synchronized (listeners) { - listeners.remove(l); + listeners.remove(listener); } } /** - * This method is auto-generated. It seems that this method is used to - * listen to any change in this wizard panel. + * Notifies any registerd change listeners of a change in the panel. */ protected final void fireChangeEvent() { Iterator it; @@ -153,12 +137,8 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,35 +28,19 @@ import org.openide.WizardValidationException; import org.openide.util.HelpCtx; import org.openide.windows.WindowManager; import java.awt.Cursor; -import org.sleuthkit.autopsy.casemodule.Case.CaseType; /** - * The "New Case" wizard panel with a component on it. This class represents - * data of wizard step. It defers creation and initialization of UI component of - * wizard panel into getComponent() method. - * - * @author jantonius + * The second panel of the New Case wizard. */ class NewCaseWizardPanel2 implements WizardDescriptor.ValidatingPanel { - /** - * The visual component that displays this panel. If you need to access the - * component from this class, just use getComponent(). - */ private NewCaseVisualPanel2 component; - private Boolean isFinish = true; - private String caseName; - private String caseDir; - private String createdDirectory; - private CaseType caseType; + private final Set listeners = new HashSet<>(1); /** - * Get the visual component for the panel. In this template, the component - * is kept separate. This can be more efficient: if the wizard is created - * but never displayed, or not all panels are displayed, it is better to - * create only those which really need to be visible. + * Get the visual component for the panel. * - * @return component the UI component of this wizard panel + * @return component The UI component of this wizard panel. */ @Override public NewCaseVisualPanel2 getComponent() { @@ -67,17 +51,17 @@ class NewCaseWizardPanel2 implements WizardDescriptor.ValidatingPanel listeners = new HashSet(1); // or can use ChangeSupport in NB 6.0 /** - * Adds a listener to changes of the panel's validity. + * Adds a change listener to this panel. * - * @param l the change listener to add + * @param listener The change listener to add. */ @Override - public final void addChangeListener(ChangeListener l) { + public final void addChangeListener(ChangeListener listener) { synchronized (listeners) { - listeners.add(l); + listeners.add(listener); } } /** - * Removes a listener to changes of the panel's validity. + * Removes a change listener from this panel. * - * @param l the change listener to move + * @param listener The change listener to remove. */ @Override - public final void removeChangeListener(ChangeListener l) { + public final void removeChangeListener(ChangeListener listener) { synchronized (listeners) { - listeners.remove(l); + listeners.remove(listener); } } /** - * This method is auto-generated. It seems that this method is used to - * listen to any change in this wizard panel. + * Notifies any registerd change listeners of a change in the panel. */ protected final void fireChangeEvent() { Iterator it; @@ -138,10 +114,6 @@ class NewCaseWizardPanel2 implements WizardDescriptor.ValidatingPanel { - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), // Should be user-friendly - NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), // Should be user-friendly + NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + } StartupWindowProvider.getInstance().open(); }); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java index 9ea9214c36..49e480450f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java @@ -407,9 +407,11 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { String[] casePaths = new String[LENGTH]; String currentCasePath = null; try { - currentCasePath = Case.getCurrentCase().getCaseMetadata().getFilePath().toString(); + currentCasePath = Case.getCurrentCase().getMetadata().getFilePath().toString(); } catch (IllegalStateException ex) { - // in case there is no current case. + /* + * There may be no current case. + */ } Iterator mostRecentFirst = recentCases.descendingIterator(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java index d46fce31cb..b60cb3e800 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java @@ -65,17 +65,20 @@ class RecentItems implements ActionListener { try { Case.openAsCurrentCase(caseMetaDataFilePath); } catch (CaseActionException ex) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog( - WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS - JOptionPane.ERROR_MESSAGE); + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + ex.getMessage(), + NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS + JOptionPane.ERROR_MESSAGE); + } StartupWindowProvider.getInstance().open(); }); } - }).start(); + }). + start(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index 50494f47a0..446c03b6c8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -204,8 +204,8 @@ public class SingleUserCaseConverter { icd.getNewCaseName(), icd.getNewCaseName(), oldCaseMetadata.getCaseNumber(), - oldCaseMetadata.getExaminer(), - dbName); + oldCaseMetadata.getExaminer()); + newCaseMetadata.setCaseDatabaseName(dbName); // Set created date. This calls writefile, no need to call it again newCaseMetadata.setCreatedDate(oldCaseMetadata.getCreatedDate()); newCaseMetadata.setCreatedByVersion(oldCaseMetadata.getCreatedByVersion()); diff --git a/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java index 3fcad59638..83e7db95e0 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java @@ -59,7 +59,7 @@ public final class LoggingProgressIndicator implements ProgressIndicator { @Override public void progress(int workUnitsCompleted) { - LOGGER.log(Level.INFO, "{1} of {2} total work units completed", new Object[]{workUnitsCompleted, this.totalWorkUnits}); + LOGGER.log(Level.INFO, "{0} of {1} total work units completed", new Object[]{workUnitsCompleted, this.totalWorkUnits}); } @Override @@ -68,8 +68,8 @@ public final class LoggingProgressIndicator implements ProgressIndicator { } @Override - public void finish(String message) { - LOGGER.log(Level.INFO, "{0} finished", message); + public void finish() { + LOGGER.log(Level.INFO, "Finished"); } } diff --git a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java index c5df4c8cfa..2527b72603 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java @@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.framework; import java.awt.Dialog; import java.awt.Frame; import java.awt.event.ActionListener; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; import javax.swing.JDialog; import javax.swing.SwingUtilities; import org.openide.DialogDescriptor; @@ -30,14 +32,17 @@ import org.openide.util.HelpCtx; /** * A progress indicator that displays progress using a modal dialog with a * message label, a progress bar, and optionally, a configurable set of buttons - * with a button listener. + * with a button listener. Setting a cancelling flag which locks in a cancelling + * message and an indeterminate progress bar is supported. */ +@ThreadSafe public final class ModalDialogProgressIndicator implements ProgressIndicator { private final Frame parent; private final ProgressPanel progressPanel; private final Dialog dialog; - private final ActionListener buttonListener; + @GuardedBy("this") + private boolean cancelling; /** * Creates a progress indicator that displays progress using a modal dialog @@ -54,6 +59,7 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { public ModalDialogProgressIndicator(Frame parent, String title, Object[] buttonLabels, Object focusedButtonLabel, ActionListener buttonListener) { this.parent = parent; progressPanel = new ProgressPanel(); + progressPanel.setIndeterminate(true); DialogDescriptor dialogDescriptor = new DialogDescriptor( progressPanel, title, @@ -64,7 +70,6 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { HelpCtx.DEFAULT_HELP, buttonListener); dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); - this.buttonListener = buttonListener; } /** @@ -77,31 +82,10 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { public ModalDialogProgressIndicator(Frame parent, String title) { this.parent = parent; progressPanel = new ProgressPanel(); + progressPanel.setIndeterminate(true); dialog = new JDialog(parent, title, true); dialog.add(progressPanel); dialog.pack(); - buttonListener = null; - } - - /** - * Calls setVisible on the underlying modal dialog. - * - * @param isVisible True or false. - */ - public void setVisible(boolean isVisible) { - if (isVisible) { - dialog.setLocationRelativeTo(parent); - } - this.dialog.setVisible(isVisible); - } - - /** - * Gets the button listener for the dialog, if there is one. - * - * @return The button listener or null. - */ - public ActionListener getButtonListener() { - return buttonListener; } /** @@ -112,14 +96,14 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * @param totalWorkUnits The total number of work units. */ @Override - public void start(String message, int totalWorkUnits) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressPanel.setInderminate(false); - progressPanel.setMessage(message); - progressPanel.setMaximum(totalWorkUnits); - } + public synchronized void start(String message, int totalWorkUnits) { + cancelling = false; + SwingUtilities.invokeLater(() -> { + progressPanel.setIndeterminate(false); + progressPanel.setMessage(message); + progressPanel.setMaximum(totalWorkUnits); + dialog.setLocationRelativeTo(parent); + this.dialog.setVisible(true); }); } @@ -130,13 +114,28 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * @param message The initial progress message. */ @Override - public void start(String message) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressPanel.setInderminate(true); - progressPanel.setMessage(message); - } + public synchronized void start(String message) { + cancelling = false; + SwingUtilities.invokeLater(() -> { + progressPanel.setIndeterminate(true); + progressPanel.setMessage(message); + dialog.setLocationRelativeTo(parent); + this.dialog.setVisible(true); + }); + } + + /** + * Sets a cancelling message and makes the progress bar indeterminate. Once + * cancel has been called, the progress indicator no longer accepts updates + * unless start is called again. + * + * @param cancellingMessage + */ + public synchronized void setCancelling(String cancellingMessage) { + cancelling = true; + SwingUtilities.invokeLater(() -> { + progressPanel.setIndeterminate(false); + progressPanel.setMessage(cancellingMessage); }); } @@ -147,14 +146,13 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * @param message The initial progress message. */ @Override - public void switchToIndeterminate(String message) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressPanel.setInderminate(true); + public synchronized void switchToIndeterminate(String message) { + if (!cancelling) { + SwingUtilities.invokeLater(() -> { + progressPanel.setIndeterminate(true); progressPanel.setMessage(message); - } - }); + }); + } } /** @@ -166,16 +164,15 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * @param totalWorkUnits The total number of work units to be completed. */ @Override - public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressPanel.setInderminate(false); + public synchronized void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { + if (!cancelling) { + SwingUtilities.invokeLater(() -> { + progressPanel.setIndeterminate(false); progressPanel.setMessage(message); progressPanel.setMaximum(totalWorkUnits); progressPanel.setCurrent(workUnitsCompleted); - } - }); + }); + } } /** @@ -184,13 +181,12 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * @param message The progress message. */ @Override - public void progress(String message) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { + public synchronized void progress(String message) { + if (!cancelling) { + SwingUtilities.invokeLater(() -> { progressPanel.setMessage(message); - } - }); + }); + } } /** @@ -201,13 +197,12 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * @param workUnitsCompleted Number of work units completed so far. */ @Override - public void progress(int workUnitsCompleted) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { + public synchronized void progress(int workUnitsCompleted) { + if (!cancelling) { + SwingUtilities.invokeLater(() -> { progressPanel.setCurrent(workUnitsCompleted); - } - }); + }); + } } /** @@ -219,28 +214,22 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { * @param workUnitsCompleted Number of work units completed so far. */ @Override - public void progress(String message, int workUnitsCompleted) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { + public synchronized void progress(String message, int workUnitsCompleted) { + if (!cancelling) { + SwingUtilities.invokeLater(() -> { progressPanel.setMessage(message); progressPanel.setCurrent(workUnitsCompleted); - } - }); + }); + } } /** * Finishes the progress indicator when the task is completed. - * - * @param message The finished message. */ @Override - public void finish(String message) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressPanel.setMessage(message); - } + public synchronized void finish() { + SwingUtilities.invokeLater(() -> { + this.dialog.setVisible(false); }); } diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java index 0f51d98d75..6f91dd3231 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java @@ -46,6 +46,7 @@ public interface ProgressIndicator { /** * Switches the progress indicator to indeterminate mode (the total number * of work units to be completed is unknown). + * * @param message The initial progress message. */ public void switchToIndeterminate(String message); @@ -54,7 +55,7 @@ public interface ProgressIndicator { * Switches the progress indicator to determinate mode (the total number of * work units to be completed is known). * - * @param message The initial progress message. + * @param message The initial progress message. * @param workUnitsCompleted The number of work units completed so far. * @param totalWorkUnits The total number of work units to be completed. */ @@ -88,9 +89,7 @@ public interface ProgressIndicator { /** * Finishes the progress indicator when the task is completed. - * - * @param message The finished message. */ - void finish(String message); + void finish(); } diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java index 4957a317b6..4130eb4852 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java @@ -35,7 +35,7 @@ class ProgressPanel extends javax.swing.JPanel { this.progressMessage.setText(message); } - void setInderminate(boolean indeterminate) { + void setIndeterminate(boolean indeterminate) { this.progressBar.setIndeterminate(indeterminate); } diff --git a/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java index 8152a097c9..b7b03b8c44 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java @@ -54,7 +54,7 @@ public class SilentProgressIndicator implements ProgressIndicator { } @Override - public void finish(String message) { + public void finish() { } } diff --git a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java index 594532cbc6..cf6f042292 100644 --- a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java +++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,98 +25,99 @@ import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; -import org.openide.util.lookup.ServiceProviders; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.framework.AutopsyService; -@ServiceProviders(value = {@ServiceProvider(service = AutopsyService.class)}) - /** - * Creates and handles closing of ImageWriter objects. - * Currently, ImageWriter is only enabled for local disks, and local disks can - * not be processed in multi user mode. If ImageWriter is ever enabled for multi user - * cases this code will need to be revised. + * Creates and handles closing of ImageWriter objects. Currently, ImageWriter is + * only enabled for local disks, and local disks can not be processed in multi + * user mode. If ImageWriter is ever enabled for multi user cases this code will + * need to be revised. */ - +@ServiceProvider(service = AutopsyService.class) public class ImageWriterService implements AutopsyService { private static final List imageWriters = new ArrayList<>(); // Contains all Image Writer objects private static final Object imageWritersLock = new Object(); // Get this lock before accessing currentImageWriters - + /** * Create an image writer object for the given data source ID. + * * @param imageId ID for the image */ - public static void createImageWriter(Long imageId, ImageWriterSettings settings){ - + public static void createImageWriter(Long imageId, ImageWriterSettings settings) { + // ImageWriter objects are created during the addImageTask. They can not arrive while // we're closing case resources so we don't need to worry about one showing up while // doing our close/cleanup. - synchronized(imageWritersLock){ + synchronized (imageWritersLock) { ImageWriter writer = new ImageWriter(imageId, settings); writer.subscribeToEvents(); imageWriters.add(writer); } } - + @Override public String getServiceName() { return NbBundle.getMessage(this.getClass(), "ImageWriterService.serviceName"); } - + @Override public void closeCaseResources(CaseContext context) throws AutopsyServiceException { - context.getProgressIndicator().progress(NbBundle.getMessage(this.getClass(), "ImageWriterService.waitingForVHDs")); - - synchronized(imageWritersLock){ + synchronized (imageWritersLock) { + if (imageWriters.isEmpty()) { + return; + } + + context.getProgressIndicator().progress(NbBundle.getMessage(this.getClass(), "ImageWriterService.waitingForVHDs")); + // If any of our ImageWriter objects haven't started the finish task, set the cancel flag // to make sure they don't start now. The reason they haven't started is that // ingest was not complete, and the user already confirmed that they want to exit // even though ingest is not complete so we will take that to mean that they // also don't want to wait for Image Writer. - for(ImageWriter writer: imageWriters){ + for (ImageWriter writer : imageWriters) { writer.cancelIfNotStarted(); } - + // Test whether any finishImage tasks are in progress boolean jobsAreInProgress = false; - for(ImageWriter writer: imageWriters){ - if(writer.jobIsInProgress()){ + for (ImageWriter writer : imageWriters) { + if (writer.jobIsInProgress()) { jobsAreInProgress = true; break; } } - - if(jobsAreInProgress){ + + if (jobsAreInProgress) { // If jobs are in progress, ask the user if they want to wait for them to complete NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - NbBundle.getMessage(this.getClass(), "ImageWriterService.shouldWait"), - NbBundle.getMessage(this.getClass(), "ImageWriterService.localDisk"), - NotifyDescriptor.YES_NO_OPTION, - NotifyDescriptor.WARNING_MESSAGE); + NbBundle.getMessage(this.getClass(), "ImageWriterService.shouldWait"), + NbBundle.getMessage(this.getClass(), "ImageWriterService.localDisk"), + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); Object response = DialogDisplayer.getDefault().notify(descriptor); - - if(response == DialogDescriptor.NO_OPTION){ + + if (response == DialogDescriptor.NO_OPTION) { // Cancel all the jobs - for(ImageWriter writer: imageWriters){ + for (ImageWriter writer : imageWriters) { writer.cancelJob(); } } - + // Wait for all finishImage jobs to complete. If the jobs got cancelled // this will be very fast. - for(ImageWriter writer: imageWriters){ + for (ImageWriter writer : imageWriters) { writer.waitForJobToFinish(); } - + } - + // Stop listening for events - for(ImageWriter writer: imageWriters){ + for (ImageWriter writer : imageWriters) { writer.unsubscribeFromEvents(); } - + // Clear out the list of Image Writers imageWriters.clear(); } diff --git a/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java index b550ca647d..f3e30a6b63 100644 --- a/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java +++ b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java @@ -53,7 +53,7 @@ public class TestAutopsyService implements AutopsyService { progressIndicator.progress(80); Thread.sleep(1000L); progressIndicator.progress(100); - progressIndicator.finish("First task completed by Test Autopsy Service."); + progressIndicator.finish(); progressIndicator.start("Test Autopsy Service doing second task..."); for (int i = 0; i < 10000; ++i) { logger.log(Level.INFO, "Test Autopsy Service simulating work on second task"); @@ -62,7 +62,7 @@ public class TestAutopsyService implements AutopsyService { break; } } - progressIndicator.finish("Second task completed by Test Autopsy Service."); + progressIndicator.finish(); } catch (InterruptedException ex) { logger.log(Level.INFO, "Test Autopsy Service interrupted (cancelled) while doing first task, cancel requested = {0}", context.cancelRequested()); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index acde9d77fc..4018ce7698 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -200,6 +200,10 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { "SolrSearch.openCore.msg=Opening text index", "SolrSearch.complete.msg=Text index successfully opened"}) public void openCaseResources(CaseContext context) throws AutopsyServiceException { + if (context.cancelRequested()) { + return; + } + ProgressIndicator progress = context.getProgressIndicator(); int totalNumProgressUnits = 7; int progressUnitsCompleted = 0; From 7d62c6c910f2ca815b042889c4c3c09c46e5b06c Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 2 May 2017 18:01:07 -0400 Subject: [PATCH 21/57] Add missing CaseActionCancelledAction --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 5bd1618859..f4b3d6d1d9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2008,7 +2008,7 @@ public class Case { } if (Thread.currentThread().isInterrupted()) { - return; + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); } } } From 54f3ef87abe12aaa4e91096fe8d6ada601673412 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 3 May 2017 08:56:43 -0400 Subject: [PATCH 22/57] Improve ModalDialogProgressIndicator, both create and display on EDT --- .../ModalDialogProgressIndicator.java | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java index 2527b72603..bd59bfdbfd 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java @@ -39,8 +39,12 @@ import org.openide.util.HelpCtx; public final class ModalDialogProgressIndicator implements ProgressIndicator { private final Frame parent; + private final String title; private final ProgressPanel progressPanel; - private final Dialog dialog; + private final Object[] buttonLabels; + private final Object focusedButtonLabel; + private final ActionListener buttonListener; + private Dialog dialog; @GuardedBy("this") private boolean cancelling; @@ -58,18 +62,22 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { */ public ModalDialogProgressIndicator(Frame parent, String title, Object[] buttonLabels, Object focusedButtonLabel, ActionListener buttonListener) { this.parent = parent; + this.title = title; progressPanel = new ProgressPanel(); progressPanel.setIndeterminate(true); - DialogDescriptor dialogDescriptor = new DialogDescriptor( - progressPanel, - title, - true, - buttonLabels, - focusedButtonLabel, - DialogDescriptor.BOTTOM_ALIGN, - HelpCtx.DEFAULT_HELP, - buttonListener); - dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); + this.buttonLabels = buttonLabels; + this.focusedButtonLabel = focusedButtonLabel; + this.buttonListener = buttonListener; +// DialogDescriptor dialogDescriptor = new DialogDescriptor( +// progressPanel, +// title, +// true, +// buttonLabels, +// focusedButtonLabel, +// DialogDescriptor.BOTTOM_ALIGN, +// HelpCtx.DEFAULT_HELP, +// buttonListener); +// dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); } /** @@ -81,11 +89,15 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { */ public ModalDialogProgressIndicator(Frame parent, String title) { this.parent = parent; + this.title = title; progressPanel = new ProgressPanel(); progressPanel.setIndeterminate(true); - dialog = new JDialog(parent, title, true); - dialog.add(progressPanel); - dialog.pack(); + this.buttonLabels = null; + this.focusedButtonLabel = null; + this.buttonListener = null; +// dialog = new JDialog(parent, title, true); +// dialog.add(progressPanel); +// dialog.pack(); } /** @@ -102,8 +114,9 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { progressPanel.setIndeterminate(false); progressPanel.setMessage(message); progressPanel.setMaximum(totalWorkUnits); - dialog.setLocationRelativeTo(parent); - this.dialog.setVisible(true); + displayDialog(); +// dialog.setLocationRelativeTo(parent); +// this.dialog.setVisible(true); }); } @@ -119,8 +132,9 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { SwingUtilities.invokeLater(() -> { progressPanel.setIndeterminate(true); progressPanel.setMessage(message); - dialog.setLocationRelativeTo(parent); - this.dialog.setVisible(true); + displayDialog(); +// dialog.setLocationRelativeTo(parent); +// this.dialog.setVisible(true); }); } @@ -233,4 +247,21 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { }); } + /** + * Creates and dislpays the dialog for the progress indicator. + */ + private void displayDialog() { + DialogDescriptor dialogDescriptor = new DialogDescriptor( + progressPanel, + title, + true, + buttonLabels, + focusedButtonLabel, + DialogDescriptor.BOTTOM_ALIGN, + HelpCtx.DEFAULT_HELP, + buttonListener); + dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); + dialog.setLocationRelativeTo(parent); + this.dialog.setVisible(true); + } } From 8af9f34eb6566936511b045f1010895a9296dd34 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 3 May 2017 10:00:19 -0400 Subject: [PATCH 23/57] Fix CasePropertiesAction bug fix --- .../casemodule/CasePropertiesAction.java | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java index 1c0e021999..451a377a2f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java @@ -43,9 +43,6 @@ final class CasePropertiesAction extends CallableSystemAction { putValue(Action.NAME, NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction")); this.setEnabled(false); Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), (PropertyChangeEvent evt) -> { - if (null == evt.getNewValue()) { - casePropertiesDialog = null; - } setEnabled(null != evt.getNewValue()); }); } @@ -53,23 +50,21 @@ final class CasePropertiesAction extends CallableSystemAction { @Override public void performAction() { SwingUtilities.invokeLater(() -> { - if (null == casePropertiesDialog) { - String title = NbBundle.getMessage(this.getClass(), "CasePropertiesAction.window.title"); - casePropertiesDialog = new JDialog(WindowManager.getDefault().getMainWindow(), title, false); - CaseInformationPanel caseInformationPanel = new CaseInformationPanel(); - caseInformationPanel.addCloseButtonAction((ActionEvent e) -> { - casePropertiesDialog.setVisible(false); - }); - casePropertiesDialog.add(caseInformationPanel); - casePropertiesDialog.setResizable(true); - casePropertiesDialog.pack(); + String title = NbBundle.getMessage(this.getClass(), "CasePropertiesAction.window.title"); + casePropertiesDialog = new JDialog(WindowManager.getDefault().getMainWindow(), title, false); + CaseInformationPanel caseInformationPanel = new CaseInformationPanel(); + caseInformationPanel.addCloseButtonAction((ActionEvent e) -> { + casePropertiesDialog.setVisible(false); + }); + casePropertiesDialog.add(caseInformationPanel); + casePropertiesDialog.setResizable(true); + casePropertiesDialog.pack(); - Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); - double w = casePropertiesDialog.getSize().getWidth(); - double h = casePropertiesDialog.getSize().getHeight(); - casePropertiesDialog.setLocation((int) ((screenDimension.getWidth() - w) / 2), (int) ((screenDimension.getHeight() - h) / 2)); - casePropertiesDialog.setVisible(true); - } + Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); + double w = casePropertiesDialog.getSize().getWidth(); + double h = casePropertiesDialog.getSize().getHeight(); + casePropertiesDialog.setLocation((int) ((screenDimension.getWidth() - w) / 2), (int) ((screenDimension.getHeight() - h) / 2)); + casePropertiesDialog.setVisible(true); casePropertiesDialog.setVisible(true); casePropertiesDialog.toFront(); }); @@ -86,6 +81,9 @@ final class CasePropertiesAction extends CallableSystemAction { } static void closeCasePropertiesWindow() { - casePropertiesDialog.setVisible(false); + if (null != casePropertiesDialog) { + casePropertiesDialog.setVisible(false); + casePropertiesDialog = null; + } } } From 87046bd279cdde77b4e77e2ef9e576d8619f0cae Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Wed, 3 May 2017 12:04:37 -0400 Subject: [PATCH 24/57] Removed unused Solr 6 references. --- KeywordSearch/ivy.xml | 17 ----------------- KeywordSearch/nbproject/project.properties | 3 --- 2 files changed, 20 deletions(-) diff --git a/KeywordSearch/ivy.xml b/KeywordSearch/ivy.xml index 6135777192..3ceb4e6378 100644 --- a/KeywordSearch/ivy.xml +++ b/KeywordSearch/ivy.xml @@ -5,10 +5,6 @@ - - - - @@ -22,19 +18,6 @@ - - - - - - - - - - - - - diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties index 3a5a642995..5aa1b716e0 100644 --- a/KeywordSearch/nbproject/project.properties +++ b/KeywordSearch/nbproject/project.properties @@ -124,9 +124,6 @@ file.reference.slf4j-api-1.7.12.jar=release/modules/ext/slf4j-api-1.7.12.jar file.reference.solr-solrj-4.9.1-javadoc.jar=release/modules/ext/solr-solrj-4.9.1-javadoc.jar file.reference.solr-solrj-4.9.1-sources.jar=release/modules/ext/solr-solrj-4.9.1-sources.jar file.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1.jar -file.reference.solr-solrj-6.2.1-javadoc.jar=release/modules/ext/solr-solrj-6.2.1-javadoc.jar -file.reference.solr-solrj-6.2.1-sources.jar=release/modules/ext/solr-solrj-6.2.1-sources.jar -file.reference.solr-solrj-6.2.1.jar=release/modules/ext/solr-solrj-6.2.1.jar file.reference.spring-aop-3.1.2.RELEASE.jar=release/modules/ext/spring-aop-3.1.2.RELEASE.jar file.reference.spring-asm-3.1.2.RELEASE.jar=release/modules/ext/spring-asm-3.1.2.RELEASE.jar file.reference.spring-beans-3.1.2.RELEASE.jar=release/modules/ext/spring-beans-3.1.2.RELEASE.jar From 781a74ffacff46754384fcd3c7136ac97138b885 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 3 May 2017 12:44:35 -0400 Subject: [PATCH 25/57] Fixes for Solr core deletion and Case delete action progress --- .../sleuthkit/autopsy/casemodule/Case.java | 10 +- .../autopsy/keywordsearch/Bundle.properties | 2 +- .../keywordsearch/Bundle_ja.properties | 2 +- .../autopsy/keywordsearch/Server.java | 123 ++++++++++-------- .../keywordsearch/SolrSearchService.java | 4 +- 5 files changed, 79 insertions(+), 62 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index f4b3d6d1d9..7b1fc2c9dd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -594,7 +594,7 @@ public class Case { "Case.exceptionMessage.cannotDeleteCaseOpenForOtherUser=Cannot delete the case, it is open for another user.",}) public static void deleteCase(CaseMetadata metadata) throws CaseActionException { synchronized (caseActionSerializationLock) { - if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(metadata.getCaseDirectory())) { + if (null != currentCase) { throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase())); } } @@ -610,7 +610,6 @@ public class Case { progressIndicator = new LoggingProgressIndicator(); } progressIndicator.start(Bundle.Case_progressMessage_preparing()); - try { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { deleteCase(metadata, progressIndicator); @@ -624,6 +623,7 @@ public class Case { assert (null != dirLock); deleteCase(metadata, progressIndicator); } catch (CoordinationServiceException ex) { + // RJCTODO: need to throw here, could not delete case at all } } } finally { @@ -860,7 +860,7 @@ public class Case { * Delete the case database from the database server. */ try { - progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDatabase()); + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase()); CaseDbConnectionInfo db; db = UserPreferences.getDatabaseConnectionInfo(); Class.forName("org.postgresql.Driver"); //NON-NLS @@ -878,7 +878,7 @@ public class Case { /* * Delete the text index. */ - progressIndicator.start(Bundle.Case_progressMessage_deletingTextIndex()); + progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex()); for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) { try { searchService.deleteTextIndex(metadata); @@ -891,7 +891,7 @@ public class Case { /* * Delete the case directory. */ - progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDirectory()); + progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory()); if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) { logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); errorsOccurred = true; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 795d6ef009..99d88938ce 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -213,7 +213,7 @@ Server.queryNumFileChunks.exception.msg=Error getting number of file chunks, Server.query.exception.msg=Error running query\: {0} Server.query2.exception.msg=Error running query\: {0} Server.queryTerms.exception.msg=Error running terms query\: {0} -Server.connect.exception.msg=Failed to connect to Solr server\: +Server.connect.exception.msg=Failed to connect to Solr server\: {0} Server.openCore.exception.msg=Keyword search service not yet running Server.openCore.exception.cantOpen.msg=Could not create or open index Server.openCore.exception.noIndexDir.msg=Index directory could not be created or is missing diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties index 78412ea29e..04b64d7414 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties @@ -270,7 +270,7 @@ HighlightedMatchesSource.getMarkup.queryFailedMsg=

Date: Wed, 3 May 2017 12:45:02 -0400
Subject: [PATCH 26/57] Revert "Using newer POI JARs."

This reverts commit 368d74429984321210d99bcc56650cc5c606f38a.
---
 CoreLibs/ivy.xml               |  4 ++--
 CoreLibs/nbproject/project.xml | 20 ++++++++++----------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml
index 9d85fdafda..c0ca478735 100644
--- a/CoreLibs/ivy.xml
+++ b/CoreLibs/ivy.xml
@@ -28,8 +28,8 @@
         
               
         
-        
-        
+        
+        
         
         
         
diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml
index d902e24cde..358637d979 100644
--- a/CoreLibs/nbproject/project.xml
+++ b/CoreLibs/nbproject/project.xml
@@ -708,8 +708,8 @@
                 release/modules/ext/jna-3.4.0.jar
             
             
-                ext/poi-ooxml-schemas-3.10.1.jar
-                release/modules/ext/poi-ooxml-schemas-3.10.1.jar
+                ext/poi-ooxml-schemas-3.8.jar
+                release/modules/ext/poi-ooxml-schemas-3.8.jar
             
             
                 ext/gson-1.4.jar
@@ -768,8 +768,8 @@
                 release/modules/ext/jcalendarbutton-1.4.6.jar
             
             
-                ext/poi-ooxml-3.10.1.jar
-                release/modules/ext/poi-ooxml-3.10.1.jar
+                ext/poi-ooxml-3.8.jar
+                release/modules/ext/poi-ooxml-3.8.jar
             
             
                 ext/imageio-psd-3.2.jar
@@ -784,8 +784,8 @@
                 release/modules/ext/servlet-api-2.5.jar
             
             
-                ext/poi-excelant-3.10.1.jar
-                release/modules/ext/poi-excelant-3.10.1.jar
+                ext/poi-excelant-3.8.jar
+                release/modules/ext/poi-excelant-3.8.jar
             
             
                 ext/imageio-pcx-3.2.jar
@@ -824,8 +824,8 @@
                 release/modules/ext/geronimo-jms_1.1_spec-1.0.jar
             
             
-                ext/poi-scratchpad-3.10.1.jar
-                release/modules/ext/poi-scratchpad-3.10.1.jar
+                ext/poi-scratchpad-3.8.jar
+                release/modules/ext/poi-scratchpad-3.8.jar
             
             
                 ext/joda-time-2.4-sources.jar
@@ -896,8 +896,8 @@
                 release/modules/ext/commons-io-2.4.jar
             
             
-                ext/poi-3.10.1.jar
-                release/modules/ext/poi-3.10.1.jar
+                ext/poi-3.8.jar
+                release/modules/ext/poi-3.8.jar
             
             
                 ext/controlsfx-8.40.11.jar

From fdc75e00c63ebce32fa28affe5648601fbc7bdba Mon Sep 17 00:00:00 2001
From: "U-BASIS\\dgrove" 
Date: Wed, 3 May 2017 13:15:01 -0400
Subject: [PATCH 27/57] Fixed 'static' reference.

---
 .../org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java
index 79f4cfb844..e5db5b6a10 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java
@@ -26,7 +26,7 @@ import java.util.List;
 import java.util.logging.Level;
 import net.htmlparser.jericho.Attributes;
 import net.htmlparser.jericho.Config;
-import static net.htmlparser.jericho.Config.LoggerProvider;
+import net.htmlparser.jericho.LoggerProvider;
 import net.htmlparser.jericho.Renderer;
 import net.htmlparser.jericho.Source;
 import net.htmlparser.jericho.StartTag;

From 9489c2a5be4dfebbf2b9dc62c8a0f3322b1c7734 Mon Sep 17 00:00:00 2001
From: Richard Cordovano 
Date: Wed, 3 May 2017 15:37:41 -0400
Subject: [PATCH 28/57] Fixes for case deletion

---
 .../sleuthkit/autopsy/casemodule/Case.java    |  8 +--
 .../autopsy/casemodule/CaseDeleteAction.java  |  5 +-
 .../ModalDialogProgressIndicator.java         | 49 ++++++-------
 .../autopsy/keywordsearch/Server.java         | 71 ++++++-------------
 .../keywordsearch/SolrSearchService.java      | 36 +++++++---
 5 files changed, 77 insertions(+), 92 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 7b1fc2c9dd..b76e4aca9e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -587,15 +587,15 @@ public class Case {
      *                             exception.
      */
     @Messages({
-        "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}",
         "Case.progressIndicatorTitle.deletingCase=Deleting Case",
         "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
         "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
-        "Case.exceptionMessage.cannotDeleteCaseOpenForOtherUser=Cannot delete the case, it is open for another user.",})
+        "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service."
+    })
     public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
         synchronized (caseActionSerializationLock) {
             if (null != currentCase) {
-                throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()));
+                throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
             }
         }
 
@@ -623,7 +623,7 @@ public class Case {
                     assert (null != dirLock);
                     deleteCase(metadata, progressIndicator);
                 } catch (CoordinationServiceException ex) {
-                    // RJCTODO: need to throw here, could not delete case at all
+                    throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase(), ex);
                 }
             }
         } finally {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java
index 8a9f7bc17c..6ce1b421d5 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java
@@ -36,7 +36,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
 /**
  * The action associated with the Delete button of the Case Properties panel. It
  * deletes the current case.
- * 
+ *
  * This action should only be invoked in the event dispatch thread (EDT).
  */
 final class CaseDeleteAction extends CallableSystemAction {
@@ -90,7 +90,7 @@ final class CaseDeleteAction extends CallableSystemAction {
                             logger.log(Level.SEVERE, String.format("Failed to delete case %s at %s", caseName, caseDirectory), ex);
                             JOptionPane.showMessageDialog(
                                     null,
-                                    Bundle.Case_deleteCaseFailureMessageBox_message(ex.getMessage()),
+                                    Bundle.Case_deleteCaseFailureMessageBox_message(ex.getLocalizedMessage()),
                                     Bundle.Case_deleteCaseFailureMessageBox_title(),
                                     JOptionPane.ERROR_MESSAGE);
                         }
@@ -99,6 +99,7 @@ final class CaseDeleteAction extends CallableSystemAction {
                          * of the Delete button that invokes this action.
                          */
                         CasePropertiesAction.closeCasePropertiesWindow();
+                        StartupWindowProvider.getInstance().open();
                     }
                 }.execute();
             }
diff --git a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java
index bd59bfdbfd..a7dcfa5503 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java
+++ b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java
@@ -68,16 +68,6 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
         this.buttonLabels = buttonLabels;
         this.focusedButtonLabel = focusedButtonLabel;
         this.buttonListener = buttonListener;
-//        DialogDescriptor dialogDescriptor = new DialogDescriptor(
-//                progressPanel,
-//                title,
-//                true,
-//                buttonLabels,
-//                focusedButtonLabel,
-//                DialogDescriptor.BOTTOM_ALIGN,
-//                HelpCtx.DEFAULT_HELP,
-//                buttonListener);
-//        dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
     }
 
     /**
@@ -95,9 +85,6 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
         this.buttonLabels = null;
         this.focusedButtonLabel = null;
         this.buttonListener = null;
-//        dialog = new JDialog(parent, title, true);
-//        dialog.add(progressPanel);
-//        dialog.pack();
     }
 
     /**
@@ -115,8 +102,6 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
             progressPanel.setMessage(message);
             progressPanel.setMaximum(totalWorkUnits);
             displayDialog();
-//            dialog.setLocationRelativeTo(parent);
-//            this.dialog.setVisible(true);
         });
     }
 
@@ -133,8 +118,6 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
             progressPanel.setIndeterminate(true);
             progressPanel.setMessage(message);
             displayDialog();
-//            dialog.setLocationRelativeTo(parent);
-//            this.dialog.setVisible(true);
         });
     }
 
@@ -251,16 +234,28 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
      * Creates and dislpays the dialog for the progress indicator.
      */
     private void displayDialog() {
-        DialogDescriptor dialogDescriptor = new DialogDescriptor(
-                progressPanel,
-                title,
-                true,
-                buttonLabels,
-                focusedButtonLabel,
-                DialogDescriptor.BOTTOM_ALIGN,
-                HelpCtx.DEFAULT_HELP,
-                buttonListener);
-        dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
+        if (null != buttonLabels && null != focusedButtonLabel && null != buttonListener) {
+            /*
+             * Dialog with buttons.
+             */
+            DialogDescriptor dialogDescriptor = new DialogDescriptor(
+                    progressPanel,
+                    title,
+                    true,
+                    buttonLabels,
+                    focusedButtonLabel,
+                    DialogDescriptor.BOTTOM_ALIGN,
+                    HelpCtx.DEFAULT_HELP,
+                    buttonListener);
+            dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
+        } else {
+            /*
+             * Dialog without buttons.
+             */
+            dialog = new JDialog(parent, title, true);
+            dialog.add(progressPanel);
+            dialog.pack();
+        }
         dialog.setLocationRelativeTo(parent);
         this.dialog.setVisible(true);
     }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
index 44d5335888..3a55aad646 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
@@ -721,55 +721,30 @@ public class Server {
     @NbBundle.Messages({
         "# {0} - core name", "Server.deleteCore.exception.msg=Failed to delete Solr core {0}",})
     void deleteCore(String coreName, Case.CaseType caseType) throws KeywordSearchServiceException {
-        if (null != currentSolrServer) {
-            /*
-             * If there is a Solr server connection and the core to delete is
-             * open, then close it.
-             */
-            currentCoreLock.readLock().lock();
-            try {
-                if (null != currentCore) {
-                    if (currentCore.getName().equals(coreName)) {
-                        // close current core first
-                        closeCore();
-                    }
-                }
-            } catch (KeywordSearchModuleException ex) {
-                throw new KeywordSearchServiceException(NbBundle.getMessage(Server.class, "Server.close.exception.msg"), ex);
-            } finally {
-                currentCoreLock.readLock().unlock();
-            }
-        }
-
-        /*
-         * Send a core unload request to the Solr server, with the parameter set
-         * that request deleting the index and the instance directory
-         * (deleteInstanceDir = true). Note that this removes everything related
-         * to the core on the server (the index directory, the configuration
-         * files, etc.), but does not delete the actual Solr index directory in
-         * the case directory.
-         */
-        HttpSolrServer solrServer;
-        if (null != currentSolrServer) {
-            solrServer = currentSolrServer;
-        } else {
-            try {
-                if (caseType == CaseType.SINGLE_USER_CASE) {
-                    Integer localSolrServerPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT));
-                    solrServer = new HttpSolrServer("http://localhost:" + localSolrServerPort + "/solr"); //NON-NLS
-                } else {
-                    String host = UserPreferences.getIndexingServerHost();
-                    String port = UserPreferences.getIndexingServerPort();
-                    solrServer = new HttpSolrServer("http://" + host + ":" + port + "/solr"); //NON-NLS
-                }
-                connectToSolrServer(solrServer);
-            } catch (SolrServerException | IOException ex) {
-                throw new KeywordSearchServiceException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);
-            }
-        }
-
         try {
-            org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(coreName, true, true, solrServer);
+            HttpSolrServer solrServer;
+            if (caseType == CaseType.SINGLE_USER_CASE) {
+                Integer localSolrServerPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT));
+                solrServer = new HttpSolrServer("http://localhost:" + localSolrServerPort + "/solr"); //NON-NLS
+            } else {
+                String host = UserPreferences.getIndexingServerHost();
+                String port = UserPreferences.getIndexingServerPort();
+                solrServer = new HttpSolrServer("http://" + host + ":" + port + "/solr"); //NON-NLS
+            }
+            connectToSolrServer(solrServer);
+            CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, solrServer);
+            if (null != response.getCoreStatus(coreName).get("instanceDir")) {             //NON-NLS
+                /*
+                 * Send a core unload request to the Solr server, with the
+                 * parameter set that request deleting the index and the
+                 * instance directory (deleteInstanceDir = true). Note that this
+                 * removes everything related to the core on the server (the
+                 * index directory, the configuration files, etc.), but does not
+                 * delete the actual Solr text index because it is currently
+                 * stored in the case directory.
+                 */
+                org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(coreName, true, true, solrServer);
+            }
         } catch (SolrServerException | HttpSolrServer.RemoteSolrException | IOException ex) {
             throw new KeywordSearchServiceException(Bundle.Server_deleteCore_exception_msg(coreName), ex);
         }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
index 89213f3a51..d54ff86062 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.autopsy.keywordsearch;
 
+import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.net.InetAddress;
@@ -37,6 +38,7 @@ import org.openide.util.lookup.ServiceProviders;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.casemodule.CaseMetadata;
 import org.sleuthkit.autopsy.core.RuntimeProperties;
+import org.sleuthkit.autopsy.coreutils.FileUtil;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.autopsy.framework.AutopsyService;
 import org.sleuthkit.autopsy.framework.ProgressIndicator;
@@ -50,7 +52,8 @@ import org.sleuthkit.datamodel.TskCoreException;
  * text indexing and search.
  */
 @ServiceProviders(value = {
-    @ServiceProvider(service = KeywordSearchService.class),
+    @ServiceProvider(service = KeywordSearchService.class)
+    ,
     @ServiceProvider(service = AutopsyService.class)}
 )
 public class SolrSearchService implements KeywordSearchService, AutopsyService {
@@ -138,16 +141,17 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
         }
     }
 
-    @NbBundle.Messages({"# {0} - case directory",
-        "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from caseDirectory: {0}",
-        "# {0} - case directory",
-        "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case"
-    })
     /**
      * Deletes Solr core for a case.
      *
      * @param metadata The CaseMetadata which will have its core deleted.
      */
+    @NbBundle.Messages({"# {0} - case directory",
+        "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from caseDirectory: {0}",
+        "# {0} - case directory",
+        "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case",
+        "SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0}"
+    })
     @Override
     public void deleteTextIndex(CaseMetadata metadata) throws KeywordSearchServiceException {
         String caseDirectory = metadata.getCaseDirectory();
@@ -162,14 +166,24 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
         String currentSchema = IndexFinder.getCurrentSchemaVersion();
         String currentSolr = IndexFinder.getCurrentSolrVersion();
         for (Index index : indexMetadata.getIndexes()) {
-            if (index.getSolrVersion().equals(currentSolr) && index.getSchemaVersion().equals(currentSchema)) { // RJCTODO
+            if (index.getSolrVersion().equals(currentSolr) && index.getSchemaVersion().equals(currentSchema)) {
+                /*
+                 * Unload/delete the core on the server and then delete the text
+                 * index files.
+                 */
                 KeywordSearch.getServer().deleteCore(index.getIndexName(), metadata.getCaseType());
-                return; //only one core exists for each combination of solr and schema version
+                if (!FileUtil.deleteDir(new File(index.getIndexPath()).getParentFile())) {
+                    throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath()));                    
+                }
             }
+            return; //only one core exists for each combination of solr and schema version
         }
+
         //this code this code will only execute if an index for the current core was not found 
-        logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
-        throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
+        logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class,
+                 "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
+        throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class,
+                 "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
     }
 
     @Override
@@ -203,7 +217,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
         if (context.cancelRequested()) {
             return;
         }
-                
+
         ProgressIndicator progress = context.getProgressIndicator();
         int totalNumProgressUnits = 7;
         int progressUnitsCompleted = 0;

From c8176c57ad18536837230b32f9ba975a93a7206d Mon Sep 17 00:00:00 2001
From: Richard Cordovano 
Date: Wed, 3 May 2017 20:12:34 -0400
Subject: [PATCH 29/57] Refactor to remove vague framework package

---
 Core/nbproject/project.xml                             |  4 +++-
 .../{framework => appservices}/AutopsyService.java     |  3 ++-
 Core/src/org/sleuthkit/autopsy/casemodule/Case.java    | 10 +++++-----
 .../sleuthkit/autopsy/casemodule/ImageDSProcessor.java |  2 +-
 .../autopsy/casemodule/LocalDiskDSProcessor.java       |  2 +-
 .../autopsy/casemodule/LocalFilesDSProcessor.java      |  2 +-
 .../AutoIngestDataSourceProcessor.java                 |  2 +-
 .../autopsy/imagewriter/ImageWriterService.java        |  2 +-
 .../autopsy/{framework => progress}/Bundle.properties  |  0
 .../LoggingProgressIndicator.java                      |  2 +-
 .../ModalDialogProgressIndicator.java                  |  2 +-
 .../{framework => progress}/ProgressIndicator.java     |  2 +-
 .../autopsy/{framework => progress}/ProgressPanel.form |  2 +-
 .../autopsy/{framework => progress}/ProgressPanel.java |  2 +-
 .../SilentProgressIndicator.java                       |  4 ++--
 .../org/sleuthkit/autopsy/test/TestAutopsyService.java |  4 ++--
 .../experimental/autoingest/AutoIngestManager.java     |  4 ++--
 .../autoingest/AutopsyManifestFileParser.java          |  2 +-
 .../sleuthkit/autopsy/keywordsearch/IndexFinder.java   |  2 +-
 .../autopsy/keywordsearch/SolrSearchService.java       |  4 ++--
 20 files changed, 30 insertions(+), 27 deletions(-)
 rename Core/src/org/sleuthkit/autopsy/{framework => appservices}/AutopsyService.java (98%)
 rename Core/src/org/sleuthkit/autopsy/{framework => datasourceprocessors}/AutoIngestDataSourceProcessor.java (98%)
 rename Core/src/org/sleuthkit/autopsy/{framework => progress}/Bundle.properties (100%)
 rename Core/src/org/sleuthkit/autopsy/{framework => progress}/LoggingProgressIndicator.java (98%)
 rename Core/src/org/sleuthkit/autopsy/{framework => progress}/ModalDialogProgressIndicator.java (99%)
 rename Core/src/org/sleuthkit/autopsy/{framework => progress}/ProgressIndicator.java (98%)
 rename Core/src/org/sleuthkit/autopsy/{framework => progress}/ProgressPanel.form (91%)
 rename Core/src/org/sleuthkit/autopsy/{framework => progress}/ProgressPanel.java (98%)
 rename Core/src/org/sleuthkit/autopsy/{framework => progress}/SilentProgressIndicator.java (93%)

diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 2d905e7e49..d2328273c1 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -243,6 +243,7 @@
                 net.sf.sevenzipjbinding.simple
                 net.sf.sevenzipjbinding.simple.impl
                 org.sleuthkit.autopsy.actions
+                org.sleuthkit.autopsy.appservices
                 org.sleuthkit.autopsy.casemodule
                 org.sleuthkit.autopsy.casemodule.events
                 org.sleuthkit.autopsy.casemodule.services
@@ -254,17 +255,18 @@
                 org.sleuthkit.autopsy.corecomponents
                 org.sleuthkit.autopsy.coreutils
                 org.sleuthkit.autopsy.datamodel
+                org.sleuthkit.autopsy.datasourceprocessors
                 org.sleuthkit.autopsy.directorytree
                 org.sleuthkit.autopsy.events
                 org.sleuthkit.autopsy.externalresults
                 org.sleuthkit.autopsy.filesearch
-                org.sleuthkit.autopsy.framework
                 org.sleuthkit.autopsy.ingest
                 org.sleuthkit.autopsy.keywordsearchservice
                 org.sleuthkit.autopsy.menuactions
                 org.sleuthkit.autopsy.modules.filetypeid
                 org.sleuthkit.autopsy.modules.hashdatabase
                 org.sleuthkit.autopsy.modules.vmextractor
+                org.sleuthkit.autopsy.progress
                 org.sleuthkit.autopsy.report
                 org.sleuthkit.datamodel
             
diff --git a/Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java b/Core/src/org/sleuthkit/autopsy/appservices/AutopsyService.java
similarity index 98%
rename from Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java
rename to Core/src/org/sleuthkit/autopsy/appservices/AutopsyService.java
index 2869b550f3..6e07fbe5fe 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java
+++ b/Core/src/org/sleuthkit/autopsy/appservices/AutopsyService.java
@@ -16,9 +16,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.framework;
+package org.sleuthkit.autopsy.appservices;
 
 import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.progress.ProgressIndicator;
 
 /**
  * An interface for services that report status and may manage case and
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index b76e4aca9e..eedf524d78 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -92,11 +92,11 @@ import org.sleuthkit.autopsy.coreutils.Version;
 import org.sleuthkit.autopsy.events.AutopsyEvent;
 import org.sleuthkit.autopsy.events.AutopsyEventException;
 import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
-import org.sleuthkit.autopsy.framework.AutopsyService;
-import org.sleuthkit.autopsy.framework.AutopsyService.CaseContext;
-import org.sleuthkit.autopsy.framework.LoggingProgressIndicator;
-import org.sleuthkit.autopsy.framework.ModalDialogProgressIndicator;
-import org.sleuthkit.autopsy.framework.ProgressIndicator;
+import org.sleuthkit.autopsy.appservices.AutopsyService;
+import org.sleuthkit.autopsy.appservices.AutopsyService.CaseContext;
+import org.sleuthkit.autopsy.progress.LoggingProgressIndicator;
+import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator;
+import org.sleuthkit.autopsy.progress.ProgressIndicator;
 import org.sleuthkit.autopsy.ingest.IngestJob;
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
index 36abbd307a..6a48384416 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
@@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
 import org.sleuthkit.autopsy.coreutils.DataSourceUtils;
-import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
 
 /**
  * A image file data source processor that implements the DataSourceProcessor
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
index f19ad01af1..26185cb9ba 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
@@ -31,7 +31,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
 import org.sleuthkit.autopsy.coreutils.DriveUtils;
 import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
-import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
 
 /**
  * A local drive data source processor that implements the DataSourceProcessor
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java
index e207734714..974244bd30 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java
@@ -29,7 +29,7 @@ import org.openide.util.lookup.ServiceProviders;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
-import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
 
 /**
  * A local/logical files and/or directories data source processor that
diff --git a/Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AutoIngestDataSourceProcessor.java
similarity index 98%
rename from Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java
rename to Core/src/org/sleuthkit/autopsy/datasourceprocessors/AutoIngestDataSourceProcessor.java
index e4acab8234..11c0559795 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AutoIngestDataSourceProcessor.java
@@ -16,7 +16,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.framework;
+package org.sleuthkit.autopsy.datasourceprocessors;
 
 import java.nio.file.Path;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
diff --git a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java
index cf6f042292..a3853f0b31 100644
--- a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java
+++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java
@@ -25,7 +25,7 @@ import org.openide.DialogDisplayer;
 import org.openide.NotifyDescriptor;
 import org.openide.util.NbBundle;
 import org.openide.util.lookup.ServiceProvider;
-import org.sleuthkit.autopsy.framework.AutopsyService;
+import org.sleuthkit.autopsy.appservices.AutopsyService;
 
 /**
  * Creates and handles closing of ImageWriter objects. Currently, ImageWriter is
diff --git a/Core/src/org/sleuthkit/autopsy/framework/Bundle.properties b/Core/src/org/sleuthkit/autopsy/progress/Bundle.properties
similarity index 100%
rename from Core/src/org/sleuthkit/autopsy/framework/Bundle.properties
rename to Core/src/org/sleuthkit/autopsy/progress/Bundle.properties
diff --git a/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/LoggingProgressIndicator.java
similarity index 98%
rename from Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java
rename to Core/src/org/sleuthkit/autopsy/progress/LoggingProgressIndicator.java
index 83e7db95e0..6ae7ebdb2d 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java
+++ b/Core/src/org/sleuthkit/autopsy/progress/LoggingProgressIndicator.java
@@ -16,7 +16,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.framework;
+package org.sleuthkit.autopsy.progress;
 
 import java.util.logging.Level;
 import org.sleuthkit.autopsy.coreutils.Logger;
diff --git a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java
similarity index 99%
rename from Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java
rename to Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java
index a7dcfa5503..d49574bf24 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java
+++ b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java
@@ -16,7 +16,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.framework;
+package org.sleuthkit.autopsy.progress;
 
 import java.awt.Dialog;
 import java.awt.Frame;
diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java
similarity index 98%
rename from Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java
rename to Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java
index 6f91dd3231..8e5b881182 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java
+++ b/Core/src/org/sleuthkit/autopsy/progress/ProgressIndicator.java
@@ -16,7 +16,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.framework;
+package org.sleuthkit.autopsy.progress;
 
 /**
  * An interface for progress indicators. A progress indicator can run in
diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.form
similarity index 91%
rename from Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form
rename to Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.form
index 59678117af..75597eef34 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.form
@@ -42,7 +42,7 @@
     
       
         
-          
+          
         
       
     
diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java
similarity index 98%
rename from Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java
rename to Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java
index 4130eb4852..b918d1f9f0 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java
@@ -16,7 +16,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.framework;
+package org.sleuthkit.autopsy.progress;
 
 /**
  * A progress panel consisting of a message label and a progress bar.
diff --git a/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/SilentProgressIndicator.java
similarity index 93%
rename from Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java
rename to Core/src/org/sleuthkit/autopsy/progress/SilentProgressIndicator.java
index b7b03b8c44..c7ed9c671c 100644
--- a/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java
+++ b/Core/src/org/sleuthkit/autopsy/progress/SilentProgressIndicator.java
@@ -16,9 +16,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.framework;
+package org.sleuthkit.autopsy.progress;
 
-import org.sleuthkit.autopsy.framework.ProgressIndicator;
+import org.sleuthkit.autopsy.progress.ProgressIndicator;
 
 /**
  * A "silent" or "null" progress indicator.
diff --git a/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java
index f3e30a6b63..4fb2997691 100644
--- a/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java
+++ b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java
@@ -21,8 +21,8 @@ package org.sleuthkit.autopsy.test;
 import java.util.logging.Level;
 import org.openide.util.lookup.ServiceProvider;
 import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.framework.AutopsyService;
-import org.sleuthkit.autopsy.framework.ProgressIndicator;
+import org.sleuthkit.autopsy.appservices.AutopsyService;
+import org.sleuthkit.autopsy.progress.ProgressIndicator;
 
 /**
  * An implementation of the Autopsy service interface used for test purposes.
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java
index 900a5a603b..d92e8d2dbe 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java
@@ -92,8 +92,8 @@ import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.Pro
 import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences;
 import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration;
 import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException;
-import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor;
-import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
+import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
 import org.sleuthkit.autopsy.ingest.IngestJob;
 import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason;
 import org.sleuthkit.autopsy.ingest.IngestJobSettings;
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java
index 5b7fc5e3c9..f25182b018 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java
@@ -42,7 +42,7 @@ public final class AutopsyManifestFileParser implements ManifestFileParser {
     private static final String ROOT_ELEM_TAG_NAME = "AutopsyManifest";
     private static final String CASE_NAME_XPATH = "/AutopsyManifest/CaseName/text()";
     private static final String DEVICE_ID_XPATH = "/AutopsyManifest/DeviceId/text()";
-    private static final String DATA_SOURCE_NAME_XPATH = "/AutopsyManifest/DataSourcePath/text()";
+    private static final String DATA_SOURCE_NAME_XPATH = "/AutopsyManifest/DataSource/text()";
     
     @Override
     public boolean fileIsManifest(Path filePath) {
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java
index 6ecf83bdce..b4a09e226e 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java
@@ -27,7 +27,7 @@ import java.util.List;
 import org.apache.commons.lang.math.NumberUtils;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.framework.AutopsyService;
+import org.sleuthkit.autopsy.appservices.AutopsyService;
 
 /**
  * This class handles the task of finding and identifying KWS index folders.
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
index d54ff86062..eee3ac4dad 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
@@ -40,8 +40,8 @@ import org.sleuthkit.autopsy.casemodule.CaseMetadata;
 import org.sleuthkit.autopsy.core.RuntimeProperties;
 import org.sleuthkit.autopsy.coreutils.FileUtil;
 import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.framework.AutopsyService;
-import org.sleuthkit.autopsy.framework.ProgressIndicator;
+import org.sleuthkit.autopsy.appservices.AutopsyService;
+import org.sleuthkit.autopsy.progress.ProgressIndicator;
 import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
 import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
 import org.sleuthkit.datamodel.BlackboardArtifact;

From cff500773063c74b352cf20603ed0db37ec8044a Mon Sep 17 00:00:00 2001
From: Brian Carrier 
Date: Wed, 3 May 2017 20:20:26 -0400
Subject: [PATCH 30/57] Added javadocs to parser interface

---
 .../autoingest/ManifestFileParser.java        | 21 ++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java
index 00fb2b9a5f..863155afcc 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2015 Basis Technology Corp.
+ * Copyright 2015-2017 Basis Technology Corp.
  * Contact: carrier  sleuthkit  org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,9 +20,28 @@ package org.sleuthkit.autopsy.experimental.autoingest;
 
 import java.nio.file.Path;
 
+/**
+ * Responsible for parsing the manifest files that
+ * describe cases, devices, and data sources.
+ * These are used by autoingest to create cases and add
+ * data sources to the correct case.
+ */
 public interface ManifestFileParser {
     
+    /**
+     * Checks if a file is this type of manifest file
+     * @param filePath Path to potential manifest file
+     * @return True if the file is a manifest that this parser supports
+     */ 
     boolean fileIsManifest(Path filePath);
+    
+    /**
+     * Parses the given file.  Will only be called if 
+     * fileIsManifest() previously returned true. 
+     * @param filePath Path to manifest file
+     * @return Parsed results
+     * @throws org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException 
+     */
     Manifest parse(Path filePath) throws ManifestFileParserException;
     
     public final static class ManifestFileParserException extends Exception {

From 0bd4607480ebb0ff5118a22990bd068f44b2b67c Mon Sep 17 00:00:00 2001
From: "U-BASIS\\zhaohui" 
Date: Thu, 4 May 2017 10:14:04 -0400
Subject: [PATCH 31/57] 2119: Adding maxParentLevels property to
 manifestclasspath to fix 'No suitable relative path' problem and let
 regression test to get the autopsy version number from project.properties
 file instead of hard coded.

---
 Testing/build.xml         |  6 ++++--
 test/script/regression.py | 35 +++++++++++++++++++++++++++++++++--
 2 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/Testing/build.xml b/Testing/build.xml
index 08624326c5..7168369574 100644
--- a/Testing/build.xml
+++ b/Testing/build.xml
@@ -17,9 +17,11 @@
     
 
         
+                  to a single jar file: allJarsInUse.jar. Then we put this new jar to classpath for testing program to avoid command line Java classpath too long problem.
+      Note: Started from ant 1.10, maxParentLevels are enforced. If you get error from manifestclasspath complaines 'No suitable relative path from ...' then it's time to
+            increase your maxParentLevels -->
     
-        
+        
             
         
         
diff --git a/test/script/regression.py b/test/script/regression.py
index 9218878968..59a346127a 100755
--- a/test/script/regression.py
+++ b/test/script/regression.py
@@ -417,11 +417,19 @@ class TestRunner(object):
         test_data.ant.append("-Dignore_unalloc=" + "%s" % test_config.args.unallocated)
         test_data.ant.append("-Dtest.timeout=" + str(test_config.timeout))
 
+        # Note: test_data has autopys_version attribute, but we couldn't see it from here. It's set after run ingest.
+        autopsyVersionPath = os.path.join("..", "..", "nbproject", "project.properties")
+
+        autopsyVersion = search_properties("app.version", autopsyVersionPath)
+        if len(autopsyVersion) == 0:
+            print("Couldn't get the autopsy version from: " + autopsyVersionPath)
+            sys.exit(1)
+
         # if need autopsyPlatform setup
         if len(test_data.main_config.autopsyPlatform) > 0:
-            test_data.ant.append("-Dnbplatform.Autopsy_4.4.0.netbeans.dest.dir=" + test_data.main_config.autopsyPlatform)
+            test_data.ant.append("-Dnbplatform.Autopsy_" + autopsyVersion + ".netbeans.dest.dir=" + test_data.main_config.autopsyPlatform)
             test_data.ant.append("-Dnbplatform.default.harness.dir=" + test_data.main_config.autopsyPlatform + "/harness")
-            test_data.ant.append("-Dnbplatform.Autopsy_4.4.0.harness.dir=" + test_data.main_config.autopsyPlatform + "/harness")
+            test_data.ant.append("-Dnbplatform.Autopsy_" + autopsyVersion + ".harness.dir=" + test_data.main_config.autopsyPlatform + "/harness")
  
         Errors.print_out("Ingesting Image:\n" + test_data.image_file + "\n")
         Errors.print_out("CMD: " + " ".join(test_data.ant))
@@ -1979,6 +1987,29 @@ def find_file_in_dir(dir, name, ext):
     except:
         raise DirNotFoundException(dir)
 
+def search_properties(string, properties_file):
+    """Find a property value.
+
+    Args:
+        string: the String to search for.
+        properties_file: the properties file to search.
+
+    Returns:
+        a string, the value for the given String.
+    """
+    result = ""
+    pf = codecs.open(properties_file, "r", "utf-8")
+    try:
+        for line in pf:
+            if string in line:
+                result = line.split('=')[1].rstrip('\n\r ')
+                break
+        pf.close()
+    except:
+        print_error("Couldn't find property:" + string + " from: " + properties_file)
+        sys.exit(1)
+    return result
+
 
 class OS:
   LINUX, MAC, WIN, CYGWIN = range(4)

From 7c22a6ade971d714ab568edde814f19448e74334 Mon Sep 17 00:00:00 2001
From: Richard Cordovano 
Date: Thu, 4 May 2017 13:59:21 -0400
Subject: [PATCH 32/57] Assorted case operatino UI fixes

---
 .../sleuthkit/autopsy/casemodule/Case.java    |  6 ++++-
 .../CaseActionCancelledException.java         |  2 +-
 .../autopsy/casemodule/CaseOpenAction.java    | 17 ++++---------
 .../casemodule/OpenRecentCasePanel.java       | 18 +++++++-------
 .../autopsy/casemodule/RecentItems.java       |  6 ++---
 .../autoingest/AutoIngestCasePanel.java       | 24 +++++++++----------
 6 files changed, 34 insertions(+), 39 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index eedf524d78..e64a1e6341 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -463,7 +463,8 @@ public class Case {
      *                             exception.
      */
     @Messages({
-        "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata."
+        "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.",
+        "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
     })
     public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
         CaseMetadata metadata;
@@ -472,6 +473,9 @@ public class Case {
         } catch (CaseMetadataException ex) {
             throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex);
         }
+        if (CaseType.MULTI_USER_CASE == metadata.getCaseType() && !UserPreferences.getIsMultiUserModeEnabled()) {
+            throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());            
+        }        
         openAsCurrentCase(new Case(metadata), false);
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java
index 042bfe912e..87635f40b9 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseActionCancelledException.java
@@ -22,7 +22,7 @@ package org.sleuthkit.autopsy.casemodule;
  * Exception thrown when a case action (e.g., create, open, close, delete) is
  * cancelled before it is completed.
  */
-class CaseActionCancelledException extends CaseActionException {
+public final class CaseActionCancelledException extends CaseActionException {
 
     private static final long serialVersionUID = 1L;
 
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java
index 983cbd9149..533cab58c5 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java
@@ -115,19 +115,9 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
                     protected void done() {
                         try {
                             get();
-                        } catch (InterruptedException ex) {
-                            logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS
-                            WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
-                            JOptionPane.showMessageDialog(
-                                    WindowManager.getDefault().getMainWindow(),
-                                    ex.getMessage(),
-                                    NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
-                                    JOptionPane.ERROR_MESSAGE);
-                            StartupWindowProvider.getInstance().open();
-                        } catch (ExecutionException ex) {
-                            if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
+                        } catch (InterruptedException | ExecutionException ex) {
+                            if (ex instanceof InterruptedException || (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException))) {
                                 logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS
-                                WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                                 JOptionPane.showMessageDialog(
                                         WindowManager.getDefault().getMainWindow(),
                                         ex.getCause().getMessage(), //get the message of the wrapped exception
@@ -135,8 +125,9 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
                                         JOptionPane.ERROR_MESSAGE);
                             }
                             StartupWindowProvider.getInstance().open();
+                        } finally {
+                            WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                         }
-                        WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                     }
                 }.execute();
             }
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java
index 562579258f..ba1d1d31cf 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java
@@ -100,9 +100,9 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
         if (casePaths.length < 1) {
             return;
         }
-        final String casePath = casePaths[imagesTable.getSelectedRow()];
+        final String caseMetadataFilePath = casePaths[imagesTable.getSelectedRow()];
         final String caseName = caseNames[imagesTable.getSelectedRow()];
-        if (!casePath.isEmpty()) {
+        if (!caseMetadataFilePath.isEmpty()) {
             try {
                 StartupWindowProvider.getInstance().close();
                 CueBannerPanel.closeOpenRecentCasesWindow();
@@ -113,25 +113,25 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
             /*
              * Open the case.
              */
-            if (caseName.isEmpty() || casePath.isEmpty() || (!new File(casePath).exists())) {
+            if (caseName.isEmpty() || caseMetadataFilePath.isEmpty() || (!new File(caseMetadataFilePath).exists())) {
                 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
                         NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName),
                         NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"),
                         JOptionPane.ERROR_MESSAGE);
-                RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore
+                RecentCases.getInstance().removeRecentCase(caseName, caseMetadataFilePath); // remove the recent case if it doesn't exist anymore
                 StartupWindowProvider.getInstance().open();
             } else {
                 new Thread(() -> {
                     try {
-                        Case.openAsCurrentCase(casePath);
+                        Case.openAsCurrentCase(caseMetadataFilePath);
                     } catch (CaseActionException ex) {
                         SwingUtilities.invokeLater(() -> {
-                            if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
-                                logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS                            
+                            if (!(ex instanceof CaseActionCancelledException)) {
+                                logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS
                                 JOptionPane.showMessageDialog(
                                         WindowManager.getDefault().getMainWindow(),
-                                        ex.getMessage(), // Should be user-friendly
-                                        NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
+                                        ex.getMessage(),
+                                        NbBundle.getMessage(OpenRecentCasePanel.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
                                         JOptionPane.ERROR_MESSAGE);
                             }
                             StartupWindowProvider.getInstance().open();
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java
index b60cb3e800..5237c90487 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java
@@ -64,9 +64,10 @@ class RecentItems implements ActionListener {
             new Thread(() -> {
                 try {
                     Case.openAsCurrentCase(caseMetaDataFilePath);
+                    StartupWindowProvider.getInstance().close();
                 } catch (CaseActionException ex) {
                     SwingUtilities.invokeLater(() -> {
-                        if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
+                        if (!(ex instanceof CaseActionCancelledException)) {
                             logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS
                             JOptionPane.showMessageDialog(
                                     WindowManager.getDefault().getMainWindow(),
@@ -77,8 +78,7 @@ class RecentItems implements ActionListener {
                         StartupWindowProvider.getInstance().open();
                     });
                 }
-            }).
-                    start();
+            }).start();
         }
     }
 }
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java
index 20d2a989cb..9c44f701cd 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java
@@ -39,7 +39,9 @@ import javax.swing.SwingWorker;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableColumn;
+import org.openide.util.NbBundle;
 import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.casemodule.CaseActionCancelledException;
 import org.sleuthkit.autopsy.casemodule.CaseMetadata;
 import org.sleuthkit.autopsy.casemodule.StartupWindowProvider;
 import org.sleuthkit.autopsy.coreutils.Logger;
@@ -295,18 +297,16 @@ public final class AutoIngestCasePanel extends JPanel {
             protected void done() {
                 try {
                     get();
-                } catch (InterruptedException ex) {
-                    logger.log(Level.SEVERE, String.format("Error while opening case with case metadata file path %s", caseMetadataFilePath), ex);
-                    JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
-                            ex.getMessage(),
-                            org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.cannotOpenCase"),
-                            JOptionPane.ERROR_MESSAGE);
-                } catch (ExecutionException ex) {
-                    logger.log(Level.SEVERE, String.format("Error while opening case with case metadata file path %s", caseMetadataFilePath), ex);
-                    JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
-                            ex.getCause().getMessage(),
-                            org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.cannotOpenCase"),
-                            JOptionPane.ERROR_MESSAGE);
+                } catch (InterruptedException | ExecutionException ex) {
+                    if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
+                        logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS
+                        JOptionPane.showMessageDialog(
+                                WindowManager.getDefault().getMainWindow(),
+                                ex.getCause().getMessage(), //get the message of the wrapped exception
+                                NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                    StartupWindowProvider.getInstance().open();
                 } finally {
                     setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                 }

From 72e0c9d7fd7eccba6bd580497e88b44e3ca7dcb3 Mon Sep 17 00:00:00 2001
From: "U-BASIS\\dgrove" 
Date: Thu, 4 May 2017 14:58:17 -0400
Subject: [PATCH 33/57] Updated JAR versions, and fixed compatibility issues.

---
 Core/ivy.xml                                  |  2 +-
 Core/nbproject/project.properties             |  2 +-
 Core/nbproject/project.xml                    |  8 +++--
 .../embeddedfileextractor/ImageExtractor.java | 33 ++++++++++---------
 .../sleuthkit/autopsy/report/ReportExcel.java | 10 +++---
 CoreLibs/ivy.xml                              |  4 +--
 CoreLibs/nbproject/project.properties         | 15 +++++----
 CoreLibs/nbproject/project.xml                | 32 ++++++++++--------
 Experimental/ivy.xml                          |  2 +-
 Experimental/nbproject/project.properties     |  2 +-
 Experimental/nbproject/project.xml            |  4 +--
 11 files changed, 61 insertions(+), 53 deletions(-)

diff --git a/Core/ivy.xml b/Core/ivy.xml
index 55468a600f..d40831b773 100644
--- a/Core/ivy.xml
+++ b/Core/ivy.xml
@@ -15,7 +15,7 @@
 
         
 
-        
+        
         
         
 
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index e3895cc49d..ddb1df5848 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -12,7 +12,7 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi
 file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
 file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar
 file.reference.StixLib.jar=release/modules/ext/StixLib.jar
-file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar
+file.reference.tika-core-1.14.jar=release/modules/ext/tika-core-1.14.jar
 file.reference.Tsk_DataModel_PostgreSQL.jar=release/modules/ext/Tsk_DataModel_PostgreSQL.jar
 file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar
 file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 2d905e7e49..eb4739d209 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -243,6 +243,7 @@
                 net.sf.sevenzipjbinding.simple
                 net.sf.sevenzipjbinding.simple.impl
                 org.sleuthkit.autopsy.actions
+                org.sleuthkit.autopsy.appservices
                 org.sleuthkit.autopsy.casemodule
                 org.sleuthkit.autopsy.casemodule.events
                 org.sleuthkit.autopsy.casemodule.services
@@ -254,17 +255,18 @@
                 org.sleuthkit.autopsy.corecomponents
                 org.sleuthkit.autopsy.coreutils
                 org.sleuthkit.autopsy.datamodel
+                org.sleuthkit.autopsy.datasourceprocessors
                 org.sleuthkit.autopsy.directorytree
                 org.sleuthkit.autopsy.events
                 org.sleuthkit.autopsy.externalresults
                 org.sleuthkit.autopsy.filesearch
-                org.sleuthkit.autopsy.framework
                 org.sleuthkit.autopsy.ingest
                 org.sleuthkit.autopsy.keywordsearchservice
                 org.sleuthkit.autopsy.menuactions
                 org.sleuthkit.autopsy.modules.filetypeid
                 org.sleuthkit.autopsy.modules.hashdatabase
                 org.sleuthkit.autopsy.modules.vmextractor
+                org.sleuthkit.autopsy.progress
                 org.sleuthkit.autopsy.report
                 org.sleuthkit.datamodel
             
@@ -325,8 +327,8 @@
                 release/modules/ext/sevenzipjbinding-AllPlatforms.jar
             
             
-                ext/tika-core-1.5.jar
-                release/modules/ext/tika-core-1.5.jar
+                ext/tika-core-1.14.jar
+                release/modules/ext/tika-core-1.14.jar
             
             
                 ext/metadata-extractor-2.8.1.jar
diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java
index 9df9927ad8..335bdc0a91 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java
@@ -27,12 +27,13 @@ import java.util.List;
 import java.util.logging.Level;
 import org.apache.poi.OldFileFormatException;
 import org.apache.poi.POIXMLException;
-import org.apache.poi.hslf.model.Picture;
-import org.apache.poi.hslf.usermodel.PictureData;
-import org.apache.poi.hslf.usermodel.SlideShow;
+import org.apache.poi.hwpf.usermodel.Picture;
+import org.apache.poi.hslf.usermodel.HSLFPictureData;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hwpf.HWPFDocument;
 import org.apache.poi.hwpf.model.PicturesTable;
+import org.apache.poi.sl.usermodel.PictureData.PictureType;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.xslf.usermodel.XMLSlideShow;
 import org.apache.poi.xslf.usermodel.XSLFPictureData;
@@ -209,7 +210,7 @@ class ImageExtractor {
      *         extracted.
      */
     private List extractImagesFromDoc(AbstractFile af) {
-        List listOfAllPictures;
+        List listOfAllPictures;
         
         try {
             HWPFDocument doc = new HWPFDocument(new ReadContentInputStream(af));
@@ -241,7 +242,7 @@ class ImageExtractor {
         }
         List listOfExtractedImages = new ArrayList<>();
         byte[] data = null;
-        for (org.apache.poi.hwpf.usermodel.Picture picture : listOfAllPictures) {
+        for (Picture picture : listOfAllPictures) {
             String fileName = picture.suggestFullFileName();
             try {
                 data = picture.getContent();
@@ -319,10 +320,10 @@ class ImageExtractor {
      *         extracted.
      */
     private List extractImagesFromPpt(AbstractFile af) {
-        PictureData[] listOfAllPictures = null;
+        List listOfAllPictures = null;
         
         try {
-            SlideShow ppt = new SlideShow(new ReadContentInputStream(af));
+            HSLFSlideShow ppt = new HSLFSlideShow(new ReadContentInputStream(af));
             listOfAllPictures = ppt.getPictureData();
         } catch (OldFileFormatException | IOException ex) {
             // OldFileFormatException:
@@ -341,7 +342,7 @@ class ImageExtractor {
         // if no images are extracted from the PPT, return null, else initialize
         // the output folder for image extraction.
         String outputFolderPath;
-        if (listOfAllPictures.length == 0) {
+        if (listOfAllPictures.isEmpty()) {
             return null;
         } else {
             outputFolderPath = getOutputFolderPath(this.parentFileName);
@@ -355,26 +356,26 @@ class ImageExtractor {
         int i = 0;
         List listOfExtractedImages = new ArrayList<>();
         byte[] data = null;
-        for (PictureData pictureData : listOfAllPictures) {
+        for (HSLFPictureData pictureData : listOfAllPictures) {
 
             // Get image extension, generate image name, write image to the module
             // output folder, add it to the listOfExtractedImageAbstractFiles
-            int type = pictureData.getType();
+            PictureType type = pictureData.getType();
             String ext;
             switch (type) {
-                case Picture.JPEG:
+                case JPEG:
                     ext = ".jpg"; //NON-NLS
                     break;
-                case Picture.PNG:
+                case PNG:
                     ext = ".png"; //NON-NLS
                     break;
-                case Picture.WMF:
+                case WMF:
                     ext = ".wmf"; //NON-NLS
                     break;
-                case Picture.EMF:
+                case EMF:
                     ext = ".emf"; //NON-NLS
                     break;
-                case Picture.PICT:
+                case PICT:
                     ext = ".pict"; //NON-NLS
                     break;
                 default:
@@ -406,7 +407,7 @@ class ImageExtractor {
         
         try {
             XMLSlideShow pptx = new XMLSlideShow(new ReadContentInputStream(af));
-            listOfAllPictures = pptx.getAllPictures();
+            listOfAllPictures = pptx.getPictureData();
         } catch (POIXMLException | IOException ex) {
             // POIXMLException:
             // Thrown when document fails to load.
diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java
index c1cffc9243..56de46f04d 100644
--- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java
+++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2013-2014 Basis Technology Corp.
+ * Copyright 2013-2017 Basis Technology Corp.
  * Contact: carrier  sleuthkit  org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -80,15 +80,15 @@ class ReportExcel implements TableReportModule {
         Font titleFont = wb.createFont();
         titleFont.setFontHeightInPoints((short) 12);
         titleStyle.setFont(titleFont);
-        titleStyle.setAlignment(CellStyle.ALIGN_LEFT);
+        titleStyle.setAlignment(HorizontalAlignment.LEFT);
         titleStyle.setWrapText(true);
 
         setStyle = wb.createCellStyle();
         Font setFont = wb.createFont();
         setFont.setFontHeightInPoints((short) 14);
-        setFont.setBoldweight((short) 10);
+        setFont.setBold(true);
         setStyle.setFont(setFont);
-        setStyle.setAlignment(CellStyle.ALIGN_LEFT);
+        setStyle.setAlignment(HorizontalAlignment.LEFT);
         setStyle.setWrapText(true);
 
         elementStyle = wb.createCellStyle();
@@ -96,7 +96,7 @@ class ReportExcel implements TableReportModule {
         Font elementFont = wb.createFont();
         elementFont.setFontHeightInPoints((short) 14);
         elementStyle.setFont(elementFont);
-        elementStyle.setAlignment(CellStyle.ALIGN_LEFT);
+        elementStyle.setAlignment(HorizontalAlignment.LEFT);
         elementStyle.setWrapText(true);
 
         writeSummaryWorksheet();
diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml
index c0ca478735..8043007b52 100644
--- a/CoreLibs/ivy.xml
+++ b/CoreLibs/ivy.xml
@@ -28,8 +28,8 @@
         
               
         
-        
-        
+        
+        
         
         
         
diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties
index 3f1452877a..62c4d535e6 100644
--- a/CoreLibs/nbproject/project.properties
+++ b/CoreLibs/nbproject/project.properties
@@ -6,7 +6,8 @@ file.reference.avalon-framework-4.1.5.jar=release/modules/ext/avalon-framework-4
 file.reference.common-image-3.2.jar=release/modules/ext/common-image-3.2.jar
 file.reference.common-io-3.2.jar=release/modules/ext/common-io-3.2.jar
 file.reference.common-lang-3.2.jar=release/modules/ext/common-lang-3.2.jar
-file.reference.commons-codec-1.5.jar=release/modules/ext/commons-codec-1.5.jar
+file.reference.commons-codec-1.10.jar=release/modules/ext/commons-codec-1.10.jar
+file.reference.commons-collections4-4.1.jar=release/modules/ext/commons-collections4-4.1.jar
 file.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4.jar
 file.reference.commons-io-2.4.jar=release/modules/ext/commons-io-2.4.jar
 file.reference.commons-lang-2.6.jar=release/modules/ext/commons-lang-2.6.jar
@@ -56,11 +57,11 @@ file.reference.logkit-1.0.1.jar=release/modules/ext/logkit-1.0.1.jar
 file.reference.mail-1.4.3.jar=release/modules/ext/mail-1.4.3.jar
 file.reference.openjfx-dialogs-1.0.2.jar=release/modules/ext/openjfx-dialogs-1.0.3.jar
 file.reference.platform-3.4.0.jar=release/modules/ext/platform-3.4.0.jar
-file.reference.poi-3.8.jar=release/modules/ext/poi-3.8.jar
-file.reference.poi-excelant-3.8.jar=release/modules/ext/poi-excelant-3.8.jar
-file.reference.poi-ooxml-3.8.jar=release/modules/ext/poi-ooxml-3.8.jar
-file.reference.poi-ooxml-schemas-3.8.jar=release/modules/ext/poi-ooxml-schemas-3.8.jar
-file.reference.poi-scratchpad-3.8.jar=release/modules/ext/poi-scratchpad-3.8.jar
+file.reference.poi-3.15.jar=release/modules/ext/poi-3.15.jar
+file.reference.poi-excelant-3.15.jar=release/modules/ext/poi-excelant-3.15.jar
+file.reference.poi-ooxml-3.15.jar=release/modules/ext/poi-ooxml-3.15.jar
+file.reference.poi-ooxml-schemas-3.15.jar=release/modules/ext/poi-ooxml-schemas-3.15.jar
+file.reference.poi-scratchpad-3.15.jar=release/modules/ext/poi-scratchpad-3.15.jar
 file.reference.reflections-0.9.8.jar=release/modules/ext/reflections-0.9.8.jar
 file.reference.servlet-api-2.5.jar=release/modules/ext/servlet-api-2.5.jar
 file.reference.sigar-1.6.4-sources.jar=release/modules/ext/sigar-1.6.4-sources.jar
@@ -69,7 +70,7 @@ file.reference.slf4j-api-1.6.1.jar=release/modules/ext/slf4j-api-1.6.1.jar
 file.reference.slf4j-simple-1.6.1.jar=release/modules/ext/slf4j-simple-1.6.1.jar
 file.reference.stax-api-1.0.1.jar=release/modules/ext/stax-api-1.0.1.jar
 file.reference.xml-apis-1.0.b2.jar=release/modules/ext/xml-apis-1.0.b2.jar
-file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar
+file.reference.xmlbeans-2.6.0.jar=release/modules/ext/xmlbeans-2.6.0.jar
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
 javadoc.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4-javadoc.jar
diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml
index 358637d979..23a41edaf1 100644
--- a/CoreLibs/nbproject/project.xml
+++ b/CoreLibs/nbproject/project.xml
@@ -700,16 +700,16 @@
                 release/modules/ext/sigar-1.6.4.jar
             
             
-                ext/xmlbeans-2.3.0.jar
-                release/modules/ext/xmlbeans-2.3.0.jar
+                ext/xmlbeans-2.6.0.jar
+                release/modules/ext/xmlbeans-2.6.0.jar
             
             
                 ext/jna-3.4.0.jar
                 release/modules/ext/jna-3.4.0.jar
             
             
-                ext/poi-ooxml-schemas-3.8.jar
-                release/modules/ext/poi-ooxml-schemas-3.8.jar
+                ext/poi-ooxml-schemas-3.15.jar
+                release/modules/ext/poi-ooxml-schemas-3.15.jar
             
             
                 ext/gson-1.4.jar
@@ -768,8 +768,8 @@
                 release/modules/ext/jcalendarbutton-1.4.6.jar
             
             
-                ext/poi-ooxml-3.8.jar
-                release/modules/ext/poi-ooxml-3.8.jar
+                ext/poi-ooxml-3.15.jar
+                release/modules/ext/poi-ooxml-3.15.jar
             
             
                 ext/imageio-psd-3.2.jar
@@ -779,13 +779,17 @@
                 ext/stax-api-1.0.1.jar
                 release/modules/ext/stax-api-1.0.1.jar
             
+            
+                ext/commons-collections4-4.1.jar
+                release/modules/ext/commons-collections4-4.1.jar
+            
             
                 ext/servlet-api-2.5.jar
                 release/modules/ext/servlet-api-2.5.jar
             
             
-                ext/poi-excelant-3.8.jar
-                release/modules/ext/poi-excelant-3.8.jar
+                ext/poi-excelant-3.15.jar
+                release/modules/ext/poi-excelant-3.15.jar
             
             
                 ext/imageio-pcx-3.2.jar
@@ -824,8 +828,8 @@
                 release/modules/ext/geronimo-jms_1.1_spec-1.0.jar
             
             
-                ext/poi-scratchpad-3.8.jar
-                release/modules/ext/poi-scratchpad-3.8.jar
+                ext/poi-scratchpad-3.15.jar
+                release/modules/ext/poi-scratchpad-3.15.jar
             
             
                 ext/joda-time-2.4-sources.jar
@@ -876,8 +880,8 @@
                 release/modules/ext/ant-1.8.2.jar
             
             
-                ext/commons-codec-1.5.jar
-                release/modules/ext/commons-codec-1.5.jar
+                ext/commons-codec-1.10.jar
+                release/modules/ext/commons-codec-1.10.jar
             
             
                 ext/javassist-3.12.1.GA.jar
@@ -896,8 +900,8 @@
                 release/modules/ext/commons-io-2.4.jar
             
             
-                ext/poi-3.8.jar
-                release/modules/ext/poi-3.8.jar
+                ext/poi-3.15.jar
+                release/modules/ext/poi-3.15.jar
             
             
                 ext/controlsfx-8.40.11.jar
diff --git a/Experimental/ivy.xml b/Experimental/ivy.xml
index 5934399ba3..658e7e5948 100644
--- a/Experimental/ivy.xml
+++ b/Experimental/ivy.xml
@@ -7,7 +7,7 @@
     
     
         
-        
+        
         
         
         
diff --git a/Experimental/nbproject/project.properties b/Experimental/nbproject/project.properties
index 34d831c12a..d47542bdb5 100644
--- a/Experimental/nbproject/project.properties
+++ b/Experimental/nbproject/project.properties
@@ -3,7 +3,7 @@ file.reference.jackson-core-2.7.0.jar=release/modules/ext/jackson-core-2.7.0.jar
 file.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1.jar
 file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar
 file.reference.postgresql-9.4-1201-jdbc41.jar=release/modules/ext/postgresql-9.4-1201-jdbc41.jar
-file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar
+file.reference.tika-core-1.14.jar=release/modules/ext/tika-core-1.14.jar
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
 javadoc.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1-javadoc.jar
diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml
index 714d6e4028..f0e1b77527 100644
--- a/Experimental/nbproject/project.xml
+++ b/Experimental/nbproject/project.xml
@@ -132,8 +132,8 @@
                 release/modules/ext/LGoodDatePicker-4.3.1.jar
             
             
-                ext/tika-core-1.5.jar
-                release/modules/ext/tika-core-1.5.jar
+                ext/tika-core-1.14.jar
+                release/modules/ext/tika-core-1.14.jar
             
             
                 ext/jackson-core-2.7.0.jar

From fcbea3b223219ed28cb4cb88d36f2efce26158d0 Mon Sep 17 00:00:00 2001
From: Raman 
Date: Thu, 4 May 2017 15:56:40 -0400
Subject: [PATCH 34/57] 2536: Review ChildFactory uses for long-running tasks

Fixed the InterestingHits nodes not showing up in the table sporadically.
---
 .../src/org/sleuthkit/autopsy/datamodel/HashsetHits.java | 2 +-
 .../org/sleuthkit/autopsy/datamodel/InterestingHits.java | 5 ++---
 .../src/org/sleuthkit/autopsy/datamodel/KeywordHits.java | 9 ++++-----
 .../autopsy/timeline/explorernodes/EventRootNode.java    | 4 ++--
 4 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
index d07a0c3ba3..2665894481 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
@@ -382,8 +382,8 @@ public class HashsetHits implements AutopsyVisitableItem {
                     if (!artifactHits.containsKey(id)) {
                         BlackboardArtifact art = skCase.getBlackboardArtifact(id);
                         artifactHits.put(id, art);
-                        list.add(id);
                     }
+                    list.add(id);
                 } catch (TskException ex) {
                     logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
                 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
index f089727da4..748ab48da9 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
@@ -356,10 +356,9 @@ public class InterestingHits implements AutopsyVisitableItem {
                 try {
                     if (!artifactHits.containsKey(id)) {
                         BlackboardArtifact art = skCase.getBlackboardArtifact(id);
-
-                        artifactHits.put(id, art);
-                        list.add(id);
+                        artifactHits.put(id, art);  
                     }
+                    list.add(id);
                 } catch (TskCoreException ex) {
                     logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
                 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
index 19fb871e1d..ccc2b3c9e7 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
@@ -708,18 +708,17 @@ public class KeywordHits implements AutopsyVisitableItem {
                 for (Long id : keywordResults.getArtifactIds(setName, keyword, DEFAULT_INSTANCE_NAME) ) {
                     RegExpInstanceKey key = new RegExpInstanceKey(id);
                     if (!nodesMap.containsKey(key)) {
-                        nodesMap.put(key, createNode(key));
-                        list.add(key);
+                        nodesMap.put(key, createNode(key));  
                     }
-                    
+                    list.add(key);
                 }
             } else {
                 for (String instance : instances) {
                     RegExpInstanceKey key = new RegExpInstanceKey(instance);
                     if (!nodesMap.containsKey(key)) {
                         nodesMap.put(key, createNode(key));
-                        list.add(key);
                     }
+                    list.add(key);
                 }
                 
             }
@@ -904,8 +903,8 @@ public class KeywordHits implements AutopsyVisitableItem {
             for (Long id : keywordResults.getArtifactIds(setName, keyword, instance) ) {
                 if (!nodesMap.containsKey(id)) {
                     nodesMap.put(id,  createBlackboardArtifactNode(id));
-                    list.add(id);
                 }
+                list.add(id); 
             }
             return true;
         }
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java
index c699c6cac8..1b35d90188 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java
@@ -102,14 +102,14 @@ public class EventRootNode extends DisplayableItemNode {
                 for (Long eventId: eventIDs){
                     if (!nodesMap.containsKey(eventId)) {
                         nodesMap.put(eventId, createNode(eventId));
-                        toPopulate.add(eventId);
                     }
+                    toPopulate.add(eventId);
                 }
             } else {
                 if (!nodesMap.containsKey(-1L)) {
                     nodesMap.put(-1L, createNode(-1L));
-                    toPopulate.add(-1L);
                 }
+                toPopulate.add(-1L);
             }
             return true;
         }

From 297308783aa0f8a1e6a63e6ece57da7d17199341 Mon Sep 17 00:00:00 2001
From: "U-BASIS\\dgrove" 
Date: Thu, 4 May 2017 16:02:45 -0400
Subject: [PATCH 35/57] Minor fix.

---
 Core/nbproject/project.xml | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index eb4739d209..ea75c68ccf 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -243,7 +243,6 @@
                 net.sf.sevenzipjbinding.simple
                 net.sf.sevenzipjbinding.simple.impl
                 org.sleuthkit.autopsy.actions
-                org.sleuthkit.autopsy.appservices
                 org.sleuthkit.autopsy.casemodule
                 org.sleuthkit.autopsy.casemodule.events
                 org.sleuthkit.autopsy.casemodule.services
@@ -255,18 +254,17 @@
                 org.sleuthkit.autopsy.corecomponents
                 org.sleuthkit.autopsy.coreutils
                 org.sleuthkit.autopsy.datamodel
-                org.sleuthkit.autopsy.datasourceprocessors
                 org.sleuthkit.autopsy.directorytree
                 org.sleuthkit.autopsy.events
                 org.sleuthkit.autopsy.externalresults
                 org.sleuthkit.autopsy.filesearch
+                org.sleuthkit.autopsy.framework
                 org.sleuthkit.autopsy.ingest
                 org.sleuthkit.autopsy.keywordsearchservice
                 org.sleuthkit.autopsy.menuactions
                 org.sleuthkit.autopsy.modules.filetypeid
                 org.sleuthkit.autopsy.modules.hashdatabase
                 org.sleuthkit.autopsy.modules.vmextractor
-                org.sleuthkit.autopsy.progress
                 org.sleuthkit.autopsy.report
                 org.sleuthkit.datamodel
             

From d8ca0a227f1dcc0ed3f90090429eb4059f549555 Mon Sep 17 00:00:00 2001
From: millmanorama 
Date: Fri, 5 May 2017 14:37:33 +0200
Subject: [PATCH 36/57] disable the IG button if we are running without GUI

---
 .../org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java
index 661a895db9..71ff3681a5 100755
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java
@@ -35,6 +35,7 @@ import org.openide.util.actions.Presenter;
 import org.openide.windows.WindowManager;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.core.Installer;
+import org.sleuthkit.autopsy.core.RuntimeProperties;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
 import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
@@ -64,7 +65,7 @@ public final class OpenAction extends CallableSystemAction implements Presenter.
         toolbarButton.addActionListener(actionEvent -> performAction());
         pcl = (PropertyChangeEvent evt) -> {
             if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) {
-                 setEnabled(evt.getNewValue() != null);
+                 setEnabled(RuntimeProperties.runningWithGUI() && evt.getNewValue() != null);
             }
         };
         Case.addPropertyChangeListener(pcl);

From bd9555c2dbcbb0752cd5991dc0eaeb28d891e311 Mon Sep 17 00:00:00 2001
From: William Schaefer 
Date: Fri, 5 May 2017 11:29:35 -0400
Subject: [PATCH 37/57] 2635-remove adding of shortcut text to remove bookmark
 tag

---
 .../actions/DeleteFileBlackboardArtifactTagAction.java      | 4 ----
 .../autopsy/actions/DeleteFileContentTagAction.java         | 6 ------
 2 files changed, 10 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java
index afb17804b7..d9fd5d364f 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java
@@ -174,10 +174,6 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem
                             for(BlackboardArtifactTag artifactTag : existingTagsList) {
                                 if(tagDisplayName.equals(artifactTag.getName().getDisplayName())) {
                                     JMenuItem tagNameItem = new JMenuItem(tagDisplayName);
-                                    // for the bookmark tag name only, added shortcut label
-                                    if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) {
-                                        tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT);
-                                    }
                                     tagNameItem.addActionListener((ActionEvent e) -> {
                                         deleteTag(tagName, artifactTag, artifact.getArtifactID());
                                     });
diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java
index 5cc37e3900..e49e0f9170 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java
@@ -159,8 +159,6 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
                 // a tag with the associated tag name.
                 if (null != tagNamesMap && !tagNamesMap.isEmpty()) {
                     try {
-                        /*List existingTagsList =
-                                Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact);*/
                         List existingTagsList =
                                 Case.getCurrentCase().getServices().getTagsManager()
                                         .getContentTagsByContent(file);
@@ -172,10 +170,6 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
                             for(ContentTag contentTag : existingTagsList) {
                                 if(tagDisplayName.equals(contentTag.getName().getDisplayName())) {
                                     JMenuItem tagNameItem = new JMenuItem(tagDisplayName);
-                                    // for the bookmark tag name only, added shortcut label
-                                    if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) {
-                                        tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT);
-                                    }
                                     tagNameItem.addActionListener((ActionEvent e) -> {
                                         deleteTag(tagName, contentTag, file.getId());
                                     });

From 5c53357bab4adb2ea615afc7b7be23424af9e9a1 Mon Sep 17 00:00:00 2001
From: "U-BASIS\\dgrove" 
Date: Fri, 5 May 2017 14:24:18 -0400
Subject: [PATCH 38/57] Added additional exception catches.

---
 .../embeddedfileextractor/ImageExtractor.java | 69 +++++++++++++++----
 1 file changed, 55 insertions(+), 14 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java
index 335bdc0a91..d58d935e97 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ImageExtractor.java
@@ -21,20 +21,24 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.IllegalArgumentException;
+import java.lang.IndexOutOfBoundsException;
+import java.lang.NullPointerException;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.logging.Level;
-import org.apache.poi.OldFileFormatException;
 import org.apache.poi.POIXMLException;
 import org.apache.poi.hwpf.usermodel.Picture;
 import org.apache.poi.hslf.usermodel.HSLFPictureData;
 import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hssf.record.RecordInputStream.LeftoverDataException;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hwpf.HWPFDocument;
 import org.apache.poi.hwpf.model.PicturesTable;
 import org.apache.poi.sl.usermodel.PictureData.PictureType;
 import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.util.RecordFormatException;
 import org.apache.poi.xslf.usermodel.XMLSlideShow;
 import org.apache.poi.xslf.usermodel.XSLFPictureData;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@@ -216,14 +220,25 @@ class ImageExtractor {
             HWPFDocument doc = new HWPFDocument(new ReadContentInputStream(af));
             PicturesTable pictureTable = doc.getPicturesTable();
             listOfAllPictures = pictureTable.getAllPictures();
-        } catch (OldFileFormatException | IOException ex) {
-            // OldFileFormatException:
-            // Thrown when the document version is unsupported (Word 95 and
-            // older)
-            
+        } catch (IOException | IllegalArgumentException |
+                IndexOutOfBoundsException | NullPointerException ex) {
             // IOException:
             // Thrown when the document has issues being read.
             
+            // IllegalArgumentException:
+            // This will catch OldFileFormatException, which is thrown when the
+            // document's format is Word 95 or older. Alternatively, this is
+            // thrown when attempting to load an RTF file as a DOC file.
+            // However, our code verifies the file format before ever running it
+            // through the ImageExtractor. This exception gets thrown in the
+            // "IN10-0137.E01" image regardless. The reason is unknown.
+            
+            // IndexOutOfBoundsException:
+            // NullPointerException:
+            // These get thrown in certain images. The reason is unknown. It is
+            // likely due to problems with the file formats that POI is poorly
+            // handling.
+            
             return null;
         } catch (Throwable ex) {
             // instantiating POI containers throw RuntimeExceptions
@@ -325,12 +340,20 @@ class ImageExtractor {
         try {
             HSLFSlideShow ppt = new HSLFSlideShow(new ReadContentInputStream(af));
             listOfAllPictures = ppt.getPictureData();
-        } catch (OldFileFormatException | IOException ex) {
-            // OldFileFormatException:
-            // Thrown when the document version is unsupported
+        } catch (IOException | IllegalArgumentException |
+                IndexOutOfBoundsException ex) {
+            // IllegalArgumentException:
+            // This will catch OldFileFormatException, which is thrown when the
+            // document version is unsupported. The IllegalArgumentException may
+            // also get thrown for unknown reasons.
             
             // IOException:
-            // Thrown when the document has issues being read
+            // Thrown when the document has issues being read.
+            
+            // IndexOutOfBoundsException:
+            // This gets thrown in certain images. The reason is unknown. It is
+            // likely due to problems with the file formats that POI is poorly
+            // handling.
             
             return null;
         } catch (Throwable ex) {
@@ -469,12 +492,30 @@ class ImageExtractor {
         try {
             Workbook xls = new HSSFWorkbook(new ReadContentInputStream(af));
             listOfAllPictures = xls.getAllPictures();
-        } catch (OldFileFormatException | IOException ex) {
-            // OldFileFormatException:
-            // Thrown when the document version is unsupported
+        } catch (IOException | LeftoverDataException |
+                RecordFormatException | IllegalArgumentException |
+                IndexOutOfBoundsException ex) {
+            // IllegalArgumentException:
+            // This will catch OldFileFormatException, which is thrown when the
+            // document version is unsupported. The IllegalArgumentException may
+            // also get thrown for unknown reasons.
             
             // IOException:
-            // Thrown when the document has issues being read
+            // Thrown when the document has issues being read.
+            
+            // LeftoverDataException:
+            // This is thrown for poorly formatted files that have more data
+            // than expected.
+            
+            // RecordFormatException:
+            // This is thrown for poorly formatted files that have less data
+            // that expected.
+            
+            // IllegalArgumentException:
+            // IndexOutOfBoundsException:
+            // These get thrown in certain images. The reason is unknown. It is
+            // likely due to problems with the file formats that POI is poorly
+            // handling.
             
             return null;
         } catch (Throwable ex) {

From 6027915f0e5a2f5a67e834f997c95c7620f83261 Mon Sep 17 00:00:00 2001
From: "U-BASIS\\zhaohui" 
Date: Fri, 5 May 2017 17:22:55 -0400
Subject: [PATCH 39/57] 2640: Pass a full title name to WinzardOperator instead
 of a partial name

---
 .../sleuthkit/autopsy/testing/AutopsyTestCases.java  | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java b/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java
index d36c168a3e..b44b050e9b 100755
--- a/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java
+++ b/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java
@@ -107,7 +107,7 @@ public class AutopsyTestCases {
 
     public void testStartAddImageFileDataSource() {
         logger.info("Starting Add Image process");
-        WizardOperator wo = new WizardOperator("Add Data");
+        WizardOperator wo = new WizardOperator("Add Data Source");
         //select the toggle button for Disk Image or VM File it will be the first button created and proceed to next panel
         JToggleButtonOperator jtbo = new JToggleButtonOperator(wo, 0);
         jtbo.clickMouse();
@@ -123,7 +123,7 @@ public class AutopsyTestCases {
 
     public void testStartAddLogicalFilesDataSource() {
         logger.info("Starting Add Logical Files process");
-        WizardOperator wo = new WizardOperator("Add Data");
+        WizardOperator wo = new WizardOperator("Add Data Source");
         //select the toggle button for Logical Files it will be the third button created and proceed to next panel
         JToggleButtonOperator jtbo = new JToggleButtonOperator(wo, 2);
         jtbo.clickMouse();
@@ -139,7 +139,7 @@ public class AutopsyTestCases {
     }
 
     public void testAddSourceWizard1() {
-        WizardOperator wo = new WizardOperator("Add Data");
+        WizardOperator wo = new WizardOperator("Add Data Source");
         while (!wo.btFinish().isEnabled()) {
             new Timeout("pausing", 1000).sleep(); // give it a second (or five) to process
         }
@@ -155,7 +155,7 @@ public class AutopsyTestCases {
         new Timeout("pausing", 10000).sleep();
 
         logger.info("Looking for hash lookup module in ingest job settings panel");
-        WizardOperator wo = new WizardOperator("Add Data");
+        WizardOperator wo = new WizardOperator("Add Data Source");
         JTableOperator jto = new JTableOperator(wo, 0);
         int row = jto.findCellRow("Hash Lookup", 2, 0);
         jto.clickOnCell(row, 1);
@@ -201,7 +201,7 @@ public class AutopsyTestCases {
 
     public void testConfigureIngest2() {
         logger.info("Looking for keyword search module in ingest job settings panel");
-        WizardOperator wo = new WizardOperator("Add Data");
+        WizardOperator wo = new WizardOperator("Add Data Source");
         JTableOperator jto = new JTableOperator(wo, 0);
         int row = jto.findCellRow("Keyword Search", 2, 0);
         jto.clickOnCell(row, 1);
@@ -234,7 +234,7 @@ public class AutopsyTestCases {
         }
         JButtonOperator jbo2 = new JButtonOperator(jdo, "OK", 0);
         jbo2.pushNoBlock();
-        WizardOperator wo = new WizardOperator("Add Data");
+        WizardOperator wo = new WizardOperator("Add Data Source");
         new Timeout("pausing", 10000).sleep(); // let things catch up
         wo.btNext().clickMouse();
     }

From 3e84286444eec9fbc2d18fee9d72ffe5f1a6759f Mon Sep 17 00:00:00 2001
From: Richard Cordovano 
Date: Fri, 5 May 2017 18:00:57 -0400
Subject: [PATCH 40/57] Fix assorted docs to eliminate doxygen warnings

---
 .../sleuthkit/autopsy/casemodule/Case.java    |  4 +-
 .../corecomponents/DataResultViewerTable.java | 96 +++++++++++--------
 .../datamodel/FileTypesByMimeType.java        |  4 +-
 .../datasourceprocessors/RawDSProcessor.java  | 55 ++++++-----
 .../directorytree/DataResultFilterNode.java   | 67 +++++++------
 .../imagewriter/ImageWriterService.java       |  3 +-
 .../autopsy/ingest/IngestJobSettings.java     |  3 +
 .../autopsy/ingest/IngestOptionsPanel.java    | 15 ---
 .../ingest/IngestOptionsPanelController.java  | 27 ------
 .../ShortcutWizardDescriptorPanel.java        |  1 -
 .../interestingitems/FilesSetDefsPanel.java   |  9 --
 .../autopsy/keywordsearch/Ingester.java       | 10 +-
 .../keywordsearch/StringsTextExtractor.java   | 15 ++-
 .../autopsy/testing/AutopsyTestCases.java     |  8 +-
 14 files changed, 147 insertions(+), 170 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index e64a1e6341..bf9be4aa69 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -2019,8 +2019,6 @@ public class Case {
 
     /**
      * Closes the case.
-     *
-     * @param progressIndicator A progress indicator.
      */
     private void close() throws CaseActionException {
         /*
@@ -2655,7 +2653,7 @@ public class Case {
      * @param textIndexName The text index name.
      *
      * @throws CaseMetadataException
-     * @dprecated Do not use.
+     * @deprecated Do not use.
      */
     @Deprecated
     public void setTextIndexName(String textIndexName) throws CaseMetadataException {
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
index 8d7fbe43a7..9bedaeeee0 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
+ *
  * Copyright 2013-2016 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.
@@ -72,11 +72,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
     private static final long serialVersionUID = 1L;
 
     private final String firstColumnLabel = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.firstColLbl");
-    /* The properties map maps
-     * key: stored value of column index -> value: property at that index
-     * We move around stored values instead of directly using the column indices
-     * in order to not override settings for a column that may not appear in the
-     * current table view due to its collection of its children's properties.
+    /*
+     * The properties map maps key: stored value of column index -> value:
+     * property at that index We move around stored values instead of directly
+     * using the column indices in order to not override settings for a column
+     * that may not appear in the current table view due to its collection of
+     * its children's properties.
      */
     private final Map> propertiesMap = new TreeMap<>();
     private final PleasewaitNodeListener pleasewaitNodeListener = new PleasewaitNodeListener();
@@ -124,15 +125,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
             @Override
             public void columnAdded(TableColumnModelEvent e) {
             }
+
             @Override
             public void columnRemoved(TableColumnModelEvent e) {
             }
+
             @Override
             public void columnMarginChanged(ChangeEvent e) {
             }
+
             @Override
             public void columnSelectionChanged(ListSelectionEvent e) {
             }
+
             @Override
             public void columnMoved(TableColumnModelEvent e) {
                 int fromIndex = e.getFromIndex();
@@ -141,14 +146,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
                     return;
                 }
 
-                /* Because a column may be dragged to several different positions before
-                 * the mouse is released (thus causing multiple TableColumnModelEvents to
-                 * be fired), we want to keep track of the starting column index in this
-                 * potential series of movements. Therefore we only keep track of the
-                 * original fromIndex in startColumnIndex, but we always update
-                 * endColumnIndex to know the final position of the moved column.
-                 * See the MouseListener mouseReleased method.
-                */
+                /*
+                 * Because a column may be dragged to several different
+                 * positions before the mouse is released (thus causing multiple
+                 * TableColumnModelEvents to be fired), we want to keep track of
+                 * the starting column index in this potential series of
+                 * movements. Therefore we only keep track of the original
+                 * fromIndex in startColumnIndex, but we always update
+                 * endColumnIndex to know the final position of the moved
+                 * column. See the MouseListener mouseReleased method.
+                 */
                 if (startColumnIndex == -1) {
                     startColumnIndex = fromIndex;
                 }
@@ -175,8 +182,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
                         propertiesMap.put(range[i], propertiesMap.get(range[i + 1]));
                     }
                     propertiesMap.put(range[rangeSize - 1], movedProp);
-                }
-                // column moved left, shift all properties right, put in moved
+                } // column moved left, shift all properties right, put in moved
                 // property at the leftmost index
                 else {
                     Property movedProp = propertiesMap.get(range[rangeSize - 1]);
@@ -194,14 +200,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
         ov.getOutline().getTableHeader().addMouseListener(new MouseAdapter() {
             @Override
             public void mouseReleased(MouseEvent e) {
-                /* If the startColumnIndex is not -1 (which is the reset value), that
-                 * means columns have been moved around. We then check to see if either
-                 * the starting or end position is 0 (the first column), and then swap
-                 * them back if that is the case because we don't want to allow movement
-                 * of the first column. We then reset startColumnIndex to -1, the reset
-                 * value.
-                 * We check if startColumnIndex is at reset or not because it is
-                 * possible for the mouse to be released and a MouseEvent to be fired
+                /*
+                 * If the startColumnIndex is not -1 (which is the reset value),
+                 * that means columns have been moved around. We then check to
+                 * see if either the starting or end position is 0 (the first
+                 * column), and then swap them back if that is the case because
+                 * we don't want to allow movement of the first column. We then
+                 * reset startColumnIndex to -1, the reset value. We check if
+                 * startColumnIndex is at reset or not because it is possible
+                 * for the mouse to be released and a MouseEvent to be fired
                  * without having moved any columns.
                  */
                 if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
@@ -265,12 +272,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
 
     /**
      * Gets regular Bean property set properties from all children and,
-     * recursively, subchildren of Node. Note: won't work out the box for lazy
-     * load - you need to set all children props for the parent by hand
+     * recursively, subchildren, of a Node.
      *
-     * @param parent Node with at least one child to get properties from
-     * @param rows   max number of rows to retrieve properties for (can be used
-     *               for memory optimization)
+     * Note: won't work out the box for lazy load - you need to set all children
+     * properties for the parent by hand.
+     *
+     * @param parent        Node (with at least one child) from which toget
+     *                      properties.
+     * @param rows          Maximum number of rows to retrieve properties for
+     *                      (can be used for memory optimization).
+     * @param propertiesAcc Set in which to accumulate the properties.
      */
     private void getAllChildPropertyHeadersRec(Node parent, int rows, Set> propertiesAcc) {
         Children children = parent.getChildren();
@@ -304,10 +315,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
     @Override
     public void setNode(Node selectedNode) {
         final OutlineView ov = ((OutlineView) this.tableScrollPanel);
-        /* The quick filter must be reset because when determining column width,
+        /*
+         * The quick filter must be reset because when determining column width,
          * ETable.getRowCount is called, and the documentation states that quick
-         * filters must be unset for the method to work
-         * "If the quick-filter is applied the number of rows do not match the number of rows in the model."
+         * filters must be unset for the method to work "If the quick-filter is
+         * applied the number of rows do not match the number of rows in the
+         * model."
          */
         ov.getOutline().unsetQuickFilter();
         // change the cursor to "waiting cursor" for this operation
@@ -430,16 +443,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
         }
 
         /**
-         * This custom renderer extends the renderer that was already being
-         * used by the outline table. This renderer colors a row if the
-         * tags property of the node is not empty.
+         * This custom renderer extends the renderer that was already being used
+         * by the outline table. This renderer colors a row if the tags property
+         * of the node is not empty.
          */
         class ColorTagCustomRenderer extends DefaultOutlineCellRenderer {
+
             private static final long serialVersionUID = 1L;
+
             @Override
             public Component getTableCellRendererComponent(JTable table,
                     Object value, boolean isSelected, boolean hasFocus, int row, int col) {
-                
+
                 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
                 // only override the color if a node is not selected
                 if (!isSelected) {
@@ -555,7 +570,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
      *
      * @param prop Property of the column
      * @param type The type of the current node
-     * @return     A generated key for the preference file
+     *
+     * @return A generated key for the preference file
      */
     private String getColumnPreferenceKey(Property prop, String type) {
         return type.replaceAll("[^a-zA-Z0-9_]", "") + "."
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
index db191ac76c..1549d6bdc1 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
@@ -453,7 +453,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
          * @return query.toString - portion of SQL query which will follow a
          *         WHERE clause.
          */
-        private String createQuery(String mime_type) {
+        private String createQuery(String mimeType) {
             StringBuilder query = new StringBuilder();
             query.append("(dir_type = ").append(TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()).append(")"); //NON-NLS
             query.append(" AND (type IN (").append(TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal()).append(",");  //NON-NLS
@@ -466,7 +466,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
             if (UserPreferences.hideKnownFilesInViewsTree()) {
                 query.append(" AND (known IS NULL OR known != ").append(TskData.FileKnown.KNOWN.getFileKnownValue()).append(")"); //NON-NLS
             }
-            query.append(" AND mime_type = '").append(mime_type).append("'");  //NON-NLS
+            query.append(" AND mime_type = '").append(mimeType).append("'");  //NON-NLS
             return query.toString();
         }
 
diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.java
index 68908ae7a3..5b9edb5a83 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
+ *
  * Copyright 2011-2016 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.
@@ -28,26 +28,27 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
 
 /**
  * A Raw data source processor that implements the DataSourceProcessor service
- * provider interface to allow integration with the  add data source wizard.
- * It also provides a run method overload to allow it to be used independently
- * of the wizard.
+ * provider interface to allow integration with the add data source wizard. It
+ * also provides a run method overload to allow it to be used independently of
+ * the wizard.
  */
 @ServiceProvider(service = DataSourceProcessor.class)
 public class RawDSProcessor implements DataSourceProcessor {
+
     private final RawDSInputPanel configPanel;
     private AddRawImageTask addImageTask;
 
     /*
      * Constructs a Raw data source processor that implements the
-     * DataSourceProcessor service provider interface to allow integration
-     * with the add data source wizard. It also provides a run method
-     * overload to allow it to be used independently of the wizard.
+     * DataSourceProcessor service provider interface to allow integration with
+     * the add data source wizard. It also provides a run method overload to
+     * allow it to be used independently of the wizard.
      */
     public RawDSProcessor() {
         configPanel = RawDSInputPanel.createInstance(RawDSProcessor.class.getName());
     }
 
-/**
+    /**
      * Gets a string that describes the type of data sources this processor is
      * able to add to the case database. The string is suitable for display in a
      * type selection UI component (e.g., a combo box).
@@ -68,7 +69,7 @@ public class RawDSProcessor implements DataSourceProcessor {
      */
     @Override
     public String getDataSourceType() {
-        return  Bundle.RawDSProcessor_dataSourceType();
+        return Bundle.RawDSProcessor_dataSourceType();
     }
 
     /**
@@ -115,32 +116,36 @@ public class RawDSProcessor implements DataSourceProcessor {
     @Override
     public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
         configPanel.storeSettings();
-        run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getTimeZone(), configPanel.getChunkSize(), progressMonitor, callback);       
+        run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getTimeZone(), configPanel.getChunkSize(), progressMonitor, callback);
     }
 
     /**
-     * Adds a data source to the case database using a background task in a
-     * separate thread and the given settings instead of those provided by the
+     * Adds a "raw" data source to the case database using a background task in
+     * a separate thread and the given settings instead of those provided by the
      * selection and configuration panel. Returns as soon as the background task
      * is started and uses the callback object to signal task completion and
      * return results.
      *
-     * @param deviceId                 An ASCII-printable identifier for the
-     *                                 device associated with the data source
-     *                                 that is intended to be unique across
-     *                                 multiple cases (e.g., a UUID).
-     * @param rawDSInputFilePath       Path to a Raw data source file.
-     * @param isHandsetFile            Indicates whether the XML file is for a
-     *                                 handset or a SIM.
-     * @param progressMonitor          Progress monitor for reporting progress
-     *                                 during processing.
+     * @param deviceId             An ASCII-printable identifier for the device
+     *                             associated with the data source that is
+     *                             intended to be unique across multiple cases
+     *                             (e.g., a UUID).
+     * @param imageFilePath        Path to the image file.
+     * @param timeZone             The time zone to use when processing dates
+     *                             and times for the image, obtained from
+     *                             java.util.TimeZone.getID.
+     * @param chunkSize            The maximum size of each chunk of the raw
+     *                             data source as it is divided up into virtual
+     *                             unallocated space files.
+     * @param progressMonitor      Progress monitor for reporting progress
+     *                             during processing.
+     * @param callback             Callback to call when processing is done.
      */
     private void run(String deviceId, String imageFilePath, String timeZone, long chunkSize, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
         addImageTask = new AddRawImageTask(deviceId, imageFilePath, timeZone, chunkSize, progressMonitor, callback);
         new Thread(addImageTask).start();
     }
 
-   
     @Override
     public void cancel() {
     }
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index 58cba4102f..0d853e09cc 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -71,11 +71,12 @@ import org.sleuthkit.datamodel.TskException;
 import org.sleuthkit.datamodel.VirtualDirectory;
 
 /**
- * This class wraps nodes as they are passed to the DataResult viewers. It
- * defines the actions that the node should have.
+ * A node used to wrap another node before passing it to the result viewers. The
+ * wrapper node defines the actions associated with the wrapped node and may
+ * filter out some of its children.
  */
 public class DataResultFilterNode extends FilterNode {
-    
+
     private static final Logger LOGGER = Logger.getLogger(DataResultFilterNode.class.getName());
 
     private static boolean filterKnownFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
@@ -111,22 +112,33 @@ public class DataResultFilterNode extends FilterNode {
     private final ExplorerManager sourceEm;
 
     /**
+     * Constructs a node used to wrap another node before passing it to the
+     * result viewers. The wrapper node defines the actions associated with the
+     * wrapped node and may filter out some of its children.
      *
-     * @param node Root node to be passed to DataResult viewers
-     * @param em   ExplorerManager for component that is creating the node
+     * @param node The node to wrap.
+     * @param em   The ExplorerManager for the component that is creating the
+     *             node.
      */
     public DataResultFilterNode(Node node, ExplorerManager em) {
         super(node, new DataResultFilterChildren(node, em));
         this.sourceEm = em;
-
     }
 
     /**
+     * Constructs a node used to wrap another node before passing it to the
+     * result viewers. The wrapper node defines the actions associated with the
+     * wrapped node and may filter out some of its children.
      *
-     * @param node Root node to be passed to DataResult viewers
-     * @param em   ExplorerManager for component that is creating the node
+     * @param node        The node to wrap.
+     * @param em          The ExplorerManager for the component that is creating
+     *                    the node.
+     * @param filterKnown Whether or not to filter out children that represent
+     *                    known files.
+     * @param filterSlack Whether or not to filter out children that represent
+     *                    virtual slack space files.
      */
-  private  DataResultFilterNode(Node node, ExplorerManager em, boolean filterKnown, boolean filterSlack) {
+    private DataResultFilterNode(Node node, ExplorerManager em, boolean filterKnown, boolean filterSlack) {
         super(node, new DataResultFilterChildren(node, em, filterKnown, filterSlack));
         this.sourceEm = em;
     }
@@ -201,9 +213,9 @@ public class DataResultFilterNode extends FilterNode {
      * DataResultFilterNode that created in the DataResultFilterNode.java.
      *
      */
-  private  static class DataResultFilterChildren extends FilterNode.Children {
+    private static class DataResultFilterChildren extends FilterNode.Children {
 
-        private final  ExplorerManager sourceEm;
+        private final ExplorerManager sourceEm;
 
         private boolean filterKnown;
         private boolean filterSlack;
@@ -211,7 +223,7 @@ public class DataResultFilterNode extends FilterNode {
         /**
          * the constructor
          */
-     private   DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
+        private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
             super(arg);
             switch (SelectionContext.getSelectionContext(arg)) {
                 case DATA_SOURCES:
@@ -230,7 +242,7 @@ public class DataResultFilterNode extends FilterNode {
             this.sourceEm = sourceEm;
         }
 
-     private   DataResultFilterChildren(Node arg, ExplorerManager sourceEm, boolean filterKnown, boolean filterSlack) {
+        private DataResultFilterChildren(Node arg, ExplorerManager sourceEm, boolean filterKnown, boolean filterSlack) {
             super(arg);
             this.filterKnown = filterKnown;
             this.filterSlack = filterSlack;
@@ -325,10 +337,10 @@ public class DataResultFilterNode extends FilterNode {
                 actionsList.add(null); // creates a menu separator
                 actionsList.add(AddContentTagAction.getInstance());
                 actionsList.add(AddBlackboardArtifactTagAction.getInstance());
-                
-                final Collection selectedFilesList =
-                        new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
-                if(selectedFilesList.size() == 1) {
+
+                final Collection selectedFilesList
+                        = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
+                if (selectedFilesList.size() == 1) {
                     actionsList.add(DeleteFileContentTagAction.getInstance());
                 }
             } else {
@@ -337,17 +349,17 @@ public class DataResultFilterNode extends FilterNode {
                 actionsList.add(null);
                 actionsList.add(AddBlackboardArtifactTagAction.getInstance());
             }
-            
-            final Collection selectedArtifactsList =
-                    new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
-            if(selectedArtifactsList.size() == 1) {
+
+            final Collection selectedArtifactsList
+                    = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
+            if (selectedArtifactsList.size() == 1) {
                 actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance());
             }
-            
-            if(n != null) {
+
+            if (n != null) {
                 actionsList.addAll(ContextMenuExtensionPoint.getActions());
             }
-            
+
             return actionsList;
         }
 
@@ -361,8 +373,7 @@ public class DataResultFilterNode extends FilterNode {
         public List visit(FileTypesNode fileTypes) {
             return defaultVisit(fileTypes);
         }
-        
-        
+
         @Override
         protected List defaultVisit(DisplayableItemNode ditem) {
             //preserve the default node's actions
@@ -454,14 +465,12 @@ public class DataResultFilterNode extends FilterNode {
         protected AbstractAction defaultVisit(DisplayableItemNode c) {
             return openChild(c);
         }
-        
+
         @Override
         public AbstractAction visit(FileTypesNode fileTypes) {
             return openChild(fileTypes);
         }
-        
 
-        
         /**
          * Tell the originating ExplorerManager to display the given
          * dataModelNode.
diff --git a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java
index a3853f0b31..8ee1fed9eb 100644
--- a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java
+++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java
@@ -42,7 +42,8 @@ public class ImageWriterService implements AutopsyService {
     /**
      * Create an image writer object for the given data source ID.
      *
-     * @param imageId ID for the image
+     * @param imageId ID for the image.
+     * @param settings Image writer settings to be used when writing the image.
      */
     public static void createImageWriter(Long imageId, ImageWriterSettings settings) {
 
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java
index 9fc5442371..ad93b012c3 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java
@@ -393,6 +393,9 @@ public class IngestJobSettings {
     /**
      * Gets the module names for a given key within these ingest job settings.
      *
+     * @param context        The identifier for the context for which to get the
+     *                       module names, e.g., the Add Data Source wizard or
+     *                       Run Ingest Modules context.
      * @param key            The key string.
      * @param defaultSetting The default list of module names.
      *
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java
index d10b987873..1c761e8578 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java
@@ -138,9 +138,6 @@ public class IngestOptionsPanel extends IngestModuleGlobalSettingsPanel implemen
 
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void addPropertyChangeListener(PropertyChangeListener l) {
         filterPanel.addPropertyChangeListener(l);
@@ -148,9 +145,6 @@ public class IngestOptionsPanel extends IngestModuleGlobalSettingsPanel implemen
         profilePanel.addPropertyChangeListener(l);
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void removePropertyChangeListener(PropertyChangeListener l) {
         filterPanel.removePropertyChangeListener(l);
@@ -158,9 +152,6 @@ public class IngestOptionsPanel extends IngestModuleGlobalSettingsPanel implemen
         profilePanel.removePropertyChangeListener(l);
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void saveSettings() {
         saveTabByIndex(tabbedPane.getSelectedIndex());
@@ -189,17 +180,11 @@ public class IngestOptionsPanel extends IngestModuleGlobalSettingsPanel implemen
         }
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void store() {
         saveSettings();
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void load() {
         filterPanel.load();
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanelController.java
index 9c908d9ebb..69c88d63c4 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanelController.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanelController.java
@@ -40,9 +40,6 @@ public class IngestOptionsPanelController extends OptionsPanelController {
     private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
     private boolean changed;
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void update() {
         getPanel().load();
@@ -69,9 +66,6 @@ public class IngestOptionsPanelController extends OptionsPanelController {
         return panel;
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void applyChanges() {
         if (changed) {
@@ -85,57 +79,36 @@ public class IngestOptionsPanelController extends OptionsPanelController {
         }
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void cancel() {
         getPanel().cancel();
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public boolean isValid() {
         return getPanel().valid();
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public boolean isChanged() {
         return changed;
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public JComponent getComponent(Lookup lkp) {
         return getPanel();
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public HelpCtx getHelpCtx() {
         return null;
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void addPropertyChangeListener(PropertyChangeListener pl) {
         pcs.addPropertyChangeListener(pl);
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void removePropertyChangeListener(PropertyChangeListener pl) {
         pcs.removePropertyChangeListener(pl);
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/ShortcutWizardDescriptorPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/ShortcutWizardDescriptorPanel.java
index 589ebdee14..ddbe9cf81d 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/ShortcutWizardDescriptorPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/ShortcutWizardDescriptorPanel.java
@@ -43,7 +43,6 @@ public abstract class ShortcutWizardDescriptorPanel implements WizardDescriptor.
 
     /**
      * Whether or not the panel immediately following this one should be skipped
-     * .
      *
      * @return true or false
      */
diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java
index 1741f43064..bdf45017d0 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java
@@ -169,9 +169,6 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
         this.equalitySignComboBox.setSelectedIndex(2);
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void saveSettings() {
         try {
@@ -203,17 +200,11 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
         ingestWarningLabel.setVisible(!isEnabled);
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void store() {
         this.saveSettings();
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void load() {
         this.resetComponents();
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
index ad9e151e9e..0ec43d4b3e 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
@@ -315,16 +315,16 @@ class Ingester {
          *
          * @return The field map of fields that are common to all file classes.
          */
-        private Map getCommonFields(AbstractFile af) {
+        private Map getCommonFields(AbstractFile file) {
             Map params = new HashMap<>();
-            params.put(Server.Schema.ID.toString(), Long.toString(af.getId()));
+            params.put(Server.Schema.ID.toString(), Long.toString(file.getId()));
             try {
-                params.put(Server.Schema.IMAGE_ID.toString(), Long.toString(af.getDataSource().getId()));
+                params.put(Server.Schema.IMAGE_ID.toString(), Long.toString(file.getDataSource().getId()));
             } catch (TskCoreException ex) {
-                logger.log(Level.SEVERE, "Could not get data source id to properly index the file " + af.getId(), ex); //NON-NLS
+                logger.log(Level.SEVERE, "Could not get data source id to properly index the file " + file.getId(), ex); //NON-NLS
                 params.put(Server.Schema.IMAGE_ID.toString(), Long.toString(-1));
             }
-            params.put(Server.Schema.FILE_NAME.toString(), af.getName());
+            params.put(Server.Schema.FILE_NAME.toString(), file.getName());
             return params;
         }
 
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java
index 940c5d0f76..4ccc8d76c5 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java
@@ -47,7 +47,7 @@ class StringsTextExtractor extends FileTextExtractor {
         EXTRACT_UTF16, ///< extract UTF16 text, true/false
         EXTRACT_UTF8, ///< extract UTF8 text, true/false
     };
-    
+
     private final List