diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 2c6b0fce9a..14b3c6afad 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -338,6 +338,10 @@ org.sleuthkit.autopsy.modules.vmextractor org.sleuthkit.autopsy.progress org.sleuthkit.autopsy.report +<<<<<<< HEAD +======= + org.sleuthkit.autopsy.tabulardatareader +>>>>>>> 4316-text-translation-service org.sleuthkit.autopsy.texttranslation org.sleuthkit.datamodel diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index cb5f797c2b..3dc230cf21 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -72,7 +72,8 @@ public final class UserPreferences { public static final String GROUP_ITEMS_IN_TREE_BY_DATASOURCE = "GroupItemsInTreeByDataSource"; //NON-NLS public static final String SHOW_ONLY_CURRENT_USER_TAGS = "ShowOnlyCurrentUserTags"; public static final String HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES = "HideCentralRepoCommentsAndOccurrences"; - + public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames"; + // Prevent instantiation. private UserPreferences() { } @@ -244,6 +245,14 @@ public final class UserPreferences { public static void setHideCentralRepoCommentsAndOccurrences(boolean value) { preferences.putBoolean(HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES, value); } + + public static void setDisplayTranslationFileNames(boolean value) { + preferences.putBoolean(DISPLAY_TRANSLATED_NAMES, value); + } + + public static boolean displayTranslationFileNames() { + return preferences.getBoolean(DISPLAY_TRANSLATED_NAMES, false); + } /** * Reads persisted case database connection info. diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 12ba493380..3e8e75dad7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -192,3 +192,5 @@ ViewPreferencesPanel.centralRepoLabel.text=Do not use Central Repository for: ViewPreferencesPanel.commentsOccurencesColumnsCheckbox.text=C(omments) and O(ccurences) columns to reduce loading times ViewPreferencesPanel.deletedFilesLimitCheckbox.text=Limit to 10,000 ViewPreferencesPanel.deletedFilesLimitLabel.text=Limit number of deleted files displayed: +ViewPreferencesPanel.fileDisplayLabel.text=Translate text in the: +ViewPreferencesPanel.translatedNamesButton.text=Table diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form index ddeb57bd74..1f76dde4dd 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form @@ -92,6 +92,8 @@ + + @@ -114,9 +116,11 @@ + + @@ -125,14 +129,12 @@ + - - - @@ -174,9 +176,15 @@ - + + + + - + + + + @@ -356,6 +364,23 @@ + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java index 51d4120449..af9a940537 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.deletedFiles.DeletedFilePreferences; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; /** * Panel for configuring view preferences. @@ -44,6 +45,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { public ViewPreferencesPanel(boolean immediateUpdates) { initComponents(); this.immediateUpdates = immediateUpdates; + + //If there is not Text Translator implementation, then hide these buttons + //from the user. + TextTranslationService tts = TextTranslationService.getInstance(); + if(!tts.hasProvider()) { + translatedNamesButton.setVisible(false); + fileDisplayLabel.setVisible(false); + } } @Override @@ -67,6 +76,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { commentsOccurencesColumnsCheckbox.setSelected(UserPreferences.hideCentralRepoCommentsAndOccurrences()); deletedFilesLimitCheckbox.setSelected(DeletedFilePreferences.getDefault().getShouldLimitDeletedFiles()); + translatedNamesButton.setSelected(UserPreferences.displayTranslationFileNames()); // Current Case Settings boolean caseIsOpen = Case.isCaseOpen(); @@ -90,6 +100,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCheckbox.isSelected()); UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected()); UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected()); + UserPreferences.setDisplayTranslationFileNames(translatedNamesButton.isSelected()); storeGroupItemsInTreeByDataSource(); @@ -142,6 +153,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { centralRepoLabel = new javax.swing.JLabel(); deletedFilesLimitCheckbox = new javax.swing.JCheckBox(); deletedFilesLimitLabel = new javax.swing.JLabel(); + translatedNamesButton = new javax.swing.JRadioButton(); + fileDisplayLabel = new javax.swing.JLabel(); currentCaseSettingsPanel = new javax.swing.JPanel(); groupByDataSourceCheckbox = new javax.swing.JCheckBox(); currentSessionSettingsPanel = new javax.swing.JPanel(); @@ -244,6 +257,15 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { org.openide.awt.Mnemonics.setLocalizedText(deletedFilesLimitLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.deletedFilesLimitLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(translatedNamesButton, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.translatedNamesButton.text")); // NOI18N + translatedNamesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + translatedNamesButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(fileDisplayLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.fileDisplayLabel.text")); // NOI18N + javax.swing.GroupLayout globalSettingsPanelLayout = new javax.swing.GroupLayout(globalSettingsPanel); globalSettingsPanel.setLayout(globalSettingsPanelLayout); globalSettingsPanelLayout.setHorizontalGroup( @@ -259,6 +281,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { .addComponent(deletedFilesLimitCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addGroup(globalSettingsPanelLayout.createSequentialGroup() .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(centralRepoLabel) + .addComponent(deletedFilesLimitLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(globalSettingsPanelLayout.createSequentialGroup() .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(hideKnownFilesLabel) @@ -274,9 +298,11 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { .addGap(10, 10, 10) .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(dataSourcesHideKnownCheckbox) - .addComponent(viewsHideKnownCheckbox))))) + .addComponent(viewsHideKnownCheckbox)))) + .addComponent(hideOtherUsersTagsLabel)) .addGap(18, 18, 18) .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(fileDisplayLabel) .addComponent(displayTimeLabel) .addGroup(globalSettingsPanelLayout.createSequentialGroup() .addGap(10, 10, 10) @@ -284,11 +310,9 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { .addComponent(keepCurrentViewerRadioButton) .addComponent(useBestViewerRadioButton) .addComponent(useGMTTimeRadioButton) - .addComponent(useLocalTimeRadioButton))) - .addComponent(selectFileLabel))) - .addComponent(hideOtherUsersTagsLabel) - .addComponent(centralRepoLabel) - .addComponent(deletedFilesLimitLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(useLocalTimeRadioButton) + .addComponent(translatedNamesButton))) + .addComponent(selectFileLabel)))) .addGap(0, 10, Short.MAX_VALUE))) .addContainerGap()) ); @@ -322,9 +346,13 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(useGMTTimeRadioButton))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(hideOtherUsersTagsLabel) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(fileDisplayLabel) + .addComponent(hideOtherUsersTagsLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(hideOtherUsersTagsCheckbox) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(hideOtherUsersTagsCheckbox) + .addComponent(translatedNamesButton)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(centralRepoLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -535,6 +563,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { } }//GEN-LAST:event_deletedFilesLimitCheckboxActionPerformed + private void translatedNamesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_translatedNamesButtonActionPerformed + if (immediateUpdates) { + UserPreferences.setDisplayTranslationFileNames(translatedNamesButton.isSelected()); + } else { + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + }//GEN-LAST:event_translatedNamesButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel centralRepoLabel; @@ -546,6 +582,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { private javax.swing.JCheckBox deletedFilesLimitCheckbox; private javax.swing.JLabel deletedFilesLimitLabel; private javax.swing.JLabel displayTimeLabel; + private javax.swing.JLabel fileDisplayLabel; private javax.swing.JPanel globalSettingsPanel; private javax.swing.JCheckBox groupByDataSourceCheckbox; private javax.swing.JLabel hideKnownFilesLabel; @@ -555,6 +592,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { private javax.swing.JLabel hideSlackFilesLabel; private javax.swing.JRadioButton keepCurrentViewerRadioButton; private javax.swing.JLabel selectFileLabel; + private javax.swing.JRadioButton translatedNamesButton; private javax.swing.JRadioButton useBestViewerRadioButton; private javax.swing.JRadioButton useGMTTimeRadioButton; private javax.swing.JRadioButton useLocalTimeRadioButton; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index b2dd93705c..c67a93c687 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -30,6 +30,7 @@ import java.util.Optional; 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.Children; @@ -52,8 +53,13 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus; +import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.texttranslation.TranslationCallback; +import org.sleuthkit.autopsy.texttranslation.TranslationException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; @@ -71,10 +77,15 @@ public abstract class AbstractAbstractFileNode extends A 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 String TRANSLATION_AVAILABLE_EVENT = "TRANSLATION_AVAILABLE"; + private static final String NO_TRANSLATION = ""; 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 String translatedFileName; + /** * @param abstractFile file to wrap */ @@ -89,6 +100,7 @@ public abstract class AbstractAbstractFileNode extends A IngestManager.getInstance().addIngestModuleEventListener(weakPcl); } } + translatedFileName = null; // Listen for case events so that we can detect when the case is closed // or when tags are added. Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); @@ -165,6 +177,9 @@ public abstract class AbstractAbstractFileNode extends A if (event.getContentID() == content.getId()) { updateSheet(); } + } else if (eventType.equals(TRANSLATION_AVAILABLE_EVENT)) { + this.translatedFileName = (String) evt.getNewValue(); + updateSheet(); } }; @@ -211,6 +226,7 @@ public abstract class AbstractAbstractFileNode extends A "AbstractAbstractFileNode.createSheet.score.name=S", "AbstractAbstractFileNode.createSheet.comment.name=C", "AbstractAbstractFileNode.createSheet.count.name=O", + "AbstractAbstractFileNode.translateFileName=Translated Name", "AbstractAbstractFileNode.locationColLbl=Location", "AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time", "AbstractAbstractFileNode.changeTimeColLbl=Change Time", @@ -454,6 +470,41 @@ public abstract class AbstractAbstractFileNode extends A } } + /** + * Attempts translation on the file name by kicking off a background task to do the + * translation. Once the background task is done, it will fire a PropertyChangeEvent, + * which will force this node to refresh itself, thus updating its translated name + * column. + * + * @return The file names translation. + */ + protected String getTranslatedFileName() { + //If already in complete English, don't translate. + if (this.content.getName().matches("^\\p{ASCII}+$")) { + return NO_TRANSLATION; + } + + //If we already have a translation use that one. + if (translatedFileName != null) { + return translatedFileName; + } + + //If not, lets fire off a background translation that will update the UI + //when it is done. + TextTranslationService tts = TextTranslationService.getInstance(); + if (tts.hasProvider()) { + //Seperate out the base and ext from the contents file name. + String base = FilenameUtils.getBaseName(this.content.getName()); + + //Send only the base file name to be translated. Once the translation comes + //back fire a PropertyChangeEvent on this nodes PropertyChangeListener. + tts.translateAsynchronously(base, new TranslateFileNameCallback(this.content, weakPcl)); + } + + //In the mean time, return a blank translation. + return NO_TRANSLATION; + } + /** * Fill map with AbstractFile properties * @@ -659,4 +710,72 @@ public abstract class AbstractAbstractFileNode extends A return ""; } } + + /** + * Implements the TranslationCallback interface so that the TextTranslationService + * can call these methods when an asynchronous translation task is complete. + */ + private class TranslateFileNameCallback implements TranslationCallback { + private final String ext; + private final String originalFileName; + + private final PropertyChangeListener listener; + + public TranslateFileNameCallback(AbstractFile file, PropertyChangeListener listener) { + this.ext = FilenameUtils.getExtension(content.getName()); + this.originalFileName = content.getName(); + this.listener = listener; + } + + /** + * Fires a PropertyChangeEvent on this nodes PropertyChangeListener + * when the translation is finished. Reconstruct the file name so we + * can properly display the translation. + * + * @param translation Result from the translation job submitted to + * Text translation service. + */ + @Override + public void onTranslationResult(String translation) { + //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()) { + listener.propertyChange(new PropertyChangeEvent( + AutopsyEvent.SourceType.LOCAL.toString(), + TRANSLATION_AVAILABLE_EVENT, null, translation)); + } else { + listener.propertyChange(new PropertyChangeEvent( + AutopsyEvent.SourceType.LOCAL.toString(), + TRANSLATION_AVAILABLE_EVENT, null, + translation + extensionDelimiter + ext)); + } + } + + /** + * Do nothing on a translation exception except log, the column will remain empty + * and there is nothing we can do as a fallback. + * + * @param ex Exception caught while translating. + */ + @Override + public void onTranslationException(TranslationException noTranslationEx) { + logger.log(Level.WARNING, "Could not successfully translate file name " + originalFileName, noTranslationEx); + } + + /** + * Do nothing on a no service provider exception except log, in this implemention we + * are only calling for translation to be done if we know that a TextTranslator + * service provider is already defined. + * + * @param ex + */ + @Override + public void onNoServiceProviderException(NoServiceProviderException noServiceEx) { + logger.log(Level.WARNING, "Translate unsuccessful because no TextTranslator " + + "implementation was provided.", noServiceEx); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 9a322446bc..7269a09e2c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -320,7 +320,7 @@ public class BlackboardArtifactNode extends AbstractContentNode { } } - @Override public T accept(ContentNodeVisitor visitor) { return visitor.visit(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java index 411cd464ec..37f1d1a136 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java @@ -38,6 +38,59 @@ public class LocalDirectoryNode extends SpecialDirectoryNode { } @Override +<<<<<<< HEAD +======= + @NbBundle.Messages({ + "LocalDirectoryNode.createSheet.name.name=Name", + "LocalDirectoryNode.createSheet.name.displayName=Name", + "LocalDirectoryNode.createSheet.name.desc=no description", + "LocalDirectoryNode.createSheet.noDesc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + List tags = getContentTagsFromDatabase(); + sheetSet.put(new NodeProperty<>(Bundle.LocalDirectoryNode_createSheet_name_name(), + Bundle.LocalDirectoryNode_createSheet_name_displayName(), + Bundle.LocalDirectoryNode_createSheet_name_desc(), + getName())); + + final String NO_DESCR = Bundle.LocalDirectoryNode_createSheet_noDesc(); + if(UserPreferences.displayTranslationFileNames()) { + String translation = getTranslatedFileName(); + sheetSet.put(new NodeProperty<>(Bundle.AbstractAbstractFileNode_translateFileName(), + Bundle.AbstractAbstractFileNode_translateFileName(), NO_DESCR, translation)); + } + + addScoreProperty(sheetSet, tags); + + CorrelationAttributeInstance correlationAttribute = null; + if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + correlationAttribute = getCorrelationAttributeInstance(); + } + addCommentProperty(sheetSet, tags, correlationAttribute); + + if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + addCountProperty(sheetSet, correlationAttribute); + } + // At present, a LocalDirectory will never be a datasource - the top level of a logical + // file set is a VirtualDirectory + Map map = new LinkedHashMap<>(); + fillPropertyMap(map, getContent()); + + for (Map.Entry entry : map.entrySet()) { + sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); + } + + return sheet; + } + + @Override +>>>>>>> 4316-text-translation-service public T accept(ContentNodeVisitor visitor) { return visitor.visit(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index beb624d1af..5c83708fec 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -61,7 +61,6 @@ public class LocalFileNode extends AbstractAbstractFileNode { } - @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); actionsList.addAll(Arrays.asList(super.getActions(true))); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 17ee1d078f..17d70834bb 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -170,6 +170,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE: case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE: case UserPreferences.HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES: + case UserPreferences.DISPLAY_TRANSLATED_NAMES: case UserPreferences.KEEP_PREFERRED_VIEWER: refreshContentTreeSafe(); break; diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/NoServiceProviderException.java b/Core/src/org/sleuthkit/autopsy/texttranslation/NoServiceProviderException.java new file mode 100755 index 0000000000..79590fcfd9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/NoServiceProviderException.java @@ -0,0 +1,30 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018-2018 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; + +/** + * Exception to indicate that no Service Provider could be found during the + * Lookup action. + */ +public class NoServiceProviderException extends Exception { + + public NoServiceProviderException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java index 4f20c748ed..eff494ac8f 100755 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java @@ -18,27 +18,41 @@ */ package org.sleuthkit.autopsy.texttranslation; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + import org.openide.util.Lookup; /** - * Service for finding and running TextTranslator implementations + * Performs a lookup for a TextTranslator service provider and if present, + * will use this provider to run translation on the input. */ -public class TextTranslationService { +public final class TextTranslationService { + private final static TextTranslationService tts = new TextTranslationService(); + private final Optional translator; - - /** - * - */ - public TextTranslationService() { + + private static ExecutorService pool; + private final static Integer MAX_POOL_SIZE = 10; + + private TextTranslationService(){ + //Perform look up for Text Translation implementations ONLY ONCE during + //class loading. translator = Optional.ofNullable(Lookup.getDefault() .lookup(TextTranslator.class)); } + + public static TextTranslationService getInstance() { + return tts; + } /** - * Performs a lookup for a TextTranslator service provider and if present, - * will use this provider to run translation on the input. + * Translates the input string using whichever TextTranslator Service Provider + * was found during lookup. * * @param input Input string to be translated * @@ -59,13 +73,51 @@ public class TextTranslationService { } /** - * Exception to indicate that no Service Provider could be found during the - * Lookup action. + * Makes the call to translate(String) happen asynchronously on a background + * thread. When it is done, it will use the appropriate TranslationCallback + * method. + * + * @param input String to be translated + * @param tcb Interface for handling the translation result or any + * exceptions thrown while running translate. + * */ - public class NoServiceProviderException extends Exception { + public void translateAsynchronously(String input, TranslationCallback tcb) { + if (translator.isPresent()) { + //Delay thread pool initialization until an asynchronous task is first called. + //That way we don't have threads sitting parked in the background for no reason. + if (pool == null) { + ThreadFactory translationFactory = new ThreadFactoryBuilder() + .setNameFormat("translation-thread-%d") + .build(); + pool = Executors.newFixedThreadPool(MAX_POOL_SIZE, translationFactory); + } - public NoServiceProviderException(String msg) { - super(msg); + //Submit the task to the pool, calling the appropriate method depending + //on the result of the translate operation. + pool.submit(() -> { + try { + String translation = translate(input); + tcb.onTranslationResult(translation); + } catch (NoServiceProviderException ex) { + tcb.onNoServiceProviderException(ex); + } catch (TranslationException ex) { + tcb.onTranslationException(ex); + } + }); } + + tcb.onNoServiceProviderException(new NoServiceProviderException( + "Could not find a TextTranslator service provider")); + } + + /** + * Returns if a TextTranslator lookup successfully found an implementing + * class. + * + * @return + */ + public boolean hasProvider() { + return translator.isPresent(); } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java index e8f8fa94d2..2e6b6bbd07 100755 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java @@ -24,13 +24,6 @@ package org.sleuthkit.autopsy.texttranslation; */ public interface TextTranslator { - public String translate(String input) throws TranslationException; + String translate(String input) throws TranslationException; - public default String[] translate(String[] inputs) throws TranslationException { - String[] outputs = new String[inputs.length]; - for(int i = 0; i < inputs.length; i++) { - outputs[i] = translate(inputs[i]); - } - return outputs; - } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/TranslationCallback.java b/Core/src/org/sleuthkit/autopsy/texttranslation/TranslationCallback.java new file mode 100755 index 0000000000..8144f6fa1f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/TranslationCallback.java @@ -0,0 +1,49 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018-2018 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; + +/** + * This callback interface will be used by TextTranslationService when doing + * translations on background tasks. When the translation is done, it's result will + * be delegated to one of the following methods. It can either be successful or fail with + * exceptions. + */ +public interface TranslationCallback { + + /** + * Provides a method to handle the translation result + * + * @param translation result of calling TextTranslationService. + */ + void onTranslationResult(String translation); + + /** + * Provides a way to handle Translation Exceptions. + * + * @param noTranslationEx + */ + void onTranslationException(TranslationException noTranslationEx); + + /** + * Provides a way to handle NoServiceProviderExceptions. + * + * @param noServiceEx + */ + void onNoServiceProviderException(NoServiceProviderException noServiceEx); +}