From 5d63ab2bb8c76a37afbb5a7ec216c24b361aedb2 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 29 May 2019 12:41:36 -0400 Subject: [PATCH 01/24] 4963: offload computation of S C & O columns to a background task, in order to speed up the loading of Data Results View table. --- .../datamodel/AbstractAbstractFileNode.java | 44 +++++++----- .../autopsy/datamodel/GetSCOTask.java | 71 +++++++++++++++++++ .../sleuthkit/autopsy/datamodel/SCOData.java | 56 +++++++++++++++ 3 files changed, 155 insertions(+), 16 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index fa4529e44b..d6b589f977 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -82,7 +82,8 @@ public abstract class AbstractAbstractFileNode extends A private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED); - private static final ExecutorService translationPool; + // pool to run long running translation and getSCO tasks in backgound + private static final ExecutorService translationSCOPool; private static final Integer MAX_POOL_SIZE = 10; /** @@ -101,7 +102,7 @@ public abstract class AbstractAbstractFileNode extends A } if (UserPreferences.displayTranslatedFileNames()) { - AbstractAbstractFileNode.translationPool.submit(new TranslationTask( + AbstractAbstractFileNode.translationSCOPool.submit(new TranslationTask( new WeakReference<>(this), weakPcl)); } @@ -113,8 +114,8 @@ public abstract class AbstractAbstractFileNode extends A static { //Initialize this pool only once! This will be used by every instance of AAFN //to do their heavy duty SCO column and translation updates. - translationPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, - new ThreadFactoryBuilder().setNameFormat("translation-task-thread-%d").build()); + translationSCOPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, + new ThreadFactoryBuilder().setNameFormat("translation-and-sco-task-thread-%d").build()); } /** @@ -145,6 +146,7 @@ public abstract class AbstractAbstractFileNode extends A */ enum NodeSpecificEvents { TRANSLATION_AVAILABLE, + SCO_AVAILABLE } private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { @@ -223,6 +225,18 @@ public abstract class AbstractAbstractFileNode extends A //Set the tooltip this.setShortDescription(content.getName()); updateSheet(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, content.getName())); + } else if (eventType.equals(NodeSpecificEvents.SCO_AVAILABLE.toString())) { + SCOData scoData = (SCOData)evt.getNewValue(); + if (scoData.getScoreAndDescription() != null) { + updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft())); + } + if (scoData.getComment() != null) { + updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, scoData.getComment())); + } + if (scoData.getCountAndDescription() != null && + !UserPreferences.hideCentralRepoCommentsAndOccurrences()) { + updateSheet(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft())); + } } }; /** @@ -368,18 +382,16 @@ public abstract class AbstractAbstractFileNode extends A properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, "")); } - //SCO column prereq info.. - List tags = getContentTagsFromDatabase(); - CorrelationAttributeInstance attribute = getCorrelationAttributeInstance(); - - Pair scoreAndDescription = getScorePropertyAndDescription(tags); - properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoreAndDescription.getRight(), scoreAndDescription.getLeft())); - DataResultViewerTable.HasCommentStatus comment = getCommentProperty(tags, attribute); - properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, comment)); - if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) { - Pair countAndDescription = getCountPropertyAndDescription(attribute); - properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), countAndDescription.getRight(), countAndDescription.getLeft())); - } + // Create place holders for S C O + properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), NO_DESCR, "")); + properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, "")); + properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), NO_DESCR, "")); + + + // Get the SCO columns data in a background task + AbstractAbstractFileNode.translationSCOPool.submit(new GetSCOTask( + new WeakReference<>(this), weakPcl)); + properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content))); properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getMtime(), content))); properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getCtime(), content))); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java new file mode 100644 index 0000000000..e8a7b5b1f5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java @@ -0,0 +1,71 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; +import java.util.List; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.events.AutopsyEvent; +import org.sleuthkit.datamodel.ContentTag; + +/** + * Background task to get Score, Comment and Occurrences values for a Abstract file node. + * + */ +class GetSCOTask implements Runnable { + + private final WeakReference> weakNodeRef; + private final PropertyChangeListener listener; + + public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { + this.weakNodeRef = weakContentRef; + this.listener = listener; + } + + @Override + public void run() { + AbstractAbstractFileNode fileNode = weakNodeRef.get(); + + //Check for stale reference + if (fileNode == null) { + return; + } + + // get the SCO column values + List tags = fileNode.getContentTagsFromDatabase(); + CorrelationAttributeInstance attribute = fileNode.getCorrelationAttributeInstance(); + + SCOData scoData = new SCOData(); + scoData.setScoreAndDescription(fileNode.getScorePropertyAndDescription(tags)); + scoData.setComment(fileNode.getCommentProperty(tags, attribute)); + if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) { + scoData.setCountAndDescription(fileNode.getCountPropertyAndDescription(attribute)); + } + + if (listener != null) { + listener.propertyChange(new PropertyChangeEvent( + AutopsyEvent.SourceType.LOCAL.toString(), + AbstractAbstractFileNode.NodeSpecificEvents.SCO_AVAILABLE.toString(), + null, scoData)); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java b/Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java new file mode 100644 index 0000000000..a9dd99369d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java @@ -0,0 +1,56 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import org.apache.commons.lang3.tuple.Pair; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; + +/** + * Container to bag the S C & O data for an abstract file node. + * + */ +class SCOData { + + private Pair scoreAndDescription = null; + private DataResultViewerTable.HasCommentStatus comment = null; + private Pair countAndDescription = null; + + Pair getScoreAndDescription() { + return scoreAndDescription; + } + + DataResultViewerTable.HasCommentStatus getComment() { + return comment; + } + + Pair getCountAndDescription() { + return countAndDescription; + } + + void setScoreAndDescription(Pair scoreAndDescription) { + this.scoreAndDescription = scoreAndDescription; + } + void setComment(DataResultViewerTable.HasCommentStatus comment) { + this.comment = comment; + } + void setCountAndDescription(Pair countAndDescription) { + this.countAndDescription = countAndDescription; + } + +} From 2cdf0dd66d6328934e61eb87b6e1647ddd9f37e6 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 3 Jun 2019 15:45:00 -0400 Subject: [PATCH 02/24] 4963: DataResultTable is slow to load. - load S C & O columns data in background for AbstractContentNode and all it's derived classes. --- .../relationships/MessageNode.java | 11 -- .../datamodel/AbstractAbstractFileNode.java | 94 +++--------- .../datamodel/AbstractContentNode.java | 145 ++++++++++++++++++ .../datamodel/BlackboardArtifactNode.java | 84 ++++++---- .../datamodel/Bundle.properties-MERGED | 1 + .../autopsy/datamodel/GetSCOTask.java | 24 +-- 6 files changed, 234 insertions(+), 125 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index e6ac3f507f..2f4ce1157a 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -81,17 +81,6 @@ final class MessageNode extends BlackboardArtifactNode { sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS - addScoreProperty(sheetSet, tags); - - CorrelationAttributeInstance correlationAttribute = null; - if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { - correlationAttribute = getCorrelationAttributeInstance(); - } - addCommentProperty(sheetSet, tags, correlationAttribute); - - if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { - addCountProperty(sheetSet, correlationAttribute); - } final BlackboardArtifact artifact = getArtifact(); BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index d6b589f977..e8d71caae7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.datamodel; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; @@ -27,8 +26,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.stream.Collectors; import org.apache.commons.io.FilenameUtils; @@ -65,6 +62,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -82,10 +80,6 @@ public abstract class AbstractAbstractFileNode extends A private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED); - // pool to run long running translation and getSCO tasks in backgound - private static final ExecutorService translationSCOPool; - private static final Integer MAX_POOL_SIZE = 10; - /** * @param abstractFile file to wrap */ @@ -102,7 +96,7 @@ public abstract class AbstractAbstractFileNode extends A } if (UserPreferences.displayTranslatedFileNames()) { - AbstractAbstractFileNode.translationSCOPool.submit(new TranslationTask( + backgroundTasksPool.submit(new TranslationTask( new WeakReference<>(this), weakPcl)); } @@ -110,14 +104,7 @@ public abstract class AbstractAbstractFileNode extends A // or when tags are added. Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); } - - static { - //Initialize this pool only once! This will be used by every instance of AAFN - //to do their heavy duty SCO column and translation updates. - translationSCOPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, - new ThreadFactoryBuilder().setNameFormat("translation-and-sco-task-thread-%d").build()); - } - + /** * The finalizer removes event listeners as the BlackboardArtifactNode is * being garbage collected. Yes, we know that finalizers are considered to @@ -138,17 +125,7 @@ public abstract class AbstractAbstractFileNode extends A Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); } - /** - * Event signals to indicate the background tasks have completed processing. - * Currently, we have one property task in the background: - * - * 1) Retreiving the translation of the file name - */ - enum NodeSpecificEvents { - TRANSLATION_AVAILABLE, - SCO_AVAILABLE - } - + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); @@ -192,7 +169,7 @@ public abstract class AbstractAbstractFileNode extends A } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) { ContentTagAddedEvent event = (ContentTagAddedEvent) evt; if (event.getAddedTag().getContent().equals(content)) { - List tags = getContentTagsFromDatabase(); + List tags = this.getAllTagsFromDatabase(); Pair scorePropAndDescr = getScorePropertyAndDescription(tags); Score value = scorePropAndDescr.getLeft(); String descr = scorePropAndDescr.getRight(); @@ -204,7 +181,7 @@ public abstract class AbstractAbstractFileNode extends A } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) { ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt; if (event.getDeletedTagInfo().getContentID() == content.getId()) { - List tags = getContentTagsFromDatabase(); + List tags = getAllTagsFromDatabase(); Pair scorePropAndDescr = getScorePropertyAndDescription(tags); Score value = scorePropAndDescr.getLeft(); String descr = scorePropAndDescr.getRight(); @@ -216,7 +193,7 @@ public abstract class AbstractAbstractFileNode extends A } else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) { CommentChangedEvent event = (CommentChangedEvent) evt; if (event.getContentID() == content.getId()) { - List tags = getContentTagsFromDatabase(); + List tags = getAllTagsFromDatabase(); CorrelationAttributeInstance attribute = getCorrelationAttributeInstance(); updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(tags, attribute))); } @@ -249,38 +226,6 @@ public abstract class AbstractAbstractFileNode extends A */ private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); - /** - * Updates the values of the properties in the current property sheet with - * the new properties being passed in. Only if that property exists in the - * current sheet will it be applied. That way, we allow for subclasses to - * add their own (or omit some!) properties and we will not accidentally - * disrupt their UI. - * - * Race condition if not synchronized. Only one update should be applied at - * a time. - * - * @param newProps New file property instances to be updated in the current - * sheet. - */ - private synchronized void updateSheet(NodeProperty... newProps) { - //Refresh ONLY those properties in the sheet currently. Subclasses may have - //only added a subset of our properties or their own props.s - Sheet visibleSheet = this.getSheet(); - Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES); - Property[] visibleProps = visibleSheetSet.getProperties(); - for (NodeProperty newProp : newProps) { - for (int i = 0; i < visibleProps.length; i++) { - if (visibleProps[i].getName().equals(newProp.getName())) { - visibleProps[i] = newProp; - } - } - } - visibleSheetSet.put(visibleProps); - visibleSheet.put(visibleSheetSet); - //setSheet() will notify Netbeans to update this node in the UI. - this.setSheet(visibleSheet); - } - /* * This is called when the node is first initialized. Any new updates or * changes happen by directly manipulating the sheet. That means we can fire @@ -389,7 +334,7 @@ public abstract class AbstractAbstractFileNode extends A // Get the SCO columns data in a background task - AbstractAbstractFileNode.translationSCOPool.submit(new GetSCOTask( + backgroundTasksPool.submit(new GetSCOTask( new WeakReference<>(this), weakPcl)); properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content))); @@ -453,7 +398,8 @@ public abstract class AbstractAbstractFileNode extends A "AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated", "# {0} - occuranceCount", "AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"}) - Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description(); try { @@ -480,7 +426,8 @@ public abstract class AbstractAbstractFileNode extends A "AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.", "AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.", "AbstractAbstractFileNode.createSheet.noScore.description=No score"}) - Pair getScorePropertyAndDescription(List tags) { + @Override + protected Pair getScorePropertyAndDescription(List tags) { DataResultViewerTable.Score score = DataResultViewerTable.Score.NO_SCORE; String description = Bundle.AbstractAbstractFileNode_createSheet_noScore_description(); if (content.getKnown() == TskData.FileKnown.BAD) { @@ -498,7 +445,7 @@ public abstract class AbstractAbstractFileNode extends A if (!tags.isEmpty() && (score == DataResultViewerTable.Score.NO_SCORE || score == DataResultViewerTable.Score.INTERESTING_SCORE)) { score = DataResultViewerTable.Score.INTERESTING_SCORE; description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description(); - for (ContentTag tag : tags) { + for (Tag tag : tags) { if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) { score = DataResultViewerTable.Score.NOTABLE_SCORE; description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description(); @@ -511,11 +458,12 @@ public abstract class AbstractAbstractFileNode extends A @NbBundle.Messages({ "AbstractAbstractFileNode.createSheet.comment.displayName=C"}) - HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + @Override + protected HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { DataResultViewerTable.HasCommentStatus status = !tags.isEmpty() ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT; - for (ContentTag tag : tags) { + for (Tag tag : tags) { if (!StringUtils.isBlank(tag.getComment())) { //if the tag is null or empty or contains just white space it will indicate there is not a comment status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT; @@ -583,7 +531,13 @@ public abstract class AbstractAbstractFileNode extends A return tags; } - CorrelationAttributeInstance getCorrelationAttributeInstance() { + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(getContentTagsFromDatabase()); + } + + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { CorrelationAttributeInstance attribute = null; if (EamDb.isEnabled() && !UserPreferences.hideCentralRepoCommentsAndOccurrences()) { attribute = EamArtifactUtil.getInstanceFromContent(content); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 4f6f2e5f47..7a2577e407 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -18,20 +18,30 @@ */ package org.sleuthkit.autopsy.datamodel; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.logging.Level; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Children; +import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskException; @@ -50,6 +60,34 @@ public abstract class AbstractContentNode extends ContentNode T content; private static final Logger logger = Logger.getLogger(AbstractContentNode.class.getName()); + /** + * A pool of background tasks to run any long computation needed to + * populate this node. + */ + static final ExecutorService backgroundTasksPool; + static final Integer MAX_POOL_SIZE = 10; + + @NbBundle.Messages("AbstractContentNode.nodescription=no description") + private static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); + + + /** + * Event signals to indicate the background tasks have completed processing. + * Currently, we have one property task in the background: + * + * 1) Retrieving the translation of the file name + */ + enum NodeSpecificEvents { + TRANSLATION_AVAILABLE, + SCO_AVAILABLE + } + + static { + //Initialize this pool only once! This will be used by every instance of AAFN + //to do their heavy duty SCO column and translation updates. + backgroundTasksPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, + new ThreadFactoryBuilder().setNameFormat("content-node-background-task-%d").build()); + } /** * Handles aspects that depend on the Content object * @@ -240,4 +278,111 @@ public abstract class AbstractContentNode extends ContentNode public int read(byte[] buf, long offset, long len) throws TskException { return content.read(buf, offset, len); } + + + /** + * Updates the values of the properties in the current property sheet with + * the new properties being passed in. Only if that property exists in the + * current sheet will it be applied. That way, we allow for subclasses to + * add their own (or omit some!) properties and we will not accidentally + * disrupt their UI. + * + * Race condition if not synchronized. Only one update should be applied at + * a time. + * + * @param newProps New file property instances to be updated in the current + * sheet. + */ + protected synchronized void updateSheet(NodeProperty... newProps) { + //Refresh ONLY those properties in the sheet currently. Subclasses may have + //only added a subset of our properties or their own props.s + Sheet visibleSheet = this.getSheet(); + Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES); + Property[] visibleProps = visibleSheetSet.getProperties(); + for (NodeProperty newProp : newProps) { + for (int i = 0; i < visibleProps.length; i++) { + if (visibleProps[i].getName().equals(newProp.getName())) { + visibleProps[i] = newProp; + } + } + } + visibleSheetSet.put(visibleProps); + visibleSheet.put(visibleSheetSet); + //setSheet() will notify Netbeans to update this node in the UI. + this.setSheet(visibleSheet); + } + + /** + * Reads and returns a list of all tags associated with this content node. + * + * This default implementation returns an empty list. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @return list of tags associated with the node. + */ + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + /** + * Returns correlation attribute instance for the underlying content of the node. + * + * This default implementation returns null. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @return correlation attribute instance for the underlying content of the node. + * + */ + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + /** + * Returns Score property for the node. + * + * This default implementation returns NO_SCORE. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @param tags list of tags. + * + * @return Score property for the underlying content of the node. + * + */ + protected Pair getScorePropertyAndDescription(List tags) { + + return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); + } + /** + * Returns comment property for the node. + * + * This default implementation returns NO_COMMENT. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @param tags list of tags + * @param attribute correlation attribute instance + * + * @return Comment property for the underlying content of the node. + * + */ + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + /** + * Returns occurrences/count property for the node. + * + * This default implementation returns -1. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @param attribute correlation attribute instance + * + * @return count property for the underlying content of the node. + * + */ + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + return Pair.of(-1L, NO_DESCR); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 55e81acd17..1f871363f9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -37,6 +38,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; @@ -55,11 +57,13 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; @@ -96,7 +100,7 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties; - private final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); + protected final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); /* @@ -151,6 +155,19 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft())); + } + if (scoData.getComment() != null) { + updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, scoData.getComment())); + } + if (scoData.getCountAndDescription() != null && + !UserPreferences.hideCentralRepoCommentsAndOccurrences()) { + updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft())); + } + } } }; @@ -335,8 +352,6 @@ public class BlackboardArtifactNode extends AbstractContentNode tags = getAllTagsFromDatabase(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); if (sheetSet == null) { sheetSet = Sheet.createPropertiesSet(); @@ -351,17 +366,15 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), NO_DESCR, "")); + + // Get the SCO columns data in a background task + backgroundTasksPool.submit(new GetSCOTask( + new WeakReference<>(this), weakPcl)); + if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { try { BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); @@ -520,6 +533,7 @@ public class BlackboardArtifactNode extends AbstractContentNode getAllTagsFromDatabase() { List tags = new ArrayList<>(); try { @@ -569,6 +583,7 @@ public class BlackboardArtifactNode extends AbstractContentNode t.getName().getDisplayName()).collect(Collectors.joining(", ")))); } + @Override protected final CorrelationAttributeInstance getCorrelationAttributeInstance() { CorrelationAttributeInstance correlationAttribute = null; if (EamDb.isEnabled()) { @@ -581,16 +596,17 @@ public class BlackboardArtifactNode extends AbstractContentNode tags, CorrelationAttributeInstance attribute) { + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT; for (Tag tag : tags) { if (!StringUtils.isBlank(tag.getComment())) { @@ -609,17 +625,16 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, - status)); + return status; } - + /** * Used by (subclasses of) BlackboardArtifactNode to add the Score property * to their sheets. * - * @param sheetSet the modifiable Sheet.Set returned by - * Sheet.get(Sheet.PROPERTIES) * @param tags the list of tags associated with the file + * + * @return score property */ @NbBundle.Messages({"BlackboardArtifactNode.createSheet.score.name=S", "BlackboardArtifactNode.createSheet.score.displayName=S", @@ -628,8 +643,10 @@ public class BlackboardArtifactNode extends AbstractContentNode tags) { - Score score = Score.NO_SCORE; + + @Override + protected Pair getScorePropertyAndDescription(List tags) { + Score score = Score.NO_SCORE; String description = Bundle.BlackboardArtifactNode_createSheet_noScore_description(); if (associated instanceof AbstractFile) { if (((AbstractFile) associated).getKnown() == TskData.FileKnown.BAD) { @@ -673,9 +690,10 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), description, score)); + + return Pair.of(score, description); } - + @NbBundle.Messages({"BlackboardArtifactNode.createSheet.count.name=O", "BlackboardArtifactNode.createSheet.count.displayName=O", "BlackboardArtifactNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated", @@ -683,7 +701,8 @@ public class BlackboardArtifactNode extends AbstractContentNode getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting String description = Bundle.BlackboardArtifactNode_createSheet_count_noCentralRepo_description(); try { @@ -699,14 +718,13 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), description, count)); - } - + return Pair.of(count, description); + } + private void updateSheet() { this.setSheet(createSheet()); } - + private String getRootParentName() { String parentName = associated.getName(); Content parent = associated; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 1d8fb7eb14..8d2bd5952d 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -37,6 +37,7 @@ AbstractAbstractFileNode.tagsProperty.displayName=Tags AbstractAbstractFileNode.typeDirColLbl=Type(Dir) AbstractAbstractFileNode.typeMetaColLbl=Type(Meta) AbstractAbstractFileNode.useridColLbl=UserID +AbstractContentNode.nodescription=no description AbstractFsContentNode.noDesc.text=no description ArtifactStringContent.attrsTableHeader.sources=Source(s) ArtifactStringContent.attrsTableHeader.type=Type diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java index e8a7b5b1f5..4278f99d66 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java @@ -25,42 +25,44 @@ import java.util.List; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.events.AutopsyEvent; -import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.Tag; /** - * Background task to get Score, Comment and Occurrences values for a Abstract file node. + * Background task to get Score, Comment and Occurrences values for an + * Abstract content node. * */ class GetSCOTask implements Runnable { - private final WeakReference> weakNodeRef; + private final WeakReference> weakNodeRef; private final PropertyChangeListener listener; - public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { + public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { this.weakNodeRef = weakContentRef; this.listener = listener; } @Override public void run() { - AbstractAbstractFileNode fileNode = weakNodeRef.get(); + AbstractContentNode contentNode = weakNodeRef.get(); //Check for stale reference - if (fileNode == null) { + if (contentNode == null) { return; } // get the SCO column values - List tags = fileNode.getContentTagsFromDatabase(); - CorrelationAttributeInstance attribute = fileNode.getCorrelationAttributeInstance(); + List tags = contentNode.getAllTagsFromDatabase(); + CorrelationAttributeInstance attribute = contentNode.getCorrelationAttributeInstance(); SCOData scoData = new SCOData(); - scoData.setScoreAndDescription(fileNode.getScorePropertyAndDescription(tags)); - scoData.setComment(fileNode.getCommentProperty(tags, attribute)); + scoData.setScoreAndDescription(contentNode.getScorePropertyAndDescription(tags)); + scoData.setComment(contentNode.getCommentProperty(tags, attribute)); if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) { - scoData.setCountAndDescription(fileNode.getCountPropertyAndDescription(attribute)); + scoData.setCountAndDescription(contentNode.getCountPropertyAndDescription(attribute)); } + // signal SCO data is available. if (listener != null) { listener.propertyChange(new PropertyChangeEvent( AutopsyEvent.SourceType.LOCAL.toString(), From ea6687c2c596939dc4e0bc40d022da947f91d543 Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 4 Jun 2019 08:20:40 -0400 Subject: [PATCH 03/24] Address Codacy comments. --- .../datamodel/AbstractContentNode.java | 53 ++++----------- .../autopsy/datamodel/ImageNode.java | 67 ++++++++++++++++++ .../autopsy/datamodel/VolumeNode.java | 68 +++++++++++++++++++ 3 files changed, 147 insertions(+), 41 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 7a2577e407..f90a15ac42 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -67,8 +67,11 @@ public abstract class AbstractContentNode extends ContentNode static final ExecutorService backgroundTasksPool; static final Integer MAX_POOL_SIZE = 10; + /** + * Default no description string + */ @NbBundle.Messages("AbstractContentNode.nodescription=no description") - private static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); + protected static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); /** @@ -315,74 +318,42 @@ public abstract class AbstractContentNode extends ContentNode /** * Reads and returns a list of all tags associated with this content node. * - * This default implementation returns an empty list. - * The derived classes should override with an implementation specific - * to the type of node. - * * @return list of tags associated with the node. */ - protected List getAllTagsFromDatabase() { - return new ArrayList<>(); - } + abstract protected List getAllTagsFromDatabase(); + /** * Returns correlation attribute instance for the underlying content of the node. * - * This default implementation returns null. - * The derived classes should override with an implementation specific - * to the type of node. - * * @return correlation attribute instance for the underlying content of the node. - * */ - protected CorrelationAttributeInstance getCorrelationAttributeInstance() { - return null; - } + abstract protected CorrelationAttributeInstance getCorrelationAttributeInstance(); /** * Returns Score property for the node. * - * This default implementation returns NO_SCORE. - * The derived classes should override with an implementation specific - * to the type of node. - * * @param tags list of tags. * * @return Score property for the underlying content of the node. - * */ - protected Pair getScorePropertyAndDescription(List tags) { - - return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); - } + abstract protected Pair getScorePropertyAndDescription(List tags); + /** * Returns comment property for the node. * - * This default implementation returns NO_COMMENT. - * The derived classes should override with an implementation specific - * to the type of node. - * * @param tags list of tags * @param attribute correlation attribute instance * * @return Comment property for the underlying content of the node. - * */ - protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { - return DataResultViewerTable.HasCommentStatus.NO_COMMENT; - } + abstract protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute); + /** * Returns occurrences/count property for the node. * - * This default implementation returns -1. - * The derived classes should override with an implementation specific - * to the type of node. - * * @param attribute correlation attribute instance * * @return count property for the underlying content of the node. - * */ - protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { - return Pair.of(-1L, NO_DESCR); - } + abstract protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index 584a405fcc..b2c80f1322 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -28,12 +28,15 @@ import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import javax.swing.Action; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; import org.sleuthkit.autopsy.directorytree.FileSearchAction; @@ -47,6 +50,7 @@ import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; +import org.sleuthkit.datamodel.Tag; /** * This class is used to represent the "Node" for the image. The children of @@ -250,4 +254,67 @@ public class ImageNode extends AbstractContentNode { } }; + /** + * Reads and returns a list of all tags associated with this content node. + * + * Null implementation of an abstract method. + * + * @return list of tags associated with the node. + */ + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + /** + * Returns correlation attribute instance for the underlying content of the node. + * + * Null implementation of an abstract method. + * + * @return correlation attribute instance for the underlying content of the node. + */ + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + /** + * Returns Score property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags. + * + * @return Score property for the underlying content of the node. + */ + @Override + protected Pair getScorePropertyAndDescription(List tags) { + return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); + } + /** + * Returns comment property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags + * @param attribute correlation attribute instance + * + * @return Comment property for the underlying content of the node. + */ + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + /** + * Returns occurrences/count property for the node. + * + * Null implementation of an abstract method. + * + * @param attribute correlation attribute instance + * + * @return count property for the underlying content of the node. + */ + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + return Pair.of(-1L, NO_DESCR); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index d25b50d59f..233d098071 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -25,10 +25,14 @@ import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import javax.swing.Action; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; @@ -39,6 +43,7 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.Volume; import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction; +import org.sleuthkit.datamodel.Tag; /** * This class is used to represent the "Node" for the volume. Its child is the @@ -212,4 +217,67 @@ public class VolumeNode extends AbstractContentNode { public String getItemType() { return DisplayableItemNode.FILE_PARENT_NODE_KEY; } + /** + * Reads and returns a list of all tags associated with this content node. + * + * Null implementation of an abstract method. + * + * @return list of tags associated with the node. + */ + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + /** + * Returns correlation attribute instance for the underlying content of the node. + * + * Null implementation of an abstract method. + * + * @return correlation attribute instance for the underlying content of the node. + */ + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + /** + * Returns Score property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags. + * + * @return Score property for the underlying content of the node. + */ + @Override + protected Pair getScorePropertyAndDescription(List tags) { + return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); + } + /** + * Returns comment property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags + * @param attribute correlation attribute instance + * + * @return Comment property for the underlying content of the node. + */ + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + /** + * Returns occurrences/count property for the node. + * + * Null implementation of an abstract method. + * + * @param attribute correlation attribute instance + * + * @return count property for the underlying content of the node. + */ + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + return Pair.of(-1L, NO_DESCR); + } } From ed4da71c1aabd6d2c12e66de88671bab92b6d5a7 Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 4 Jun 2019 08:29:37 -0400 Subject: [PATCH 04/24] Remove unused import. --- .../src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index f90a15ac42..154ca68d50 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datamodel; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; From ff9a26a447e0c6ef9b7a49f0f163b6c1760d0523 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 4 Jun 2019 12:38:39 -0400 Subject: [PATCH 05/24] Adding export CSV action --- .../datamodel/DataModelActionsFactory.java | 11 + .../autopsy/datamodel/DirectoryNode.java | 2 + .../sleuthkit/autopsy/datamodel/FileNode.java | 2 + .../autopsy/datamodel/LayoutFileNode.java | 2 + .../autopsy/datamodel/LocalFileNode.java | 2 + .../autopsy/datamodel/SlackFileNode.java | 2 + .../datamodel/SpecialDirectoryNode.java | 2 + .../directorytree/Bundle.properties-MERGED | 9 + .../directorytree/DataResultFilterNode.java | 1 + .../ExplorerNodeActionVisitor.java | 5 + .../directorytree/ExportCSVAction.java | 298 ++++++++++++++++++ .../keywordsearch/AdHocSearchFilterNode.java | 2 + 12 files changed, 338 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index 13a0753ca1..bdb4a394a8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -92,6 +93,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -119,6 +121,7 @@ public class DataModelActionsFactory { actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, slackFileNode)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -155,6 +158,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance());// + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -189,6 +193,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -223,6 +228,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -257,6 +263,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -291,6 +298,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -325,6 +333,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -379,6 +388,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -415,6 +425,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index 97b0fd8216..5a3476f896 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -28,6 +28,7 @@ import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -89,6 +90,7 @@ public class DirectoryNode extends AbstractFsContentNode { actionsList.add(ViewFileInTimelineAction.createViewFileAction(content)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(new RunIngestModulesAction(content)); actionsList.add(null); // creates a menu separator diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 1695b253b0..7b3a55c20b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -173,6 +174,7 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(null); // Creates an item separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // Creates an item separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index bfd8f7bd91..41ecad0340 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -29,6 +29,7 @@ import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -105,6 +106,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 6d64aeb621..4a62cd9e4f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -82,6 +83,7 @@ public class LocalFileNode extends AbstractAbstractFileNode { actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java index 49d1b9da54..0e7bce56cc 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java @@ -28,6 +28,7 @@ import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -85,6 +86,7 @@ public class SlackFileNode extends AbstractFsContentNode { NbBundle.getMessage(this.getClass(), "SlackFileNode.getActions.viewInNewWin.text"), this)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java index 47f616de12..ce5948ce68 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java @@ -25,6 +25,7 @@ import javax.swing.Action; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.FileSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; @@ -61,6 +62,7 @@ public abstract class SpecialDirectoryNode extends AbstractAbstractFileNode visit(final DerivedFile d) { List actionsList = new ArrayList<>(); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(AddContentTagAction.getInstance()); final Collection selectedFilesList = @@ -166,6 +169,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final LocalFile d) { List actionsList = new ArrayList<>(); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(AddContentTagAction.getInstance()); final Collection selectedFilesList = @@ -182,6 +186,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final org.sleuthkit.datamodel.File d) { List actionsList = new ArrayList<>(); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(AddContentTagAction.getInstance()); final Collection selectedFilesList = diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java new file mode 100644 index 0000000000..169701a55c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -0,0 +1,298 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this content except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.directorytree; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.netbeans.api.progress.ProgressHandle; +import org.openide.util.Cancellable; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Node; +import org.openide.nodes.Node.PropertySet; +import org.openide.nodes.Node.Property; + +/** + * Exports CSV version of result nodes to a location selected by the user. + */ +public final class ExportCSVAction extends AbstractAction { + + private Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); + + private String userDefinedExportPath; + + // This class is a singleton to support multi-selection of nodes, since + // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every + // node in the array returns a reference to the same action object from Node.getActions(boolean). + private static ExportCSVAction instance; + + public static synchronized ExportCSVAction getInstance() { + if (null == instance) { + instance = new ExportCSVAction(); + } + return instance; + } + + /** + * Private constructor for the action. + */ + @NbBundle.Messages({"ExportCSV.title.text=Export to CSV"}) + private ExportCSVAction() { + super(Bundle.ExportCSV_title_text()); + } + + /** + * Asks user to choose destination, then extracts content to destination + * (recursing on directories). + * + * @param e The action event. + */ + @NbBundle.Messages({ + "# {0} - Output file", + "ExportCSV.actionPerformed.fileExists=File {0} already exists", + "ExportCSV.actionPerformed.noCurrentCase=No open case available"}) + @Override + public void actionPerformed(ActionEvent e) { + + Collection selectedNodes = Utilities.actionsGlobalContext().lookupAll(Node.class); + if (selectedNodes.isEmpty()) { + return; + } + + Node parent = selectedNodes.iterator().next().getParentNode(); + if (parent != null) { + System.out.println("HTML name: " + parent.getHtmlDisplayName()); + System.out.println("Display name: " + parent.getDisplayName()); + System.out.println("Class: " + parent.getClass().getCanonicalName()); + for (PropertySet set : parent.getPropertySets()) { + for (Property prop : set.getProperties()) { + try { + System.out.println(" " + prop.getDisplayName() + " : " + prop.getValue().toString()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + } + + try { + String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_listing.csv", Calendar.getInstance()); + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows()))); + fileChooser.setSelectedFile(new File(fileName)); + fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); + + int returnVal = fileChooser.showSaveDialog((Component) e.getSource()); + if (returnVal == JFileChooser.APPROVE_OPTION) { + + File selectedFile = fileChooser.getSelectedFile(); + if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS + selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS + } + updateExportDirectory(selectedFile.getParent(), Case.getCurrentCaseThrows()); + + if (selectedFile.exists()) { + logger.log(Level.SEVERE, "File {0} already exists", selectedFile.getAbsolutePath()); //NON-NLS + MessageNotifyUtil.Message.info(Bundle.ExportCSV_actionPerformed_fileExists(selectedFile)); + return; + } + + CSVWriter writer = new CSVWriter(selectedNodes, selectedFile); + writer.execute(); + } + } catch (NoCurrentCaseException ex) { + JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExportCSV_actionPerformed_noCurrentCase()); + logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS + } + } + + /** + * Get the export directory path. + * + * @param openCase The current case. + * + * @return The export directory path. + */ + private String getExportDirectory(Case openCase) { + String caseExportPath = openCase.getExportDirectory(); + + if (userDefinedExportPath == null) { + return caseExportPath; + } + + File file = new File(userDefinedExportPath); + if (file.exists() == false || file.isDirectory() == false) { + return caseExportPath; + } + + return userDefinedExportPath; + } + + /** + * Update the default export directory. If the directory path matches the + * case export directory, then the directory used will always match the + * export directory of any given case. Otherwise, the path last used will be + * saved. + * + * @param exportPath The export path. + * @param openCase The current case. + */ + private void updateExportDirectory(String exportPath, Case openCase) { + if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) { + userDefinedExportPath = null; + } else { + userDefinedExportPath = exportPath; + } + } + + + /** + * Thread that does the actual extraction work + */ + private class CSVWriter extends SwingWorker { + + private final Logger logger = Logger.getLogger(CSVWriter.class.getName()); + private ProgressHandle progress; + + private final List nodesToExport; + private final File outputFile; + + /** + * Create an instance of the CSVWriter. + * + * @param extractionTasks List of file extraction tasks. + */ + CSVWriter(Collection selectedNodes, File outputFile) { + this.nodesToExport = new ArrayList<>(selectedNodes); + this.outputFile = outputFile; + } + + @NbBundle.Messages({"CSVWriter.progress.extracting=Exporting to CSV file", + "CSVWriter.progress.cancelling=Cancelling"}) + @Override + protected Object doInBackground() throws Exception { + if (nodesToExport.isEmpty()) { + return null; + } + + // Set up progress bar. + final String displayName = Bundle.CSVWriter_progress_extracting(); + progress = ProgressHandle.createHandle(displayName, new Cancellable() { + @Override + public boolean cancel() { + if (progress != null) { + progress.setDisplayName(Bundle.CSVWriter_progress_cancelling()); + } + return ExportCSVAction.CSVWriter.this.cancel(true); + } + }); + progress.start(); + progress.switchToIndeterminate(); + + try (BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))) { + // Write BOM + br.write('\ufeff'); + + // Write the header + List headers = new ArrayList<>(); + PropertySet[] sets = nodesToExport.get(0).getPropertySets(); + for(PropertySet set : sets) { + for (Property prop : set.getProperties()) { + headers.add(prop.getDisplayName()); + } + } + br.write(listToCSV(headers)); + + // Write each line + for (Node node : nodesToExport) { + if (this.isCancelled()) { + break; + } + + List values = new ArrayList<>(); + sets = node.getPropertySets(); + for(PropertySet set : sets) { + for (Property prop : set.getProperties()) { + values.add(prop.getValue().toString()); + } + } + br.write(listToCSV(values)); + } + } + + return null; + } + + private String listToCSV(List values) { + return "\"" + String.join("\",\"", values) + "\"\n"; + } + + @NbBundle.Messages({"CSVWriter.done.notifyMsg.error=Error exporting to CSV file", + "# {0} - Output file", + "CSVWriter.done.notifyMsg.success=Wrote to {0}"}) + @Override + protected void done() { + boolean msgDisplayed = false; + try { + super.get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS + MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_error()); + msgDisplayed = true; + } catch (java.util.concurrent.CancellationException ex) { + // catch and ignore if we were cancelled + } finally { + progress.finish(); + if (!this.isCancelled() && !msgDisplayed) { + MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_success(outputFile)); + } + } + } + } +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java index 57b84b11d6..cff13cc16e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java @@ -32,6 +32,7 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.lookup.ProxyLookup; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; @@ -163,6 +164,7 @@ class AdHocSearchFilterNode extends FilterNode { actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); actionsList.add(null); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); From 2ecd3a56b4f5740dc03992b21a05765cd5dbcde7 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 5 Jun 2019 11:13:32 -0400 Subject: [PATCH 06/24] Addressed review comments. --- .../relationships/MessageNode.java | 5 -- .../datamodel/AbstractAbstractFileNode.java | 10 +-- .../datamodel/AbstractContentNode.java | 7 +- .../datamodel/BlackboardArtifactNode.java | 82 +++++++++++++++---- .../datamodel/Bundle.properties-MERGED | 2 +- .../autopsy/datamodel/GetSCOTask.java | 2 +- 6 files changed, 79 insertions(+), 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index 2f4ce1157a..d1cdcbabf2 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -18,13 +18,10 @@ */ package org.sleuthkit.autopsy.communications.relationships; -import java.util.List; import java.util.TimeZone; import java.util.logging.Level; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Sheet; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.core.UserPreferences; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; @@ -40,7 +37,6 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHO import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT; import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; -import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.communications.Utils; @@ -72,7 +68,6 @@ final class MessageNode extends BlackboardArtifactNode { @Override protected Sheet createSheet() { Sheet sheet = super.createSheet(); - List tags = getAllTagsFromDatabase(); Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); if (sheetSet == null) { sheetSet = Sheet.createPropertiesSet(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index e8d71caae7..158f2ef4d9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -74,8 +74,6 @@ import org.sleuthkit.datamodel.TskData; public abstract class AbstractAbstractFileNode extends AbstractContentNode { private static final Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName()); - @NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description") - private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc(); private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED); @@ -328,9 +326,11 @@ public abstract class AbstractAbstractFileNode extends A } // Create place holders for S C O - properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), NO_DESCR, "")); - properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, "")); - properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), NO_DESCR, "")); + properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), VALUE_LOADING, "")); + properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), VALUE_LOADING, "")); + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), VALUE_LOADING, "")); + } // Get the SCO columns data in a background task diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 154ca68d50..b9e012e6af 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -64,14 +64,15 @@ public abstract class AbstractContentNode extends ContentNode * populate this node. */ static final ExecutorService backgroundTasksPool; - static final Integer MAX_POOL_SIZE = 10; + private static final Integer MAX_POOL_SIZE = 10; /** * Default no description string */ - @NbBundle.Messages("AbstractContentNode.nodescription=no description") + @NbBundle.Messages({"AbstractContentNode.nodescription=no description", + "AbstractContentNode.valueLoading=value loading"}) protected static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); - + protected static final String VALUE_LOADING = Bundle.AbstractContentNode_valueLoading(); /** * Event signals to indicate the background tasks have completed processing. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 1f871363f9..23ab78fa01 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -99,10 +99,7 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties; - - protected final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); - - + /* * Artifact types which should have the full unique path of the associated * content as a property. @@ -367,9 +364,11 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), NO_DESCR, "")); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, "")); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), VALUE_LOADING, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), VALUE_LOADING, "")); + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), VALUE_LOADING, "")); + } // Get the SCO columns data in a background task backgroundTasksPool.submit(new GetSCOTask( @@ -596,14 +595,33 @@ public class BlackboardArtifactNode extends AbstractContentNode tags, CorrelationAttributeInstance attribute) { + HasCommentStatus status = getCommentProperty(tags, attribute ); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, + status)); + } + + /** + * Gets the comment property for the node + * * @param tags the list of tags associated with the file * @param attribute the correlation attribute associated with this * artifact's associated file, null if central repo is not * enabled * @return comment property */ - @NbBundle.Messages({"BlackboardArtifactNode.createSheet.comment.name=C", - "BlackboardArtifactNode.createSheet.comment.displayName=C"}) @Override protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { @@ -627,14 +645,15 @@ public class BlackboardArtifactNode extends AbstractContentNode tags) { + Pair scoreAndDescription = getScorePropertyAndDescription(tags); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoreAndDescription.getRight(), scoreAndDescription.getLeft())); + } + /** + * Get the score property for the node. + * + * @param tags the list of tags associated with the file + * + * @return score property and description + */ @Override protected Pair getScorePropertyAndDescription(List tags) { Score score = Score.NO_SCORE; @@ -693,17 +724,40 @@ public class BlackboardArtifactNode extends AbstractContentNode countAndDescription = getCountPropertyAndDescription(attribute); + sheetSet.put( + new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), countAndDescription.getRight(), countAndDescription.getLeft())); + } + + /** + * Gets the Occurrences property for the node. + * + * @param attribute correlation attribute instance + * + * @return count and description + * + */ @Override protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { - Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting + Long count = -1L; String description = Bundle.BlackboardArtifactNode_createSheet_count_noCentralRepo_description(); try { //don't perform the query if there is no correlation value diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 8d2bd5952d..0386b0fef2 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -1,5 +1,4 @@ AbstractAbstractFileNode.accessTimeColLbl=Access Time -AbstractAbstractFileNode.addFileProperty.desc=no description AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr. AbstractAbstractFileNode.changeTimeColLbl=Change Time AbstractAbstractFileNode.createdTimeColLbl=Created Time @@ -38,6 +37,7 @@ AbstractAbstractFileNode.typeDirColLbl=Type(Dir) AbstractAbstractFileNode.typeMetaColLbl=Type(Meta) AbstractAbstractFileNode.useridColLbl=UserID AbstractContentNode.nodescription=no description +AbstractContentNode.valueLoading=value loading AbstractFsContentNode.noDesc.text=no description ArtifactStringContent.attrsTableHeader.sources=Source(s) ArtifactStringContent.attrsTableHeader.type=Type diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java index 4278f99d66..b7f4e38a5e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java @@ -37,7 +37,7 @@ class GetSCOTask implements Runnable { private final WeakReference> weakNodeRef; private final PropertyChangeListener listener; - public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { + GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { this.weakNodeRef = weakContentRef; this.listener = listener; } From f8d02a3002e361f9d94be229b16bd75163c0d67b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 5 Jun 2019 12:19:38 -0400 Subject: [PATCH 07/24] Added button --- .../autopsy/corecomponents/Bundle.properties | 1 + .../corecomponents/Bundle.properties-MERGED | 1 + .../corecomponents/DataResultViewerTable.form | 24 +++- .../corecomponents/DataResultViewerTable.java | 29 +++-- .../directorytree/Bundle.properties-MERGED | 6 +- .../directorytree/ExportCSVAction.java | 117 ++++++++++++------ 6 files changed, 122 insertions(+), 56 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 7eb261880f..fae74a30ac 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -217,3 +217,4 @@ DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n +DataResultViewerTable.exportCSVButton.text=Save table as CSV diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index cd75ae919e..43ceba39ba 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -270,3 +270,4 @@ DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n +DataResultViewerTable.exportCSVButton.text=Save table as CSV diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index 6b17cfd6dd..87e771695b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -16,9 +16,10 @@ - - - + + + + @@ -39,7 +40,7 @@ - + @@ -48,9 +49,10 @@ + - - + + @@ -164,5 +166,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index a5bbc461b8..eb5fad97f5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -1291,6 +1291,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); gotoPageLabel = new javax.swing.JLabel(); gotoPageTextField = new javax.swing.JTextField(); + exportCSVButton = new javax.swing.JButton(); pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N @@ -1338,13 +1339,21 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } }); + exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N + exportCSVButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + exportCSVButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap(608, Short.MAX_VALUE) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(exportCSVButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(pageLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -1366,7 +1375,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() + .addGap(3, 3, 3) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(pageLabel) .addComponent(pageNumLabel) @@ -1374,9 +1383,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(gotoPageLabel) - .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 324, Short.MAX_VALUE) + .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(exportCSVButton)) + .addGap(3, 3, 3) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE) .addContainerGap()) ); @@ -1397,7 +1407,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer { pagingSupport.gotoPage(); }//GEN-LAST:event_gotoPageTextFieldActionPerformed + private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed + org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(rootNode.getChildren().getNodes()), this); + }//GEN-LAST:event_exportCSVButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton exportCSVButton; private javax.swing.JLabel gotoPageLabel; private javax.swing.JTextField gotoPageTextField; private org.openide.explorer.view.OutlineView outlineView; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index d58338c450..6ec6a0e76b 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -11,9 +11,9 @@ DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data s DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module. DirectoryTreeTopComponent.resultsView.title=Listing # {0} - Output file -ExportCSV.actionPerformed.fileExists=File {0} already exists -ExportCSV.actionPerformed.noCurrentCase=No open case available -ExportCSV.title.text=Export to CSV +ExportCSV.saveNodesToCSV.fileExists=File {0} already exists +ExportCSV.saveNodesToCSV.noCurrentCase=No open case available +ExportCSV.title.text=Export selected rows to CSV ExternalViewerAction.actionPerformed.failure.exe.message=The file is an executable and will not be opened. ExternalViewerAction.actionPerformed.failure.IO.message=There is no associated editor for files of this type or the associated application failed to launch. ExternalViewerAction.actionPerformed.failure.missingFile.message=The file no longer exists. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index 169701a55c..dce6244084 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -25,8 +25,10 @@ import java.io.BufferedWriter; import java.io.FileWriter; import java.io.FileOutputStream; import java.io.OutputStreamWriter; +import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.HashSet; @@ -63,9 +65,11 @@ import org.openide.nodes.Node.Property; */ public final class ExportCSVAction extends AbstractAction { - private Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); + private static Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); + private final static String DEFAULT_FILENAME = "Results"; + private final static List columnsToSkip = Arrays.asList("S", "C", "O"); - private String userDefinedExportPath; + private static volatile String userDefinedExportPath; // TODO make volatile or whatever // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every @@ -82,53 +86,44 @@ public final class ExportCSVAction extends AbstractAction { /** * Private constructor for the action. */ - @NbBundle.Messages({"ExportCSV.title.text=Export to CSV"}) + @NbBundle.Messages({"ExportCSV.title.text=Export selected rows to CSV"}) private ExportCSVAction() { super(Bundle.ExportCSV_title_text()); } - + /** * Asks user to choose destination, then extracts content to destination * (recursing on directories). * * @param e The action event. */ - @NbBundle.Messages({ - "# {0} - Output file", - "ExportCSV.actionPerformed.fileExists=File {0} already exists", - "ExportCSV.actionPerformed.noCurrentCase=No open case available"}) + @Override public void actionPerformed(ActionEvent e) { - Collection selectedNodes = Utilities.actionsGlobalContext().lookupAll(Node.class); - if (selectedNodes.isEmpty()) { + saveNodesToCSV(selectedNodes, (Component)e.getSource()); + } + + @NbBundle.Messages({ + "# {0} - Output file", + "ExportCSV.saveNodesToCSV.fileExists=File {0} already exists", + "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available"}) + public static void saveNodesToCSV(Collection nodesToExport, Component component) { + + if (nodesToExport.isEmpty()) { return; } - Node parent = selectedNodes.iterator().next().getParentNode(); - if (parent != null) { - System.out.println("HTML name: " + parent.getHtmlDisplayName()); - System.out.println("Display name: " + parent.getDisplayName()); - System.out.println("Class: " + parent.getClass().getCanonicalName()); - for (PropertySet set : parent.getPropertySets()) { - for (Property prop : set.getProperties()) { - try { - System.out.println(" " + prop.getDisplayName() + " : " + prop.getValue().toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - } - } - try { - String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_listing.csv", Calendar.getInstance()); + + String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode()); + JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows()))); fileChooser.setSelectedFile(new File(fileName)); fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); - int returnVal = fileChooser.showSaveDialog((Component) e.getSource()); + int returnVal = fileChooser.showSaveDialog(component); if (returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = fileChooser.getSelectedFile(); @@ -143,15 +138,51 @@ public final class ExportCSVAction extends AbstractAction { return; } - CSVWriter writer = new CSVWriter(selectedNodes, selectedFile); + CSVWriter writer = new CSVWriter(nodesToExport, selectedFile); writer.execute(); } } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExportCSV_actionPerformed_noCurrentCase()); + JOptionPane.showMessageDialog(component, Bundle.ExportCSV_actionPerformed_noCurrentCase()); logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS } } + /** + * Create a default name for the CSV output. + * + * @param parent The parent node for the selected nodes + * + * @return the default name + */ + private static String getDefaultOutputFileName(Node parent) { + String dateStr = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS", Calendar.getInstance()); + + if (parent != null) { + // The first value in the property set is generally a reasonable name + for (PropertySet set : parent.getPropertySets()) { + for (Property prop : set.getProperties()) { + try { + String parentName = prop.getValue().toString(); + + // Strip off the count (if present) + System.out.println("parentName (raw) : " + parentName); + parentName = parentName.replaceAll("\\([0-9]+\\)$", ""); + System.out.println("parentName (after paren regex) : " + parentName); + + // Strip out any invalid characters + parentName = parentName.replaceAll("[\\\\/:*?\"<>|]", "_"); + System.out.println("parentName (after char regex) : " + parentName); + + return parentName + " " + dateStr; + } catch (IllegalAccessException | InvocationTargetException ex) { + logger.log(Level.WARNING, "Failed to get property set value as string", ex); + } + } + } + } + return DEFAULT_FILENAME + " " + dateStr; + } + /** * Get the export directory path. * @@ -159,7 +190,7 @@ public final class ExportCSVAction extends AbstractAction { * * @return The export directory path. */ - private String getExportDirectory(Case openCase) { + private static String getExportDirectory(Case openCase) { // TODO sync String caseExportPath = openCase.getExportDirectory(); if (userDefinedExportPath == null) { @@ -183,7 +214,7 @@ public final class ExportCSVAction extends AbstractAction { * @param exportPath The export path. * @param openCase The current case. */ - private void updateExportDirectory(String exportPath, Case openCase) { + private static void updateExportDirectory(String exportPath, Case openCase) { // TODO sync if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) { userDefinedExportPath = null; } else { @@ -195,12 +226,12 @@ public final class ExportCSVAction extends AbstractAction { /** * Thread that does the actual extraction work */ - private class CSVWriter extends SwingWorker { + private static class CSVWriter extends SwingWorker { private final Logger logger = Logger.getLogger(CSVWriter.class.getName()); private ProgressHandle progress; - private final List nodesToExport; + private final Collection nodesToExport; private final File outputFile; /** @@ -208,8 +239,8 @@ public final class ExportCSVAction extends AbstractAction { * * @param extractionTasks List of file extraction tasks. */ - CSVWriter(Collection selectedNodes, File outputFile) { - this.nodesToExport = new ArrayList<>(selectedNodes); + CSVWriter(Collection nodesToExport, File outputFile) { + this.nodesToExport = nodesToExport; this.outputFile = outputFile; } @@ -241,25 +272,31 @@ public final class ExportCSVAction extends AbstractAction { // Write the header List headers = new ArrayList<>(); - PropertySet[] sets = nodesToExport.get(0).getPropertySets(); + PropertySet[] sets = nodesToExport.iterator().next().getPropertySets(); for(PropertySet set : sets) { for (Property prop : set.getProperties()) { - headers.add(prop.getDisplayName()); + if ( ! columnsToSkip.contains(prop.getDisplayName())) { + headers.add(prop.getDisplayName()); + } } } br.write(listToCSV(headers)); // Write each line - for (Node node : nodesToExport) { + Iterator nodeIterator = nodesToExport.iterator(); + while (nodeIterator.hasNext()) { if (this.isCancelled()) { break; } + Node node = (Node)nodeIterator.next(); List values = new ArrayList<>(); sets = node.getPropertySets(); for(PropertySet set : sets) { for (Property prop : set.getProperties()) { - values.add(prop.getValue().toString()); + if ( ! columnsToSkip.contains(prop.getDisplayName())) { + values.add(prop.getValue().toString()); + } } } br.write(listToCSV(values)); From 3ef9746d2647f76d29d04dea6e21c6c7164f727d Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 6 Jun 2019 10:26:02 -0400 Subject: [PATCH 08/24] Handle empty tables. Cleanup --- .../corecomponents/Bundle.properties-MERGED | 1 + .../corecomponents/DataResultViewerTable.form | 10 ++-- .../corecomponents/DataResultViewerTable.java | 27 +++++++--- .../directorytree/Bundle.properties-MERGED | 1 + .../directorytree/ExportCSVAction.java | 50 ++++++++++++------- 5 files changed, 59 insertions(+), 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index 43ceba39ba..b0e36da986 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -32,6 +32,7 @@ DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s) DataResultViewerTable.countRender.name=O DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository +DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export DataResultViewerTable.firstColLbl=Name DataResultViewerTable.goToPageTextField.err=Invalid page number # {0} - totalPages diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index 87e771695b..6aeda07298 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -18,12 +18,11 @@ - - + - + - + @@ -33,7 +32,8 @@ - + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index eb5fad97f5..e6c1110038 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -77,6 +77,7 @@ import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; @@ -176,6 +177,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer { initComponents(); initializePagingSupport(); + + /* + * Disable the CSV export button for the common properties results + */ + if (this instanceof org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributesSearchResultsViewerTable) { + exportCSVButton.setEnabled(false); + } /* * Configure the child OutlineView (explorer view) component. @@ -1352,12 +1360,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer { layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addComponent(exportCSVButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap() .addComponent(pageLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGap(14, 14, 14) .addComponent(pagesLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -1367,7 +1374,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { .addComponent(gotoPageLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap()) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(exportCSVButton)) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton}); @@ -1407,8 +1415,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer { pagingSupport.gotoPage(); }//GEN-LAST:event_gotoPageTextFieldActionPerformed + @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export" + }) private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed - org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(rootNode.getChildren().getNodes()), this); + Node currentRoot = this.getExplorerManager().getRootContext(); + if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) { + org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this); + } else { + MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty()); + } }//GEN-LAST:event_exportCSVButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index 6ec6a0e76b..f70a374bc5 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -10,6 +10,7 @@ DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contai DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source? DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module. DirectoryTreeTopComponent.resultsView.title=Listing +ExportCSV.saveNodesToCSV.empty=No data to export # {0} - Output file ExportCSV.saveNodesToCSV.fileExists=File {0} already exists ExportCSV.saveNodesToCSV.noCurrentCase=No open case available diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index dce6244084..7fa4560afc 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ import java.awt.Component; import java.awt.event.ActionEvent; import java.io.File; import java.io.BufferedWriter; -import java.io.FileWriter; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.lang.reflect.InvocationTargetException; @@ -31,10 +30,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; -import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -48,14 +45,8 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; -import org.sleuthkit.autopsy.datamodel.FileNode; -import org.sleuthkit.datamodel.AbstractFile; -import org.openide.nodes.AbstractNode; import org.openide.nodes.Node; import org.openide.nodes.Node.PropertySet; import org.openide.nodes.Node.Property; @@ -76,6 +67,12 @@ public final class ExportCSVAction extends AbstractAction { // node in the array returns a reference to the same action object from Node.getActions(boolean). private static ExportCSVAction instance; + /** + * Get an instance of the Action. See above for why + * the class is a singleton. + * + * @return the instance + */ public static synchronized ExportCSVAction getInstance() { if (null == instance) { instance = new ExportCSVAction(); @@ -104,20 +101,28 @@ public final class ExportCSVAction extends AbstractAction { saveNodesToCSV(selectedNodes, (Component)e.getSource()); } + /** + * Save the selected nodes to a CSV file + * + * @param nodesToExport the nodes to save + * @param component + */ @NbBundle.Messages({ "# {0} - Output file", "ExportCSV.saveNodesToCSV.fileExists=File {0} already exists", - "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available"}) + "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available", + "ExportCSV.saveNodesToCSV.empty=No data to export"}) public static void saveNodesToCSV(Collection nodesToExport, Component component) { if (nodesToExport.isEmpty()) { + MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_empty()); return; } try { - + // Set up the file chooser with a default name and either the Export + // folder or the last used folder. String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode()); - JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows()))); fileChooser.setSelectedFile(new File(fileName)); @@ -126,15 +131,18 @@ public final class ExportCSVAction extends AbstractAction { int returnVal = fileChooser.showSaveDialog(component); if (returnVal == JFileChooser.APPROVE_OPTION) { + // Get the file name, appending .csv if necessary File selectedFile = fileChooser.getSelectedFile(); if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS } + + // Save the directory used for next time updateExportDirectory(selectedFile.getParent(), Case.getCurrentCaseThrows()); if (selectedFile.exists()) { logger.log(Level.SEVERE, "File {0} already exists", selectedFile.getAbsolutePath()); //NON-NLS - MessageNotifyUtil.Message.info(Bundle.ExportCSV_actionPerformed_fileExists(selectedFile)); + MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_fileExists(selectedFile)); return; } @@ -142,7 +150,7 @@ public final class ExportCSVAction extends AbstractAction { writer.execute(); } } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog(component, Bundle.ExportCSV_actionPerformed_noCurrentCase()); + JOptionPane.showMessageDialog(component, Bundle.ExportCSV_saveNodesToCSV_noCurrentCase()); logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS } } @@ -165,13 +173,10 @@ public final class ExportCSVAction extends AbstractAction { String parentName = prop.getValue().toString(); // Strip off the count (if present) - System.out.println("parentName (raw) : " + parentName); parentName = parentName.replaceAll("\\([0-9]+\\)$", ""); - System.out.println("parentName (after paren regex) : " + parentName); // Strip out any invalid characters parentName = parentName.replaceAll("[\\\\/:*?\"<>|]", "_"); - System.out.println("parentName (after char regex) : " + parentName); return parentName + " " + dateStr; } catch (IllegalAccessException | InvocationTargetException ex) { @@ -190,7 +195,7 @@ public final class ExportCSVAction extends AbstractAction { * * @return The export directory path. */ - private static String getExportDirectory(Case openCase) { // TODO sync + private static String getExportDirectory(Case openCase) { String caseExportPath = openCase.getExportDirectory(); if (userDefinedExportPath == null) { @@ -306,6 +311,13 @@ public final class ExportCSVAction extends AbstractAction { return null; } + /** + * Convert list of values to a comma separated string. + * + * @param values Values to convert + * + * @return values as CSV + */ private String listToCSV(List values) { return "\"" + String.join("\",\"", values) + "\"\n"; } From 07ec6bdc3e9fa49092bfc1b0a26dd77ff16769a8 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 6 Jun 2019 11:09:28 -0400 Subject: [PATCH 09/24] Disable Interesting File buttons when no set is selected --- .../modules/interestingitems/FilesSetDefsPanel.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java index e919bf189f..d226e6765b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java @@ -278,6 +278,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp //enable the new button FilesSetDefsPanel.this.newSetButton.setEnabled(canBeEnabled); FilesSetDefsPanel.this.importSetButton.setEnabled(canBeEnabled); + // Get the selected interesting files set and populate the set // components. FilesSet selectedSet = FilesSetDefsPanel.this.setsList.getSelectedValue(); @@ -302,6 +303,12 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp if (!FilesSetDefsPanel.this.rulesListModel.isEmpty()) { FilesSetDefsPanel.this.rulesList.setSelectedIndex(0); } + } else { + // Disable the edit, delete, copy, and export buttons + FilesSetDefsPanel.this.editSetButton.setEnabled(false); + FilesSetDefsPanel.this.deleteSetButton.setEnabled(false); + FilesSetDefsPanel.this.copySetButton.setEnabled(false); + FilesSetDefsPanel.this.exportSetButton.setEnabled(false); } } From 2823ef0396924550e9e0a33c9501fb1dd36c54f5 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Thu, 6 Jun 2019 13:55:07 -0400 Subject: [PATCH 10/24] Disabled 'Text' content viewer tab for diectories and empty files --- .../textcontentviewer/TextContentViewer.java | 12 ++++++++++++ .../textcontentviewer/TextContentViewerPanel.java | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java index 81384acbaa..d7f853caab 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java @@ -23,6 +23,7 @@ import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.datamodel.AbstractFile; /** * A DataContentViewer that displays text with the TextViewers available. @@ -90,6 +91,17 @@ public class TextContentViewer implements DataContentViewer { if (node == null) { return false; } + // get the node's File, if it has one + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file == null) { + return false; + } + + // disable the text content viewer for directories and empty files + if (file.isDir() || file.getSize() == 0) { + return false; + } + return panel.isSupported(node); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java index e1af060cfc..7c80d606fd 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java @@ -79,7 +79,7 @@ public class TextContentViewerPanel extends javax.swing.JPanel implements DataCo } /** - * Deterime wether the content viewer which displays this panel isSupported. + * Determine whether the content viewer which displays this panel isSupported. * This panel is supported if any of the TextViewer's displayed in it are * supported. * From ae0aeddc5fa73266597cca05d540bf0740c7f400 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 6 Jun 2019 14:07:33 -0400 Subject: [PATCH 11/24] cleanup --- .../org/sleuthkit/autopsy/directorytree/ExportCSVAction.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index 7fa4560afc..4a87f72828 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -60,7 +60,7 @@ public final class ExportCSVAction extends AbstractAction { private final static String DEFAULT_FILENAME = "Results"; private final static List columnsToSkip = Arrays.asList("S", "C", "O"); - private static volatile String userDefinedExportPath; // TODO make volatile or whatever + private static String userDefinedExportPath; // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every @@ -219,7 +219,7 @@ public final class ExportCSVAction extends AbstractAction { * @param exportPath The export path. * @param openCase The current case. */ - private static void updateExportDirectory(String exportPath, Case openCase) { // TODO sync + private static void updateExportDirectory(String exportPath, Case openCase) { if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) { userDefinedExportPath = null; } else { From 6b5fc4af0e96cbdcae13821a58b6081c66fc0bd5 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 6 Jun 2019 14:46:54 -0400 Subject: [PATCH 12/24] 5079: Ingest multiple vcards from a vcf file --- .../org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java index 15d52b536e..7e5a321313 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java @@ -143,8 +143,9 @@ final class VcardParser { * @throws NoCurrentCaseException If there is no open case. */ void parse(File vcardFile, AbstractFile abstractFile) throws IOException, NoCurrentCaseException { - VCard vcard = Ezvcard.parse(vcardFile).first(); - addContactArtifact(vcard, abstractFile); + for (VCard vcard: Ezvcard.parse(vcardFile).all()) { + addContactArtifact(vcard, abstractFile); + } } From a7afef0e68af25ad6e78b0d697ed5841d9056340 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Thu, 6 Jun 2019 14:47:31 -0400 Subject: [PATCH 13/24] Fixed bug which was allocating too much memory to byte buffers --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index e9656bb0a9..4b284c53fa 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -296,8 +296,8 @@ public final class TranslatedTextViewer implements TextViewer { //Correct for UTF-8 byte[] resultInUTF8Bytes = result.getBytes("UTF8"); - byte[] trimTo1MB = Arrays.copyOfRange(resultInUTF8Bytes, 0, MAX_SIZE_1MB ); - return new String(trimTo1MB, "UTF-8"); + byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0, Math.min(resultInUTF8Bytes.length, MAX_SIZE_1MB) ); + return new String(trimToArraySize, "UTF-8"); } /** @@ -343,7 +343,7 @@ public final class TranslatedTextViewer implements TextViewer { } // The trim is on here because HTML files were observed with nearly 1MB of white space at the end - return textBuilder.toString().trim(); + return textBuilder.toString(); } /** From e837513c91a7d94ea4715b7c13fe641807ca6ffd Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 7 Jun 2019 10:09:06 -0400 Subject: [PATCH 14/24] Removed a comment --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 4b284c53fa..28c0c64a6c 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -341,8 +341,7 @@ public final class TranslatedTextViewer implements TextViewer { textBuilder.append(cbuf, 0, read); bytesRead += read; } - - // The trim is on here because HTML files were observed with nearly 1MB of white space at the end + return textBuilder.toString(); } From 605e6959921c161fcf584f32ff2d7508a833c940 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 7 Jun 2019 10:43:13 -0400 Subject: [PATCH 15/24] Use enum for SCO column names. Escape quotes. --- .../autopsy/directorytree/ExportCSVAction.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index 4a87f72828..759078fe2b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType; import org.openide.nodes.Node; import org.openide.nodes.Node.PropertySet; import org.openide.nodes.Node.Property; @@ -58,7 +59,8 @@ public final class ExportCSVAction extends AbstractAction { private static Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); private final static String DEFAULT_FILENAME = "Results"; - private final static List columnsToSkip = Arrays.asList("S", "C", "O"); + private final static List columnsToSkip = Arrays.asList(AbstractFilePropertyType.SCORE.toString(), + AbstractFilePropertyType.COMMENT.toString(), AbstractFilePropertyType.OCCURRENCES.toString()); private static String userDefinedExportPath; @@ -300,7 +302,7 @@ public final class ExportCSVAction extends AbstractAction { for(PropertySet set : sets) { for (Property prop : set.getProperties()) { if ( ! columnsToSkip.contains(prop.getDisplayName())) { - values.add(prop.getValue().toString()); + values.add(escapeQuotes(prop.getValue().toString())); } } } @@ -311,6 +313,18 @@ public final class ExportCSVAction extends AbstractAction { return null; } + /** + * Escape any quotes in the string + * + * @param original + * + * @return the string with quotes escaped + */ + private String escapeQuotes(String original) { + String result = original.replaceAll("\"", "\\\\\""); + return result; + } + /** * Convert list of values to a comma separated string. * From 15116f9f8a10ad5ae6cc7a427f6a6a97cb1d76ff Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 7 Jun 2019 11:15:33 -0400 Subject: [PATCH 16/24] Addressing codacy complaints --- .../sleuthkit/autopsy/directorytree/ExportCSVAction.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index 759078fe2b..7761e82231 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -57,7 +57,7 @@ import org.openide.nodes.Node.Property; */ public final class ExportCSVAction extends AbstractAction { - private static Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); + private static final Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); private final static String DEFAULT_FILENAME = "Results"; private final static List columnsToSkip = Arrays.asList(AbstractFilePropertyType.SCORE.toString(), AbstractFilePropertyType.COMMENT.toString(), AbstractFilePropertyType.OCCURRENCES.toString()); @@ -235,7 +235,7 @@ public final class ExportCSVAction extends AbstractAction { */ private static class CSVWriter extends SwingWorker { - private final Logger logger = Logger.getLogger(CSVWriter.class.getName()); + private static final Logger logger = Logger.getLogger(CSVWriter.class.getName()); private ProgressHandle progress; private final Collection nodesToExport; @@ -321,8 +321,7 @@ public final class ExportCSVAction extends AbstractAction { * @return the string with quotes escaped */ private String escapeQuotes(String original) { - String result = original.replaceAll("\"", "\\\\\""); - return result; + return original.replaceAll("\"", "\\\\\""); } /** From c6d732036891a0e0591d2e76ec9915db44b43729 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 13:10:01 -0400 Subject: [PATCH 17/24] Added 50KB limit on original text viewing --- .../texttranslation/ui/Bundle.properties | 1 - .../ui/Bundle.properties-MERGED | 3 +-- .../ui/TranslatedTextViewer.java | 7 +++--- .../ui/TranslationContentPanel.form | 23 ++++--------------- .../ui/TranslationContentPanel.java | 20 +++++----------- 5 files changed, 16 insertions(+), 38 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties index 08c2523b0f..946cae6db9 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties @@ -1,7 +1,6 @@ OptionsCategory_Name_Machine_Translation=Machine Translation OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings TranslationContentPanel.ShowLabel.text=Show: -TranslationContentPanel.warningLabel2MB.text=Only the first 1MB of text will be displayed TranslationContentPanel.ocrLabel.text=OCR: TranslationOptionsPanel.translationServiceLabel.text=Text translator: TranslationOptionsPanelController.moduleErr=Module Error diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED index 0304d7b1f3..24a322f6ed 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED @@ -1,6 +1,6 @@ OptionsCategory_Name_Machine_Translation=Machine Translation OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings -TranslatedContentPanel.comboBoxOption.originalText=Original Text +TranslatedContentPanel.comboBoxOption.originalText=Original Text (First 50KB) TranslatedContentPanel.comboBoxOption.translatedText=Translated Text TranslatedContentViewer.emptyTranslation=The resulting translation was empty. TranslatedContentViewer.errorExtractingText=Could not extract text from file. @@ -16,7 +16,6 @@ TranslatedTextViewer.title=Translation TranslatedTextViewer.toolTip=Displays translated file text. TranslationContentPanel.autoDetectOCR=Autodetect language TranslationContentPanel.ShowLabel.text=Show: -TranslationContentPanel.warningLabel2MB.text=Only the first 1MB of text will be displayed TranslationContentPanel.ocrLabel.text=OCR: TranslationOptionsPanel.noTextTranslators.text=No text translators exist, translation is disabled. TranslationOptionsPanel.noTextTranslatorSelected.text=No text translator selected, translation is disabled. diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 28c0c64a6c..a3f25f549f 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -67,7 +67,7 @@ public final class TranslatedTextViewer implements TextViewer { private static final boolean OCR_ENABLED = true; private static final boolean OCR_DISABLED = false; - private static final int MAX_SIZE_1MB = 1024000; + private static final int MAX_EXTRACT_SIZE_BYTES = 51200; private static final List INSTALLED_LANGUAGE_PACKS = PlatformUtil.getOcrLanguagePacks(); private final TranslationContentPanel panel = new TranslationContentPanel(); @@ -296,7 +296,8 @@ public final class TranslatedTextViewer implements TextViewer { //Correct for UTF-8 byte[] resultInUTF8Bytes = result.getBytes("UTF8"); - byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0, Math.min(resultInUTF8Bytes.length, MAX_SIZE_1MB) ); + byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0, + Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES) ); return new String(trimToArraySize, "UTF-8"); } @@ -331,7 +332,7 @@ public final class TranslatedTextViewer implements TextViewer { //Short-circuit the read if its greater than our max //translatable size - int bytesLeft = MAX_SIZE_1MB - bytesRead; + int bytesLeft = MAX_EXTRACT_SIZE_BYTES - bytesRead; if (bytesLeft < read) { textBuilder.append(cbuf, 0, bytesLeft); diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form index e042876476..325810a96d 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form @@ -47,13 +47,11 @@ - - - + - - - + + + @@ -73,13 +71,12 @@ - + - @@ -153,16 +150,6 @@ - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java index 0e625f1baa..aa5b543e29 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java @@ -246,7 +246,7 @@ public class TranslationContentPanel extends javax.swing.JPanel { /** * Selection choices to be displayed in the combobox dropdown. */ - @Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text", + @Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text (First 50KB)", "TranslatedContentPanel.comboBoxOption.translatedText=Translated Text"}) static enum DisplayDropdownOptions { ORIGINAL_TEXT(Bundle.TranslatedContentPanel_comboBoxOption_originalText()), @@ -280,7 +280,6 @@ public class TranslationContentPanel extends javax.swing.JPanel { displayTextComboBox = new javax.swing.JComboBox<>(); ocrDropdown = new javax.swing.JComboBox<>(); ocrLabel = new javax.swing.JLabel(); - warningLabel2MB = new javax.swing.JLabel(); jScrollPane1 = new javax.swing.JScrollPane(); displayTextArea = new javax.swing.JTextArea(); @@ -302,21 +301,16 @@ public class TranslationContentPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(ocrLabel, org.openide.util.NbBundle.getMessage(TranslationContentPanel.class, "TranslationContentPanel.ocrLabel.text")); // NOI18N ocrLabel.setEnabled(false); - warningLabel2MB.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/warning16.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(warningLabel2MB, org.openide.util.NbBundle.getMessage(TranslationContentPanel.class, "TranslationContentPanel.warningLabel2MB.text")); // NOI18N - javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(warningLabel2MB) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap(204, Short.MAX_VALUE) .addComponent(ShowLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(displayTextComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 128, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(displayTextComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 150, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(ocrLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(ocrDropdown, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -337,8 +331,7 @@ public class TranslationContentPanel extends javax.swing.JPanel { .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(displayTextComboBox, javax.swing.GroupLayout.DEFAULT_SIZE, 26, Short.MAX_VALUE) .addComponent(ShowLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(ocrLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(warningLabel2MB)) + .addComponent(ocrLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addComponent(ocrDropdown)) .addGap(7, 7, 7)) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -384,6 +377,5 @@ public class TranslationContentPanel extends javax.swing.JPanel { private javax.swing.JSeparator jSeparator2; private javax.swing.JComboBox ocrDropdown; private javax.swing.JLabel ocrLabel; - private javax.swing.JLabel warningLabel2MB; // End of variables declaration//GEN-END:variables } From 1295216b7eacdff579b548038ad1e388a9fd5acc Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 13:54:52 -0400 Subject: [PATCH 18/24] Bump limit down to 25K, changing component direction on 50K worth of text takes some time --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index a3f25f549f..d0dbcb3685 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -67,7 +67,7 @@ public final class TranslatedTextViewer implements TextViewer { private static final boolean OCR_ENABLED = true; private static final boolean OCR_DISABLED = false; - private static final int MAX_EXTRACT_SIZE_BYTES = 51200; + private static final int MAX_EXTRACT_SIZE_BYTES = 25600; private static final List INSTALLED_LANGUAGE_PACKS = PlatformUtil.getOcrLanguagePacks(); private final TranslationContentPanel panel = new TranslationContentPanel(); From 26b47b9ca979d35662c0600346f5ff9d42339c37 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 13:58:02 -0400 Subject: [PATCH 19/24] Added payload limit API and implemented it in the Bing and Google translators --- .../texttranslation/TextTranslator.java | 7 ++++++ .../translators/BingTranslator.java | 6 ++++- .../translators/GoogleTranslator.java | 25 +++++++++++-------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java index 5e6ad8b19e..e996e6331e 100755 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java @@ -56,4 +56,11 @@ public interface TextTranslator { * Save the settings as they have been modified in the component. */ void saveSettings(); + + /** + * Returns the hard limit for translation request sizes. + * + * @return + */ + int getMaxPayloadSize(); } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index 2b234f71d3..3dfd984b0a 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -108,7 +108,6 @@ public class BingTranslator implements TextTranslator { String toTranslate = string.trim(); //Translates some text into English, without specifying the source langauge. - // HTML files were producing lots of white space at the end //Google Translate required us to replace (\r\n|\n) with
//but Bing Translator doesn not have that requirement. //The free account has a maximum file size. If you have a paid account, @@ -172,4 +171,9 @@ public class BingTranslator implements TextTranslator { throw new TranslationException("JSON text does not match Bing Translator scheme: " + e); } } + + @Override + public int getMaxPayloadSize() { + return MAX_STRING_LENGTH; + } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index 46bdd6da67..3142eb7542 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -47,7 +47,8 @@ import org.sleuthkit.autopsy.texttranslation.TranslationException; public final class GoogleTranslator implements TextTranslator { private static final Logger logger = Logger.getLogger(GoogleTranslator.class.getName()); - private static final int MAX_STRING_LENGTH = 15000; + //See translate method for justification of this limit. + private static final int MAX_PAYLOAD_SIZE = 5000; private final GoogleTranslatorSettingsPanel settingsPanel; private final GoogleTranslatorSettings settings = new GoogleTranslatorSettings(); private Translate googleTranslate; @@ -90,21 +91,20 @@ public final class GoogleTranslator implements TextTranslator { if (googleTranslate != null) { try { // Translates some text into English, without specifying the source language. - - // HTML files were producing lots of white space at the end String substring = string.trim(); // We can't currently set parameters, so we are using the default behavior of // assuming the input is HTML. We need to replace newlines with
for Google to preserve them substring = substring.replaceAll("(\r\n|\n)", "
"); - // The API complains if the "Payload" is over 204800 bytes. I'm assuming that - // deals with the full request. At some point, we get different errors about too - // much text. Officially, Google says they will googleTranslate only 5k chars, - // but we have seen more than that working. - // there could be a value betwen 15k and 25k that works. I (BC) didn't test further - if (substring.length() > MAX_STRING_LENGTH) { - substring = substring.substring(0, MAX_STRING_LENGTH); + // The API complains if the "Payload" is over 204800 bytes. Google references that + //their service is optimized for 2K code points and recommends keeping the requests that size. + //There is a hard limit of 30K code points per request. There is also a time-based quota that + //we are not enforcing, which may lead to 403 errors. We are currently configured for a max of 5K + //in each request, for two reasons. 1) Is to be more in line with Google's recommendation. 2) Is to + //minimize accidental exceedence of time based quotas. + if (substring.length() > MAX_PAYLOAD_SIZE) { + substring = substring.substring(0, MAX_PAYLOAD_SIZE); } Translation translation = googleTranslate.translate(substring); @@ -178,4 +178,9 @@ public final class GoogleTranslator implements TextTranslator { settings.saveSettings(); loadTranslator(); } + + @Override + public int getMaxPayloadSize() { + return MAX_PAYLOAD_SIZE; + } } From ae54f5e6d6126c2ad0b8aac5b9811f4cf81b3419 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 14:38:18 -0400 Subject: [PATCH 20/24] Reflect new limit in pull down --- .../autopsy/texttranslation/ui/TranslationContentPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java index aa5b543e29..632674473c 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java @@ -246,7 +246,7 @@ public class TranslationContentPanel extends javax.swing.JPanel { /** * Selection choices to be displayed in the combobox dropdown. */ - @Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text (First 50KB)", + @Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text (First 25KB)", "TranslatedContentPanel.comboBoxOption.translatedText=Translated Text"}) static enum DisplayDropdownOptions { ORIGINAL_TEXT(Bundle.TranslatedContentPanel_comboBoxOption_originalText()), From 70f0cfa42b3db4debd3aacca32725c606a177afd Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 14:39:35 -0400 Subject: [PATCH 21/24] Initial commit for new warning label --- .../TextTranslationService.java | 8 ++++ .../texttranslation/ui/Bundle.properties | 1 + .../ui/Bundle.properties-MERGED | 2 + .../ui/TranslatedTextViewer.java | 6 +++ .../ui/TranslationContentPanel.form | 40 ++++++++++++++----- .../ui/TranslationContentPanel.java | 36 ++++++++++++----- 6 files changed, 73 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java index f1e6f084b7..fbab180785 100755 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java @@ -120,4 +120,12 @@ public final class TextTranslationService { public boolean hasProvider() { return selectedTranslator.isPresent(); } + + /** + * + * @return + */ + public int getMaxPayloadSize() { + return selectedTranslator.get().getMaxPayloadSize(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties index 946cae6db9..0298ef77ae 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties @@ -5,3 +5,4 @@ TranslationContentPanel.ocrLabel.text=OCR: TranslationOptionsPanel.translationServiceLabel.text=Text translator: TranslationOptionsPanelController.moduleErr=Module Error TranslationOptionsPanelController.moduleErr.msg=A module caused an error listening to TranslationSettingsPanelController updates. See log to determine which module. Some data could be incomplete. +TranslationContentPanel.warningLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED index 24a322f6ed..6d5dcb793d 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED @@ -12,6 +12,7 @@ TranslatedContentViewer.noServiceProvider=Machine Translation software was not f TranslatedContentViewer.textAlreadyIndexed=Please view the original text in the Indexed Text viewer. TranslatedContentViewer.translatingText=Translating text, please wait... TranslatedContentViewer.translationException=Error encountered while attempting translation. +TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated TranslatedTextViewer.title=Translation TranslatedTextViewer.toolTip=Displays translated file text. TranslationContentPanel.autoDetectOCR=Autodetect language @@ -24,3 +25,4 @@ TranslationOptionsPanel.translationDisabled.text=Translation disabled TranslationOptionsPanel.translationServiceLabel.text=Text translator: TranslationOptionsPanelController.moduleErr=Module Error TranslationOptionsPanelController.moduleErr.msg=A module caused an error listening to TranslationSettingsPanelController updates. See log to determine which module. Some data could be incomplete. +TranslationContentPanel.warningLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index d0dbcb3685..b111f431d9 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -77,6 +77,9 @@ public final class TranslatedTextViewer implements TextViewer { = new ThreadFactoryBuilder().setNameFormat("translation-content-viewer-%d").build(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory); + @NbBundle.Messages({ + "TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated" + }) @Override public void setNode(final Node node) { this.node = node; @@ -92,6 +95,9 @@ public final class TranslatedTextViewer implements TextViewer { panel.addLanguagePackNames(INSTALLED_LANGUAGE_PACKS); } } + + int payloadMaxInKB = TextTranslationService.getInstance().getMaxPayloadSize() / 1000; + panel.setWarningLabelMsg(String.format(Bundle.TranslatedTextViewer_maxPayloadSize(), payloadMaxInKB)); //Force a background task. displayDropDownListener.actionPerformed(null); diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form index 325810a96d..a46e1ee1e3 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form @@ -16,7 +16,7 @@ - + @@ -47,7 +47,9 @@ - + + + @@ -70,17 +72,25 @@ - + - - - - - + + + + + + + + + + + + + + + - - @@ -150,6 +160,16 @@
+ + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java index aa5b543e29..1ec6da6bec 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java @@ -87,6 +87,10 @@ public class TranslationContentPanel extends javax.swing.JPanel { dropDown.removeActionListener(listener); } } + + void setWarningLabelMsg(String msg) { + warningLabel.setText(msg); + } @NbBundle.Messages({"TranslationContentPanel.autoDetectOCR=Autodetect language"}) final void reset() { @@ -280,6 +284,7 @@ public class TranslationContentPanel extends javax.swing.JPanel { displayTextComboBox = new javax.swing.JComboBox<>(); ocrDropdown = new javax.swing.JComboBox<>(); ocrLabel = new javax.swing.JLabel(); + warningLabel = new javax.swing.JLabel(); jScrollPane1 = new javax.swing.JScrollPane(); displayTextArea = new javax.swing.JTextArea(); @@ -301,12 +306,17 @@ public class TranslationContentPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(ocrLabel, org.openide.util.NbBundle.getMessage(TranslationContentPanel.class, "TranslationContentPanel.ocrLabel.text")); // NOI18N ocrLabel.setEnabled(false); + warningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/warning16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(TranslationContentPanel.class, "TranslationContentPanel.warningLabel.text")); // NOI18N + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addContainerGap(204, Short.MAX_VALUE) + .addContainerGap() + .addComponent(warningLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 422, Short.MAX_VALUE) .addComponent(ShowLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(displayTextComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 150, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -325,15 +335,20 @@ public class TranslationContentPanel extends javax.swing.JPanel { ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createSequentialGroup() .addGap(9, 9, 9) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(displayTextComboBox, javax.swing.GroupLayout.DEFAULT_SIZE, 26, Short.MAX_VALUE) - .addComponent(ShowLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(ocrLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addComponent(ocrDropdown)) - .addGap(7, 7, 7)) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(warningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(displayTextComboBox, javax.swing.GroupLayout.DEFAULT_SIZE, 26, Short.MAX_VALUE) + .addComponent(ShowLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(ocrLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(ocrDropdown)) + .addGap(7, 7, 7)))) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() @@ -355,7 +370,7 @@ public class TranslationContentPanel extends javax.swing.JPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 628, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 872, Short.MAX_VALUE) .addComponent(jScrollPane1) ); layout.setVerticalGroup( @@ -377,5 +392,6 @@ public class TranslationContentPanel extends javax.swing.JPanel { private javax.swing.JSeparator jSeparator2; private javax.swing.JComboBox ocrDropdown; private javax.swing.JLabel ocrLabel; + private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables } From eaa0842533e780a7c63a4f51a14409ab2f71f664 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 15:23:09 -0400 Subject: [PATCH 22/24] Slight tweak in pull down wording --- .../autopsy/texttranslation/ui/Bundle.properties-MERGED | 2 +- .../autopsy/texttranslation/ui/TranslationContentPanel.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED index 24a322f6ed..04ec083675 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED @@ -1,6 +1,6 @@ OptionsCategory_Name_Machine_Translation=Machine Translation OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings -TranslatedContentPanel.comboBoxOption.originalText=Original Text (First 50KB) +TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB) TranslatedContentPanel.comboBoxOption.translatedText=Translated Text TranslatedContentViewer.emptyTranslation=The resulting translation was empty. TranslatedContentViewer.errorExtractingText=Could not extract text from file. diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java index 632674473c..7c72b65431 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java @@ -246,7 +246,7 @@ public class TranslationContentPanel extends javax.swing.JPanel { /** * Selection choices to be displayed in the combobox dropdown. */ - @Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text (First 25KB)", + @Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB)", "TranslatedContentPanel.comboBoxOption.translatedText=Translated Text"}) static enum DisplayDropdownOptions { ORIGINAL_TEXT(Bundle.TranslatedContentPanel_comboBoxOption_originalText()), From cae8662769753ff85e4967adfb70f6c4de5839e6 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 15:25:44 -0400 Subject: [PATCH 23/24] Added comment --- .../autopsy/texttranslation/TextTranslationService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java index fbab180785..b24dca2aac 100755 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java @@ -122,6 +122,7 @@ public final class TextTranslationService { } /** + * Returns the hard limit for translation request sizes. * * @return */ From 72c55785ce9e42dcd43f47bca1a025d81e7c8615 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 7 Jun 2019 15:30:35 -0400 Subject: [PATCH 24/24] Fixed comment wording --- .../autopsy/texttranslation/translators/GoogleTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index 3142eb7542..8289010828 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -101,7 +101,7 @@ public final class GoogleTranslator implements TextTranslator { //their service is optimized for 2K code points and recommends keeping the requests that size. //There is a hard limit of 30K code points per request. There is also a time-based quota that //we are not enforcing, which may lead to 403 errors. We are currently configured for a max of 5K - //in each request, for two reasons. 1) Is to be more in line with Google's recommendation. 2) Is to + //in each request, for two reasons. 1) To be more in line with Google's recommendation. 2) To //minimize accidental exceedence of time based quotas. if (substring.length() > MAX_PAYLOAD_SIZE) { substring = substring.substring(0, MAX_PAYLOAD_SIZE);