Autopsy  4.12.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;
59 
63 @ServiceProvider(service = TextViewer.class, position = 4)
64 public final class TranslatedTextViewer implements TextViewer {
65 
66  private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName());
67 
68  private static final boolean OCR_ENABLED = true;
69  private static final boolean OCR_DISABLED = false;
70  private static final int MAX_EXTRACT_SIZE_BYTES = 25600;
71  private static final List<String> INSTALLED_LANGUAGE_PACKS = PlatformUtil.getOcrLanguagePacks();
72  private final TranslationContentPanel panel = new TranslationContentPanel();
73 
74  private volatile Node node;
76  private final ThreadFactory translationThreadFactory
77  = new ThreadFactoryBuilder().setNameFormat("translation-content-viewer-%d").build();
78  private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory);
79 
80  @NbBundle.Messages({
81  "TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated"
82  })
83  @Override
84  public void setNode(final Node node) {
85  this.node = node;
86  SelectionChangeListener displayDropDownListener = new DisplayDropDownChangeListener();
87  panel.addDisplayTextActionListener(displayDropDownListener);
88  panel.addOcrDropDownActionListener(new OCRDropdownChangeListener());
89  Content source = DataContentViewerUtility.getDefaultContent(node);
90 
91  if (source instanceof AbstractFile) {
92  boolean isImage = ((AbstractFile) source).getMIMEType().toLowerCase().startsWith("image/");
93  if (isImage) {
94  panel.enableOCRSelection(OCR_ENABLED);
95  panel.addLanguagePackNames(INSTALLED_LANGUAGE_PACKS);
96  }
97  }
98 
99  int payloadMaxInKB = TextTranslationService.getInstance().getMaxTextChars() / 1000;
100  panel.setWarningLabelMsg(String.format(Bundle.TranslatedTextViewer_maxPayloadSize(), payloadMaxInKB));
101 
102  //Force a background task.
103  displayDropDownListener.actionPerformed(null);
104  }
105 
106  @NbBundle.Messages({"TranslatedTextViewer.title=Translation"})
107  @Override
108  public String getTitle() {
109  return Bundle.TranslatedTextViewer_title();
110  }
111 
112  @NbBundle.Messages({"TranslatedTextViewer.toolTip=Displays translated file text."})
113  @Override
114  public String getToolTip() {
115  return Bundle.TranslatedTextViewer_toolTip();
116  }
117 
118  @Override
119  public TextViewer createInstance() {
120  return new TranslatedTextViewer();
121  }
122 
123  @Override
124  public Component getComponent() {
125  return panel;
126  }
127 
128  @Override
129  public void resetComponent() {
130  panel.reset();
131  this.node = null;
132  if (updateTask != null) {
133  updateTask.cancel(true);
134  }
135  updateTask = null;
136  }
137 
138  @Override
139  public boolean isSupported(Node node) {
140  if (null == node) {
141  return false;
142  }
143 
145  return false;
146  }
147 
148  AbstractFile file = node.getLookup().lookup(AbstractFile.class);
149  return file != null;
150  }
151 
152  @Override
153  public int isPreferred(Node node) {
154  // Returning zero makes it unlikely this object will be the preferred content viewer,
155  // i.e., the active tab, when the content viewers are first displayed.
156  return 0;
157  }
158 
162  private class BackgroundTranslationTask extends SwingWorker<String, Void> {
163 
164  @NbBundle.Messages({
165  "TranslatedContentViewer.noIndexedTextMsg=Run the Keyword Search Ingest Module to get text for translation.",
166  "TranslatedContentViewer.textAlreadyIndexed=Please view the original text in the Indexed Text viewer.",
167  "TranslatedContentViewer.errorMsg=Error encountered while getting file text.",
168  "TranslatedContentViewer.errorExtractingText=Could not extract text from file.",
169  "TranslatedContentViewer.translatingText=Translating text, please wait..."
170  })
171  @Override
172  public String doInBackground() throws InterruptedException {
173  if (this.isCancelled()) {
174  throw new InterruptedException();
175  }
176  String dropdownSelection = panel.getDisplayDropDownSelection();
177 
178  if (dropdownSelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString())) {
179  try {
180  return getFileText(node);
181  } catch (IOException ex) {
182  logger.log(Level.WARNING, "Error getting text", ex);
183  return Bundle.TranslatedContentViewer_errorMsg();
184  } catch (TextExtractor.InitReaderException ex) {
185  logger.log(Level.WARNING, "Error getting text", ex);
186  return Bundle.TranslatedContentViewer_errorExtractingText();
187  }
188  } else {
189  try {
190  return translate(getFileText(node));
191  } catch (IOException ex) {
192  logger.log(Level.WARNING, "Error translating text", ex);
193  return Bundle.TranslatedContentViewer_errorMsg();
194  } catch (TextExtractor.InitReaderException ex) {
195  logger.log(Level.WARNING, "Error translating text", ex);
196  return Bundle.TranslatedContentViewer_errorExtractingText();
197  }
198  }
199  }
200 
206  @NbBundle.Messages({"TranslatedContentViewer.extractingImageText=Extracting text from image, please wait...",
207  "TranslatedContentViewer.extractingFileText=Extracting text from file, please wait...",})
208  private void updateExtractionLoadingMessage(boolean isImage) {
209  if (isImage) {
210  panel.display(Bundle.TranslatedContentViewer_extractingImageText(),
211  ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
212  } else {
213  panel.display(Bundle.TranslatedContentViewer_extractingFileText(),
214  ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
215  }
216  }
217 
218  @Override
219  public void done() {
220  try {
221  String result = get();
222  if (this.isCancelled()) {
223  throw new InterruptedException();
224  }
225  int len = result.length();
226  int maxOrientChars = Math.min(len, 1024);
227  String orientDetectSubstring = result.substring(0, maxOrientChars);
228  ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring);
229  panel.display(result, orientation, Font.PLAIN);
230  } catch (InterruptedException | ExecutionException | CancellationException ignored) {
231  //InterruptedException & CancellationException - User cancelled, no error.
232  }
233  }
234 
242  @NbBundle.Messages({
243  "TranslatedContentViewer.emptyTranslation=The resulting translation was empty.",
244  "TranslatedContentViewer.noServiceProvider=Machine Translation software was not found.",
245  "TranslatedContentViewer.translationException=Error encountered while attempting translation."})
246  private String translate(String input) throws InterruptedException {
247  if (this.isCancelled()) {
248  throw new InterruptedException();
249  }
250 
251  panel.display(Bundle.TranslatedContentViewer_translatingText(),
252  ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
253 
254  try {
256  String translatedResult = translatorInstance.translate(input);
257  if (translatedResult.isEmpty()) {
258  return Bundle.TranslatedContentViewer_emptyTranslation();
259  }
260  return translatedResult;
261  } catch (NoServiceProviderException ex) {
262  return Bundle.TranslatedContentViewer_noServiceProvider();
263  } catch (TranslationException ex) {
264  logger.log(Level.WARNING, "Error translating text", ex);
265  return Bundle.TranslatedContentViewer_translationException() + " (" + ex.getMessage() + ")";
266  }
267  }
268 
283  private String getFileText(Node node) throws IOException,
284  InterruptedException, TextExtractor.InitReaderException {
285 
286  AbstractFile source = (AbstractFile) DataContentViewerUtility.getDefaultContent(node);
287  boolean isImage = false;
288 
289  if (source != null) {
290  isImage = source.getMIMEType().toLowerCase().startsWith("image/");
291  }
292 
293  updateExtractionLoadingMessage(isImage);
294 
295  String result;
296 
297  if (isImage) {
298  result = extractText(source, OCR_ENABLED);
299  } else {
300  result = extractText(source, OCR_DISABLED);
301  }
302 
303  //Correct for UTF-8
304  byte[] resultInUTF8Bytes = result.getBytes("UTF8");
305  byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0,
306  Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES) );
307  return new String(trimToArraySize, "UTF-8");
308  }
309 
323  private String extractText(AbstractFile source, boolean ocrEnabled) throws IOException, InterruptedException, TextExtractor.InitReaderException {
324  Reader textExtractor = getTextExtractor(source, ocrEnabled);
325 
326  char[] cbuf = new char[8096];
327  StringBuilder textBuilder = new StringBuilder();
328 
329  //bytesRead can be an int so long as the max file size
330  //is sufficiently small
331  int bytesRead = 0;
332  int read;
333 
334  while ((read = textExtractor.read(cbuf)) != -1) {
335  if (this.isCancelled()) {
336  throw new InterruptedException();
337  }
338 
339  //Short-circuit the read if its greater than our max
340  //translatable size
341  int bytesLeft = MAX_EXTRACT_SIZE_BYTES - bytesRead;
342 
343  if (bytesLeft < read) {
344  textBuilder.append(cbuf, 0, bytesLeft);
345  return textBuilder.toString();
346  }
347 
348  textBuilder.append(cbuf, 0, read);
349  bytesRead += read;
350  }
351 
352  return textBuilder.toString();
353  }
354 
368  private Reader getTextExtractor(AbstractFile file, boolean ocrEnabled) throws IOException,
370  Lookup context = null;
371 
372  if (ocrEnabled) {
373  ImageConfig imageConfig = new ImageConfig();
374  imageConfig.setOCREnabled(true);
375 
376  String ocrSelection = panel.getSelectedOcrLanguagePack();
377  if (!ocrSelection.isEmpty()) {
378  imageConfig.setOCRLanguages(Lists.newArrayList(ocrSelection));
379  }
380 
381  //Terminate any OS process running in the extractor if this
382  //SwingWorker has been cancelled.
383  ProcessTerminator terminator = () -> isCancelled();
384  context = Lookups.fixed(imageConfig, terminator);
385  }
386 
387  try {
388  return TextExtractorFactory.getExtractor(file, context).getReader();
390  //Fall-back onto the strings extractor
391  return TextExtractorFactory.getStringsExtractor(file, context).getReader();
392  }
393  }
394  }
395 
400  private abstract class SelectionChangeListener implements ActionListener {
401 
402  public String currentSelection = null;
403 
404  public abstract String getSelection();
405 
406  @Override
407  public final void actionPerformed(ActionEvent e) {
408  String selection = getSelection();
409  if (!selection.equals(currentSelection)) {
410  currentSelection = selection;
411  if (updateTask != null && !updateTask.isDone()) {
412  updateTask.cancel(true);
413  }
414  updateTask = new BackgroundTranslationTask();
415 
416  //Pass the background task to a single threaded pool to keep
417  //the number of jobs running to one.
418  executorService.execute(updateTask);
419  }
420  }
421  }
422 
427 
428  @Override
429  public String getSelection() {
430  return panel.getDisplayDropDownSelection();
431  }
432  }
433 
438 
439  @Override
440  public String getSelection() {
441  return panel.getSelectedOcrLanguagePack();
442  }
443  }
444 }
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-2018 Basis Technology. Generated on: Wed Sep 18 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.