Autopsy  4.13.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
TranslatedTextViewer.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.texttranslation.ui;
20 
21 import com.google.common.collect.Lists;
22 import com.google.common.util.concurrent.ThreadFactoryBuilder;
23 import java.awt.Component;
24 import java.awt.ComponentOrientation;
25 import java.awt.Font;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.io.IOException;
29 import java.io.Reader;
30 import java.util.Arrays;
31 import java.util.concurrent.CancellationException;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.ThreadFactory;
36 import org.openide.nodes.Node;
37 import org.openide.util.lookup.ServiceProvider;
39 import org.sleuthkit.datamodel.AbstractFile;
40 import javax.swing.SwingWorker;
41 import org.openide.util.Lookup;
42 import org.openide.util.NbBundle;
43 import org.openide.util.lookup.Lookups;
53 import org.sleuthkit.datamodel.Content;
54 import java.util.List;
55 import java.util.logging.Level;
56 import javax.swing.SwingUtilities;
60 
64 @ServiceProvider(service = TextViewer.class, position = 4)
65 public final class TranslatedTextViewer implements TextViewer {
66 
67  private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName());
68 
69  private static final boolean OCR_ENABLED = true;
70  private static final boolean OCR_DISABLED = false;
71  private static final int MAX_EXTRACT_SIZE_BYTES = 25600;
72  private static final List<String> INSTALLED_LANGUAGE_PACKS = PlatformUtil.getOcrLanguagePacks();
73  private final TranslationContentPanel panel = new TranslationContentPanel();
74 
75  private volatile Node node;
77  private final ThreadFactory translationThreadFactory
78  = new ThreadFactoryBuilder().setNameFormat("translation-content-viewer-%d").build();
79  private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory);
80 
81  @NbBundle.Messages({
82  "TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated"
83  })
84  @Override
85  public void setNode(final Node node) {
86  this.node = node;
87  SelectionChangeListener displayDropDownListener = new DisplayDropDownChangeListener();
88  panel.addDisplayTextActionListener(displayDropDownListener);
89  panel.addOcrDropDownActionListener(new OCRDropdownChangeListener());
90  Content source = DataContentViewerUtility.getDefaultContent(node);
91 
92  if (source instanceof AbstractFile) {
93  boolean isImage = ((AbstractFile) source).getMIMEType().toLowerCase().startsWith("image/");
94  if (isImage) {
95  panel.enableOCRSelection(OCR_ENABLED);
96  panel.addLanguagePackNames(INSTALLED_LANGUAGE_PACKS);
97  }
98  }
99 
100  int payloadMaxInKB = TextTranslationService.getInstance().getMaxTextChars() / 1000;
101  panel.setWarningLabelMsg(String.format(Bundle.TranslatedTextViewer_maxPayloadSize(), payloadMaxInKB));
102 
103  //Force a background task.
104  displayDropDownListener.actionPerformed(null);
105  }
106 
107  @NbBundle.Messages({"TranslatedTextViewer.title=Translation"})
108  @Override
109  public String getTitle() {
110  return Bundle.TranslatedTextViewer_title();
111  }
112 
113  @NbBundle.Messages({"TranslatedTextViewer.toolTip=Displays translated file text."})
114  @Override
115  public String getToolTip() {
116  return Bundle.TranslatedTextViewer_toolTip();
117  }
118 
119  @Override
120  public TextViewer createInstance() {
121  return new TranslatedTextViewer();
122  }
123 
124  @Override
125  public Component getComponent() {
126  return panel;
127  }
128 
129  @Override
130  public void resetComponent() {
131  panel.reset();
132  this.node = null;
133  if (backgroundTask != null) {
134  backgroundTask.cancel(true);
135  }
136  backgroundTask = null;
137  }
138 
139  @Override
140  public boolean isSupported(Node node) {
141  if (null == node) {
142  return false;
143  }
144 
146  return false;
147  }
148 
149  AbstractFile file = node.getLookup().lookup(AbstractFile.class);
150  return file != null;
151  }
152 
153  @Override
154  public int isPreferred(Node node) {
155  // Returning zero makes it unlikely this object will be the preferred content viewer,
156  // i.e., the active tab, when the content viewers are first displayed.
157  return 0;
158  }
159 
163  private class ExtractAndTranslateTextTask extends SwingWorker<String, Void> {
164 
165  private final AbstractFile file;
166  private final boolean translateText;
167 
168  private ExtractAndTranslateTextTask(AbstractFile file, boolean translateText) {
169  this.file = file;
170  this.translateText = translateText;
171  }
172 
173  @NbBundle.Messages({
174  "TranslatedContentViewer.extractingText=Extracting text, please wait...",
175  "TranslatedContentViewer.translatingText=Translating text, please wait...",
176  "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).",
177  "TranslatedContentViewer.fileHasNoText=File has no text.",
178  "TranslatedContentViewer.noServiceProvider=The machine translation software was not found.",
179  "# {0} - exception message", "TranslatedContentViewer.translationException=An error occurred while translating the text ({0})."
180  })
181  @Override
182  public String doInBackground() throws InterruptedException {
183  if (this.isCancelled()) {
184  throw new InterruptedException();
185  }
186 
187  SwingUtilities.invokeLater(() -> {
188  panel.display(Bundle.TranslatedContentViewer_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
189  });
190  String fileText;
191  try {
192  fileText = getFileText(file);
193  } catch (IOException | TextExtractor.InitReaderException ex) {
194  logger.log(Level.WARNING, String.format("Error extracting text for file %s (objId=%d)", file.getName(), file.getId()), ex);
195  return Bundle.TranslatedContentViewer_errorExtractingText(ex.getMessage());
196  }
197 
198  if (this.isCancelled()) {
199  throw new InterruptedException();
200  }
201 
202  if (fileText == null || fileText.isEmpty()) {
203  return Bundle.TranslatedContentViewer_fileHasNoText();
204  }
205 
206  if (!this.translateText) {
207  return fileText;
208  }
209 
210  SwingUtilities.invokeLater(() -> {
211  panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
212  });
213  String translation;
214  try {
215  translation = translate(fileText);
216  } catch (NoServiceProviderException ex) {
217  logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex);
218  translation = Bundle.TranslatedContentViewer_noServiceProvider();
219  } catch (TranslationException ex) {
220  logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex);
221  translation = Bundle.TranslatedContentViewer_translationException(ex.getMessage());
222  }
223 
224  if (this.isCancelled()) {
225  throw new InterruptedException();
226  }
227 
228  return translation;
229  }
230 
231  @Override
232  public void done() {
233  try {
234  String result = get();
235  if (this.isCancelled()) {
236  throw new InterruptedException();
237  }
238  int len = result.length();
239  int maxOrientChars = Math.min(len, 1024);
240  String orientDetectSubstring = result.substring(0, maxOrientChars);
241  ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring);
242  panel.display(result, orientation, Font.PLAIN);
243 
244  } catch (InterruptedException | CancellationException ignored) {
245  // Task cancelled, no error.
246  } catch (ExecutionException ex) {
247  logger.log(Level.WARNING, String.format("Error occurred during background task execution for file %s (objId=%d)", file.getName(), file.getId()), ex);
248  panel.display(Bundle.TranslatedContentViewer_translationException(ex.getMessage()), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
249  }
250  }
251 
259  @NbBundle.Messages({
260  "TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text."
261  })
262  private String translate(String input) throws NoServiceProviderException, TranslationException {
264  String translatedResult = translatorInstance.translate(input);
265  if (translatedResult.isEmpty()) {
266  return Bundle.TranslatedContentViewer_emptyTranslation();
267  }
268  return translatedResult;
269  }
270 
283  private String getFileText(AbstractFile file) throws IOException,
284  InterruptedException, TextExtractor.InitReaderException {
285 
286  final boolean isImage = file.getMIMEType().toLowerCase().startsWith("image/"); // NON-NLS
287  String result;
288  if (isImage) {
289  result = extractText(file, OCR_ENABLED);
290  } else {
291  result = extractText(file, OCR_DISABLED);
292  }
293 
294  //Correct for UTF-8
295  byte[] resultInUTF8Bytes = result.getBytes("UTF8");
296  byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0,
297  Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES));
298  return new String(trimToArraySize, "UTF-8");
299  }
300 
314  private String extractText(AbstractFile source, boolean ocrEnabled) throws IOException, InterruptedException, TextExtractor.InitReaderException {
315  Reader textExtractor = getTextExtractor(source, ocrEnabled);
316 
317  char[] cbuf = new char[8096];
318  StringBuilder textBuilder = new StringBuilder();
319 
320  //bytesRead can be an int so long as the max file size
321  //is sufficiently small
322  int bytesRead = 0;
323  int read;
324 
325  while ((read = textExtractor.read(cbuf)) != -1) {
326  if (this.isCancelled()) {
327  throw new InterruptedException();
328  }
329 
330  //Short-circuit the read if its greater than our max
331  //translatable size
332  int bytesLeft = MAX_EXTRACT_SIZE_BYTES - bytesRead;
333 
334  if (bytesLeft < read) {
335  textBuilder.append(cbuf, 0, bytesLeft);
336  return textBuilder.toString();
337  }
338 
339  textBuilder.append(cbuf, 0, read);
340  bytesRead += read;
341  }
342 
343  return textBuilder.toString();
344  }
345 
359  private Reader getTextExtractor(AbstractFile file, boolean ocrEnabled) throws IOException,
361  Lookup context = null;
362 
363  if (ocrEnabled) {
364  ImageConfig imageConfig = new ImageConfig();
365  imageConfig.setOCREnabled(true);
366 
367  String ocrSelection = panel.getSelectedOcrLanguagePack();
368  if (!ocrSelection.isEmpty()) {
369  imageConfig.setOCRLanguages(Lists.newArrayList(ocrSelection));
370  }
371 
372  //Terminate any OS process running in the extractor if this
373  //SwingWorker has been cancelled.
374  ProcessTerminator terminator = () -> isCancelled();
375  context = Lookups.fixed(imageConfig, terminator);
376  }
377 
378  try {
379  return TextExtractorFactory.getExtractor(file, context).getReader();
381  //Fall-back onto the strings extractor
382  return TextExtractorFactory.getStringsExtractor(file, context).getReader();
383  }
384  }
385  }
386 
391  private abstract class SelectionChangeListener implements ActionListener {
392 
393  private String currentSelection;
394 
395  abstract String getSelection();
396 
397  @Override
398  public final void actionPerformed(ActionEvent e) {
399  String selection = getSelection();
400  if (!selection.equals(currentSelection)) {
401  currentSelection = selection;
402 
403  if (backgroundTask != null && !backgroundTask.isDone()) {
404  backgroundTask.cancel(true);
405  }
406 
407  AbstractFile file = node.getLookup().lookup(AbstractFile.class);
408  String textDisplaySelection = panel.getDisplayDropDownSelection();
409  boolean translateText = !textDisplaySelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString());
410  backgroundTask = new ExtractAndTranslateTextTask(file, translateText);
411 
412  //Pass the background task to a single threaded pool to keep
413  //the number of jobs running to one.
414  executorService.execute(backgroundTask);
415  }
416  }
417  }
418 
423 
424  @Override
425  String getSelection() {
426  return panel.getDisplayDropDownSelection();
427  }
428  }
429 
434 
435  @Override
436  String getSelection() {
437  return panel.getSelectedOcrLanguagePack();
438  }
439  }
440 }
void display(String text, ComponentOrientation direction, int font)
static TextExtractor getStringsExtractor(Content content, Lookup context)
static TextExtractor getExtractor(Content content, Lookup context)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static ComponentOrientation getTextDirection(String text)
Definition: TextUtil.java:36

Copyright © 2012-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.