diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 1d53e46ca6..74ac603e0b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -28,7 +29,6 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; @@ -66,6 +66,7 @@ import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.texttranslation.utils.FileNameTranslationUtil; /** * An abstract node that encapsulates AbstractFile data @@ -94,15 +95,15 @@ public abstract class AbstractAbstractFileNode extends A IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); } } - + try { //See JIRA-5971 //Attempt to cache file path during construction of this UI component. this.content.getUniquePath(); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Failed attempt to cache the " - + "unique path of the abstract file instance. Name: %s (objID=%d)", - this.content.getName(), this.content.getId()), ex); + + "unique path of the abstract file instance. Name: %s (objID=%d)", + this.content.getName(), this.content.getId()), ex); } if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { @@ -490,39 +491,18 @@ public abstract class AbstractAbstractFileNode extends A } /** - * Translates this nodes content name. Doesn't attempt translation if the - * name is in english or if there is now translation service available. + * Translates the name of the file this node represents. An empty string + * will be returned if the translation fails for any reason. + * + * @return The translated file name or the empty string. */ String getTranslatedFileName() { - //If already in complete English, don't translate. - if (content.getName().matches("^\\p{ASCII}+$")) { + try { + return FileNameTranslationUtil.translate(content.getName()); + } catch (NoServiceProviderException | TranslationException ex) { + logger.log(Level.WARNING, MessageFormat.format("Error translating file name (objID={0}))", content.getId()), ex); return ""; } - TextTranslationService tts = TextTranslationService.getInstance(); - if (tts.hasProvider()) { - //Seperate out the base and ext from the contents file name. - String base = FilenameUtils.getBaseName(content.getName()); - try { - String translation = tts.translate(base); - String ext = FilenameUtils.getExtension(content.getName()); - - //If we have no extension, then we shouldn't add the . - String extensionDelimiter = (ext.isEmpty()) ? "" : "."; - - //Talk directly to this nodes pcl, fire an update when the translation - //is complete. - if (!translation.isEmpty()) { - return translation + extensionDelimiter + ext; - } - } catch (NoServiceProviderException noServiceEx) { - logger.log(Level.WARNING, "Translate unsuccessful because no TextTranslator " - + "implementation was provided.", noServiceEx.getMessage()); - } catch (TranslationException noTranslationEx) { - logger.log(Level.WARNING, "Could not successfully translate file name " - + content.getName(), noTranslationEx.getMessage()); - } - } - return ""; } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index b4aafc2bcc..8358dd7d34 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -76,6 +76,9 @@ import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; /** * A BlackboardArtifactNode is an AbstractNode implementation that can be used @@ -124,7 +127,8 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties; - private final PropertyChangeListener appEventListener = new PropertyChangeListener() { + private final PropertyChangeListener listener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); @@ -148,25 +152,19 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft())); + updateSheet(new NodeProperty<>( + 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())); + updateSheet(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_comment_name(), + Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), + NO_DESCR, scoData.getComment())); } if (scoData.getCountAndDescription() != null) { - updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft())); + updateSheet(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_count_name(), + Bundle.BlackboardArtifactNode_createSheet_count_displayName(), + scoData.getCountAndDescription().getRight(), + scoData.getCountAndDescription().getLeft())); } + } else if (eventType.equals(FileNameTransTask.getPropertyName())) { + /* + * Replace the value of the Source File property with the + * translated name via setDisplayName (see note in createSheet), + * and put the untranslated name in the Original Name property + * and in the tooltip. + */ + String originalName = evt.getOldValue().toString(); + translatedSourceName = evt.getNewValue().toString(); + setDisplayName(translatedSourceName); + setShortDescription(originalName); + updateSheet(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_srcFile_origName(), + Bundle.BlackboardArtifactNode_createSheet_srcFile_origDisplayName(), + NO_DESCR, + originalName)); } } }; @@ -198,7 +223,7 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"), + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_srcFile_name(), + Bundle.BlackboardArtifactNode_createSheet_srcFile_displayName(), NO_DESCR, - this.getSourceName())); + getDisplayName())); + + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { + /* + * If machine translation is configured, add the original name of + * the of the source content of the artifact represented by this + * node to the sheet. + */ + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_srcFile_origName(), + Bundle.BlackboardArtifactNode_createSheet_srcFile_origDisplayName(), + NO_DESCR, + translatedSourceName != null ? srcContent.getName() : "")); + if (translatedSourceName == null) { + /* + * NOTE: The task makes its own weak reference to the listener. + */ + new FileNameTransTask(srcContent.getName(), this, listener).submit(); + } + } if (!UserPreferences.getHideSCOColumns()) { /* @@ -396,12 +449,24 @@ public class BlackboardArtifactNode extends AbstractContentNode(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, "")); + 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 (CentralRepository.isEnabled()) { - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), VALUE_LOADING, "")); + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_count_name(), + Bundle.BlackboardArtifactNode_createSheet_count_displayName(), + VALUE_LOADING, + "")); } - backgroundTasksPool.submit(new GetSCOTask(new WeakReference<>(this), weakAppEventListener)); + backgroundTasksPool.submit(new GetSCOTask(new WeakReference<>(this), weakListener)); } /* @@ -414,11 +479,13 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.displayName"), NO_DESCR, associatedArtifact.getDisplayName())); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.displayName"), NO_DESCR, associatedArtifact.getShortDescription())); @@ -459,15 +526,17 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"), NO_DESCR, ext)); @@ -484,12 +553,11 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), + AbstractFile file = srcContent instanceof AbstractFile ? (AbstractFile) srcContent : null; + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getMtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getCtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getAtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getCrtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"), "", file == null ? "" : file.getSize())); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_artifactMD5_name(), + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_artifactMD5_name(), Bundle.BlackboardArtifactNode_createSheet_artifactMD5_displayName(), "", file == null ? "" : StringUtils.defaultString(file.getMd5Hash()))); } } else { String dataSourceStr = ""; - if (srcContent != null) { - try { - Content dataSource = srcContent.getDataSource(); - if (dataSource != null) { - dataSourceStr = dataSource.getName(); - } else { - dataSourceStr = getRootAncestorName(); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting source data source name (artifact objID={0})", artifact.getId()), ex); //NON-NLS + try { + Content dataSource = srcContent.getDataSource(); + if (dataSource != null) { + dataSourceStr = dataSource.getName(); + } else { + dataSourceStr = getRootAncestorName(); } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting source data source name (artifact objID={0})", artifact.getId()), ex); //NON-NLS + } if (dataSourceStr.isEmpty() == false) { @@ -563,24 +636,27 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.displayName"), NO_DESCR, size)); - sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.displayName"), - NO_DESCR, - path)); + sheetSet + .put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.displayName"), + NO_DESCR, + path)); } return sheet; @@ -597,9 +673,7 @@ public class BlackboardArtifactNode extends AbstractContentNode tags = new ArrayList<>(); try { tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); - if (srcContent != null) { - tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(srcContent)); - } + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(srcContent)); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, MessageFormat.format("Error getting tags for artifact and its source content (artifact objID={0})", artifact.getId()), ex); } @@ -617,7 +691,7 @@ public class BlackboardArtifactNode extends AbstractContentNode sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,13 +24,11 @@ import java.util.Arrays; import java.util.List; import java.util.logging.Level; import javax.swing.Action; -import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -46,14 +44,14 @@ import static org.sleuthkit.autopsy.datamodel.Bundle.*; * tag name nodes have tag type child nodes; tag type nodes are the parents of * either content or blackboard artifact tag nodes. */ -public class BlackboardArtifactTagNode extends DisplayableItemNode { +public class BlackboardArtifactTagNode extends TagNode { private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactTagNode.class.getName()); private static final String ICON_PATH = "org/sleuthkit/autopsy/images/green-tag-icon-16.png"; //NON-NLS private final BlackboardArtifactTag tag; public BlackboardArtifactTagNode(BlackboardArtifactTag tag) { - super(Children.LEAF, Lookups.fixed(tag, tag.getArtifact(), tag.getContent())); + super(Lookups.fixed(tag, tag.getArtifact(), tag.getContent()), tag.getContent()); super.setName(tag.getContent().getName()); super.setDisplayName(tag.getContent().getName()); this.setIconBaseWithExtension(ICON_PATH); @@ -75,6 +73,7 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFile.text"), "", tag.getContent().getName())); + addOriginalNameProp(properties); String contentPath; try { contentPath = tag.getContent().getUniquePath(); @@ -82,7 +81,6 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + tag.getContent().getId() + ")", ex); //NON-NLS contentPath = NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.unavail.text"); } - properties.put(new NodeProperty<>( NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), @@ -146,11 +144,6 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { return visitor.visit(this); } - @Override - public boolean isLeafTypeNode() { - return true; - } - @Override public String getItemType() { return getClass().getName(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 3fa8cd0341..83225be1c2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -11,8 +11,6 @@ ArtifactTypeNode.createSheet.childCnt.name=Child Count ArtifactTypeNode.createSheet.childCnt.displayName=Child Count ArtifactTypeNode.createSheet.childCnt.desc=no description BlackboardArtifactNode.noDesc.text=no description -BlackboardArtifactNode.createSheet.srcFile.name=Source File -BlackboardArtifactNode.createSheet.srcFile.displayName=Source File BlackboardArtifactNode.createSheet.ext.name=Extension BlackboardArtifactNode.createSheet.ext.displayName=Extension BlackboardArtifactNode.createSheet.mimeType.name=MIME Type diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 8289f947da..e7310882a3 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -74,10 +74,12 @@ BlackboardArtifactNode.createSheet.path.displayName=Path BlackboardArtifactNode.createSheet.path.name=Path BlackboardArtifactNode.createSheet.score.displayName=S BlackboardArtifactNode.createSheet.score.name=S +BlackboardArtifactNode.createSheet.srcFile.displayName=Source File +BlackboardArtifactNode.createSheet.srcFile.name=Source File +BlackboardArtifactNode.createSheet.srcFile.origDisplayName=Original Name +BlackboardArtifactNode.createSheet.srcFile.origName=Original Name BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged. BlackboardArtifactNode.createSheet.tags.displayName=Tags -# {0} - artifactDisplayName -BlackboardArtifactNode.displayName.artifact={0} Artifact BlackboardArtifactTagNode.createSheet.userName.text=User Name BlackboardArtifactTagNode.viewSourceArtifact.text=View Source Result Category.five=CAT-5: Non-pertinent @@ -88,6 +90,7 @@ Category.two=CAT-2: Child Exploitation (Non-Illegal/Age Difficult) Category.zero=CAT-0: Uncategorized ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash ContentTagNode.createSheet.artifactMD5.name=MD5 Hash +ContentTagNode.createSheet.origFileName=Original Name ContentTagNode.createSheet.userName.text=User Name DeletedContent.allDelFilter.text=All DeletedContent.createSheet.filterType.desc=no description @@ -175,8 +178,6 @@ ArtifactTypeNode.createSheet.childCnt.name=Child Count ArtifactTypeNode.createSheet.childCnt.displayName=Child Count ArtifactTypeNode.createSheet.childCnt.desc=no description BlackboardArtifactNode.noDesc.text=no description -BlackboardArtifactNode.createSheet.srcFile.name=Source File -BlackboardArtifactNode.createSheet.srcFile.displayName=Source File BlackboardArtifactNode.createSheet.ext.name=Extension BlackboardArtifactNode.createSheet.ext.displayName=Extension BlackboardArtifactNode.createSheet.mimeType.name=MIME Type @@ -345,6 +346,8 @@ TagNameNode.bbArtTagTypeNodeKey.text=Result Tags TagNameNode.bookmark.text=Bookmark TagNameNode.createSheet.name.name=Name TagNameNode.createSheet.name.displayName=Name +TagNode.propertySheet.origName=Original Name +TagNode.propertySheet.origNameDisplayName=Original Name TagsNode.displayName.text=Tags TagsNode.createSheet.name.name=Name TagsNode.createSheet.name.displayName=Name diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index e213e460cb..89d6c2fae2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2016 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,6 @@ import java.util.List; import java.util.logging.Level; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -39,18 +38,16 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Instances of this class wrap ContentTag objects. In the Autopsy presentation * of the SleuthKit data model, they are leaf nodes of a tree consisting of - * content and blackboard artifact tags, grouped first by tag type, then by tag - * name. + * content and artifact tags, grouped first by tag type, then by tag name. */ -class ContentTagNode extends DisplayableItemNode { +class ContentTagNode extends TagNode { private static final Logger LOGGER = Logger.getLogger(ContentTagNode.class.getName()); - private static final String ICON_PATH = "org/sleuthkit/autopsy/images/blue-tag-icon-16.png"; //NON-NLS private final ContentTag tag; - public ContentTagNode(ContentTag tag) { - super(Children.LEAF, Lookups.fixed(tag, tag.getContent())); + ContentTagNode(ContentTag tag) { + super(Lookups.fixed(tag, tag.getContent()), tag.getContent()); super.setName(tag.getContent().getName()); super.setDisplayName(tag.getContent().getName()); this.setIconBaseWithExtension(ICON_PATH); @@ -58,6 +55,7 @@ class ContentTagNode extends DisplayableItemNode { } @Messages({ + "ContentTagNode.createSheet.origFileName=Original Name", "ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash", "ContentTagNode.createSheet.artifactMD5.name=MD5 Hash", "ContentTagNode.createSheet.userName.text=User Name"}) @@ -79,15 +77,19 @@ class ContentTagNode extends DisplayableItemNode { properties = Sheet.createPropertiesSet(); propertySheet.put(properties); } - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.displayName"), "", content.getName())); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.name"), + addOriginalNameProp(properties); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.displayName"), "", contentPath)); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.displayName"), "", tag.getComment())); @@ -95,23 +97,28 @@ class ContentTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getMtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getAtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), "", content.getSize())); - properties.put(new NodeProperty<>(Bundle.ContentTagNode_createSheet_artifactMD5_name(), + properties.put(new NodeProperty<>( + Bundle.ContentTagNode_createSheet_artifactMD5_name(), Bundle.ContentTagNode_createSheet_artifactMD5_displayName(), "", file != null ? StringUtils.defaultString(file.getMd5Hash()) : "")); @@ -128,8 +135,7 @@ class ContentTagNode extends DisplayableItemNode { List actions = new ArrayList<>(); actions.addAll(Arrays.asList(super.getActions(context))); - AbstractFile file = getLookup().lookup(AbstractFile.class - ); + AbstractFile file = getLookup().lookup(AbstractFile.class); if (file != null) { actions.add(ViewFileInTimelineAction.createViewFileAction(file)); } @@ -144,13 +150,9 @@ class ContentTagNode extends DisplayableItemNode { return visitor.visit(this); } - @Override - public boolean isLeafTypeNode() { - return true; - } - @Override public String getItemType() { return getClass().getName(); } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java index c723d99b55..c6bc88129a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datamodel; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; +import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -142,4 +143,26 @@ public abstract class DisplayableItemNode extends AbstractNode { return selectedChildNodeInfo; } + /** + * Updates the node property sheet by replacing existing properties with new + * properties with the same property name. + * + * @param newProps The replacement property objects. + */ + protected synchronized final void updatePropertySheet(NodeProperty... newProps) { + Sheet currentSheet = this.getSheet(); + Sheet.Set currentPropsSet = currentSheet.get(Sheet.PROPERTIES); + Property[] currentProps = currentPropsSet.getProperties(); + for (NodeProperty newProp : newProps) { + for (int i = 0; i < currentProps.length; i++) { + if (currentProps[i].getName().equals(newProp.getName())) { + currentProps[i] = newProp; + } + } + } + currentPropsSet.put(currentProps); + currentSheet.put(currentPropsSet); + this.setSheet(currentSheet); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java new file mode 100755 index 0000000000..9653c44d04 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java @@ -0,0 +1,128 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020-2020 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 org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.datamodel.Content; + +/** + * An abstract superclass for a node that represents a tag, uses the name of a + * given Content object as its display name, and has a property sheet with an + * original name property when machine translation is enabled. + * + * The translation of the Content name is done in a background thread. The + * translated name is made the display name of the node and the untranslated + * name is put into both the original name property and into the node's tooltip. + * + * TODO (Jira-6174): Consider modifying this class to be able to use it more broadly + * within the Autopsy data model (i.e., AbstractNode suclasses). It's not really + * specific to a tag node. + */ +@NbBundle.Messages({ + "TagNode.propertySheet.origName=Original Name", + "TagNode.propertySheet.origNameDisplayName=Original Name" +}) +abstract class TagNode extends DisplayableItemNode { + + private final static String ORIG_NAME_PROP_NAME = Bundle.TagNode_propertySheet_origName(); + private final static String ORIG_NAME_PROP_DISPLAY_NAME = Bundle.TagNode_propertySheet_origNameDisplayName(); + + private final String originalName; + private volatile String translatedName; + + /** + * An abstract superclass for a node that represents a tag, uses the name of + * a given Content object as its display name, and has a property sheet with + * an untranslated file name property when machine translation is enabled. + * + * @param lookup The Lookup of the node. + * @param content The Content to use for the node display name. + */ + TagNode(Lookup lookup, Content content) { + super(Children.LEAF, lookup); + originalName = content.getName(); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + abstract public String getItemType(); + + @Override + abstract public T accept(DisplayableItemNodeVisitor visitor); + + /** + * Adds an original name property to the node's property sheet and submits + * an original name translation task. + * + * The translation of the original name is done in a background thread. The + * translated name is made the display name of the node and the untranslated + * name is put into both the original name property and into the node's + * tooltip. + * + * @param properties The node's property sheet. + */ + protected void addOriginalNameProp(Sheet.Set properties) { + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { + properties.put(new NodeProperty<>( + ORIG_NAME_PROP_NAME, + ORIG_NAME_PROP_DISPLAY_NAME, + "", + translatedName != null ? originalName : "")); + if (translatedName == null) { + new FileNameTransTask(originalName, this, new NameTranslationListener()).submit(); + } + } + } + + /** + * A listener for PropertyChangeEvents from a background task used to + * translate the original display name associated with the node. + */ + private class NameTranslationListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(FileNameTransTask.getPropertyName())) { + translatedName = evt.getNewValue().toString(); + String originalName = evt.getOldValue().toString(); + setDisplayName(translatedName); + setShortDescription(originalName); + updatePropertySheet(new NodeProperty<>( + ORIG_NAME_PROP_NAME, + ORIG_NAME_PROP_DISPLAY_NAME, + "", + originalName)); + } + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java index 368b09b142..0a7e0a6f3f 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java @@ -64,7 +64,7 @@ public abstract class AbstractNodePropertySheetTask impl * @return The Future of the task, may be used for task cancellation by * calling Future.cancel(true). */ - public static Future submitTask(AbstractNodePropertySheetTask task) { + private static Future submitTask(AbstractNodePropertySheetTask task) { return executor.submit(task); } @@ -104,12 +104,22 @@ public abstract class AbstractNodePropertySheetTask impl * * @param node The AbstractNode. * - * @return The result of the computation as a PropertyChangeEvent. + * @return The result of the computation as a PropertyChangeEvent, may be + * null. */ protected abstract PropertyChangeEvent computePropertyValue(T node) throws Exception; + /** + * Submits this task to the ExecutorService for the thread pool. + * + * @return The task's Future from the ExecutorService. + */ + public final Future submit() { + return submitTask(this); + } + @Override - final public void run() { + public final void run() { try { T node = this.weakNodeRef.get(); PropertyChangeListener listener = this.weakListenerRef.get(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileNameTransTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileNameTransTask.java new file mode 100755 index 0000000000..8b755ec3dc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileNameTransTask.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.utils; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import org.openide.nodes.AbstractNode; +import org.sleuthkit.autopsy.texttranslation.utils.FileNameTranslationUtil; + +/** + * An AbstractNodePropertySheetTask that translates a file name for an + * AbstractNode's property sheet. + */ +public class FileNameTransTask extends AbstractNodePropertySheetTask { + + private final static String EVENT_SOURCE = FileNameTransTask.class.getName(); + private final static String PROPERTY_NAME = EVENT_SOURCE + ".TranslatedFileName"; + private final String fileName; + + public static String getPropertyName() { + return PROPERTY_NAME; + } + + /** + * Constructs an AbstractNodePropertySheetTask that translates a file name + * for an AbstractNode's property sheet. When the translation is complete, a + * PropertyChangeEvent will be fired to the node's PropertyChangeListener. + * Call getPropertyName() to identify the property. + * + * @param node The node. + * @param listener The node's PropertyChangeListener. + * @param fileName THe file name. + */ + public FileNameTransTask(String fileName, AbstractNode node, PropertyChangeListener listener) { + super(node, listener); + this.fileName = fileName; + } + + @Override + protected PropertyChangeEvent computePropertyValue(AbstractNode node) throws Exception { + String translatedFileName = FileNameTranslationUtil.translate(fileName); + return translatedFileName.isEmpty() ? null : new PropertyChangeEvent(EVENT_SOURCE, PROPERTY_NAME, fileName, translatedFileName); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 522d3836c3..a412bb5970 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -187,27 +187,6 @@ public class DataResultFilterNode extends FilterNode { return propertySets; } - /** - * Gets the display name for the wrapped node. - * - * OutlineView used in the DataResult table uses getDisplayName() to - * populate the first column, which is Source File. - * - * Hence this override to return the 'correct' displayName for the wrapped - * node. - * - * @return The display name for the node. - */ - @Override - public String getDisplayName() { - final Node orig = getOriginal(); - String name = orig.getDisplayName(); - if ((orig instanceof BlackboardArtifactNode)) { - name = ((BlackboardArtifactNode) orig).getSourceName(); - } - return name; - } - /** * Adds information about which child node of this node, if any, should be * selected. Can be null. diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/utils/FileNameTranslationUtil.java b/Core/src/org/sleuthkit/autopsy/texttranslation/utils/FileNameTranslationUtil.java new file mode 100755 index 0000000000..752e981990 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/utils/FileNameTranslationUtil.java @@ -0,0 +1,74 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020-2020 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.texttranslation.utils; + +import org.apache.commons.io.FilenameUtils; +import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.texttranslation.TranslationException; + +/** + * A utility to translate file names. + */ +public final class FileNameTranslationUtil { + + /** + * Translates a file name using the configured machine translation service. + * + * @param fileName The file name. + * + * @return The translation of the file name. + * + * @throws NoServiceProviderException If machine translation is not + * configured. + * @throws TranslationException If there is an error doing the + * translation. + */ + public static String translate(String fileName) throws NoServiceProviderException, TranslationException { + /* + * Don't attempt translation if the characters of the file name are all + * ASCII chars. + * + * TODO (Jira-6175): This filter prevents translation of many + * non-English file names composed entirely of Latin chars. + */ + if (fileName.matches("^\\p{ASCII}+$")) { + return ""; + } + + TextTranslationService translator = TextTranslationService.getInstance(); + String baseName = FilenameUtils.getBaseName(fileName); + String translation = translator.translate(baseName); + if (!translation.isEmpty()) { + String extension = FilenameUtils.getExtension(fileName); + if (!extension.isEmpty()) { + String extensionDelimiter = (extension.isEmpty()) ? "" : "."; + translation += extensionDelimiter + extension; + } + } + return translation; + } + + /** + * Prevent instantiation of this utility class + */ + private FileNameTranslationUtil() { + } + +}