From ff8f444a4c4d79957242259fc5bdb92e946b66a8 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 22 May 2019 09:53:37 -0400 Subject: [PATCH 001/453] inital commit of email threader --- .../EmailMessageThreader.java | 205 ++++++++++++++++++ .../ThunderbirdMboxFileIngestModule.java | 3 +- 2 files changed, 207 insertions(+), 1 deletion(-) create mode 100755 thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java new file mode 100755 index 0000000000..0061ef6c88 --- /dev/null +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java @@ -0,0 +1,205 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.thunderbirdparser; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * + * + */ +final class EmailMessageThreader { + + public void threadMessages(List emailMessages) { + HashMap id_table = createIDTable(emailMessages); + Set rootSet = getRootSet(id_table); + + for(Container container: rootSet) { + System.out.println(container.getMessage().getSubject()); + + Container child = container.getChild(); + String tabs = "\t"; + while(child != null) { + System.out.println(tabs + child.getMessage().getSubject()); + + child = child.getChild(); + tabs = tabs + "\t"; + } + } + } + + private HashMap createIDTable(List emailMessages) { + HashMap id_table = new HashMap<>(); + + for(EmailMessage message: emailMessages) { + String messageID = message.getMessageID(); + + Container container = id_table.get(messageID); + if(container == null) { + container = new Container(message); + id_table.put(messageID, container); + } else { + if(container.getMessage() == null) { + container.setMessage(message); + } + } + + + + List referenceList = message.getReferences(); + + if(referenceList != null) { + Container parent = null; + Container current; + + for(String refID: referenceList){ + current = id_table.get(refID); + + if(current == null ) { + current = new Container(); + id_table.put(refID, current); + } + + // If a link already exists don't change it; + + if(parent != null) { + parent.setChild(current); + + Container grandparent = parent.getParent(); + if(grandparent != null) { + grandparent.setNextSibling(current); + } + + } + + current.setParent(parent); + parent = current; + } + + // + parent = id_table.get(referenceList.get(referenceList.size() - 1)); + Container oldParent = container.getParent(); + if(oldParent != parent) { + container.setParent(parent); + parent.setChild(container); + parent.setNextSibling(container.getChild()); + + if(oldParent != null) { + oldParent.setChild(null); + oldParent.setNextSibling(null); + } + } + } + } + + return id_table; + } + + Set getRootSet(HashMap id_table) { + HashSet rootSet = new HashSet<>(); + + id_table.values().stream().filter((container) -> + (!container.hasParent())).forEachOrdered((container) -> { + rootSet.add(container); + }); + + return rootSet; + } + + void pruneEmptyContainers(Set rootSet) { + for(Container container: rootSet) { + if(!container.hasMessage()) { // Container doesn't have message + if(!container.hasChild()) { + rootSet.remove(container); + } else { + if(!container.hasNextSibling()) { // Container has no message, but one child + rootSet.remove(container); // prompot the child to the rootSet + rootSet.add(container.child); + } else { + pruneChildren(container); + } + } + } + } + } + + void pruneChildren(Container container) { + if(!container.hasMessage()) { + if(container.hasChild()) { + Container parent = container.getParent(); + parent.setChild(container.getChild()); + parent.setNextSibling(container.getNextSibling()); + container.getChild().setParent(parent); + + pruneChildren(container.child); + } + } + } + + final class Container{ + private EmailMessage message; + private Container parent; + private Container child; // firt Child + private Container next; // next element in siblint list or null; + + Container() {} + + Container(EmailMessage message) { + this.message = message; + } + + EmailMessage getMessage() { + return message; + } + + void setMessage(EmailMessage message) { + this.message = message; + } + + boolean hasMessage() { + return message != null; + } + + Container getParent() { + return parent; + } + + void setParent(Container container) { + parent = container; + } + + boolean hasParent() { + return parent != null; + } + + Container getChild() { + return child; + } + + void setChild(Container container) { + child = container; + } + + boolean hasChild() { + return child != null; + } + + Container getNextSibling() { + return next; + } + + void setNextSibling(Container container) { + next = container; + } + + boolean hasNextSibling() { + return next != null; + } + } +} diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index d5cb95bcc2..a6728507ea 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -410,7 +410,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { private void processEmails(List emails, AbstractFile abstractFile) { List derivedFiles = new ArrayList<>(); - + EmailMessageThreader threader = new EmailMessageThreader(); + threader.threadMessages(emails); for (EmailMessage email : emails) { BlackboardArtifact msgArtifact = addEmailArtifact(email, abstractFile); From 638750c7201d860a7136919b70972f1267b67a97 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 23 May 2019 11:46:47 -0400 Subject: [PATCH 002/453] Checkin of the current state of the UI, will continue to make improvements --- .../contentviewers/MediaViewImagePanel.form | 55 ++++ .../contentviewers/MediaViewImagePanel.java | 194 ++++++-------- .../imagetagging/ControlType.java | 32 +++ .../imagetagging/FocusChangeEvent.java | 49 ++++ .../imagetagging/FocusChangeListener.java | 18 ++ .../imagetagging/ImageTagCreator.java | 145 +++++++++++ .../imagetagging/StoredTag.java | 239 ++++++++++++++++++ .../imagetagging/StoredTagEvent.java | 40 +++ .../imagetagging/StoredTagListener.java | 33 +++ .../imagetagging/TopLevelTagsGroup.java | 98 +++++++ 10 files changed, 788 insertions(+), 115 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ControlType.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form index e057337e6f..fb17c441db 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form @@ -1,6 +1,31 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + @@ -200,6 +225,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index 5c16dbed93..ebf7f48444 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -18,6 +18,11 @@ */ package org.sleuthkit.autopsy.contentviewers; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagCreator; +import org.sleuthkit.autopsy.contentviewers.imagetagging.StoredTag; +import org.sleuthkit.autopsy.contentviewers.imagetagging.StoredTagListener; +import org.sleuthkit.autopsy.contentviewers.imagetagging.TopLevelTagsGroup; +import org.sleuthkit.autopsy.contentviewers.imagetagging.StoredTagEvent; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.util.Collections; @@ -32,6 +37,7 @@ import javafx.embed.swing.JFXPanel; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.Cursor; +import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -49,11 +55,8 @@ import javax.swing.JPanel; import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; import org.python.google.common.collect.Lists; -import javafx.scene.Group; -import javafx.scene.input.MouseEvent; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ControlType; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; @@ -74,9 +77,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private final boolean fxInited; private JFXPanel fxPanel; - private Group imageGroup; - private ImageTaggingTool tagger; + private TopLevelTagsGroup imageGroup; + private ImageTagCreator tagger; private ImageView fxImageView; + private Node focusedNode; private ScrollPane scrollPane; private final ProgressBar progressBar = new ProgressBar(); private final MaskerPane maskerPane = new MaskerPane(); @@ -121,8 +125,18 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan // build jfx ui (we could do this in FXML?) fxImageView = new ImageView(); // will hold image - imageGroup = new Group(); - imageGroup.getChildren().add(fxImageView); + imageGroup = new TopLevelTagsGroup(fxImageView); + deleteTagButton.setEnabled(false); + imageGroup.addFocusChangeListener((event) -> { + if(event.getType() == ControlType.NOT_FOCUSED || event.getNode() == fxImageView) { + deleteTagButton.setEnabled(false); + createTagButton.setEnabled(true); + } else if (event.getType() == ControlType.FOCUSED) { + deleteTagButton.setEnabled(true); + createTagButton.setEnabled(false); + focusedNode = event.getNode(); + } + }); scrollPane = new ScrollPane(imageGroup); // scrolls and sizes imageview scrollPane.getStyleClass().add("bg"); //NOI18N scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED); @@ -154,8 +168,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan Platform.runLater(() -> { fxImageView.setViewport(new Rectangle2D(0, 0, 0, 0)); fxImageView.setImage(null); - tagger.defaultSettings(); - + imageGroup.getChildren().clear(); scrollPane.setContent(null); scrollPane.setContent(imageGroup); }); @@ -205,11 +218,17 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan try { Image fxImage = readImageTask.get(); + imageGroup.getChildren().clear(); + imageGroup.getChildren().add(fxImageView); if (nonNull(fxImage)) { // We have a non-null image, so let's show it. fxImageView.setImage(fxImage); - imageGroup.getChildren().remove(tagger); - tagger = new ImageTaggingTool(fxImageView, Color.RED); + tagger = new ImageTagCreator(fxImageView); + StoredTagListener newTagListener = (StoredTagEvent evt) -> { + StoredTag tag = evt.getTag(); + imageGroup.getChildren().add(tag); + }; + tagger.addNewTagListener(newTagListener); imageGroup.getChildren().add(tagger); resetView(); scrollPane.setContent(imageGroup); @@ -293,6 +312,9 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan // //GEN-BEGIN:initComponents private void initComponents() { + jMenu1 = new javax.swing.JMenu(); + jPopupMenu1 = new javax.swing.JPopupMenu(); + jPopupMenu2 = new javax.swing.JPopupMenu(); toolbar = new javax.swing.JToolBar(); rotationTextField = new javax.swing.JTextField(); rotateLeftButton = new javax.swing.JButton(); @@ -303,6 +325,12 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan zoomInButton = new javax.swing.JButton(); jSeparator2 = new javax.swing.JToolBar.Separator(); zoomResetButton = new javax.swing.JButton(); + jSeparator3 = new javax.swing.JToolBar.Separator(); + createTagButton = new javax.swing.JButton(); + jSeparator4 = new javax.swing.JToolBar.Separator(); + deleteTagButton = new javax.swing.JButton(); + + org.openide.awt.Mnemonics.setLocalizedText(jMenu1, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.jMenu1.text")); // NOI18N setBackground(new java.awt.Color(0, 0, 0)); addComponentListener(new java.awt.event.ComponentAdapter() { @@ -406,6 +434,30 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan } }); toolbar.add(zoomResetButton); + toolbar.add(jSeparator3); + + org.openide.awt.Mnemonics.setLocalizedText(createTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.createTagButton.text")); // NOI18N + createTagButton.setFocusable(false); + createTagButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + createTagButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + createTagButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + createTagButtonActionPerformed(evt); + } + }); + toolbar.add(createTagButton); + toolbar.add(jSeparator4); + + org.openide.awt.Mnemonics.setLocalizedText(deleteTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.deleteTagButton.text")); // NOI18N + deleteTagButton.setFocusable(false); + deleteTagButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + deleteTagButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + deleteTagButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteTagButtonActionPerformed(evt); + } + }); + toolbar.add(deleteTagButton); add(toolbar); }// //GEN-END:initComponents @@ -450,9 +502,24 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan updateView(); }//GEN-LAST:event_formComponentResized + private void deleteTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTagButtonActionPerformed + imageGroup.deleteNode(focusedNode); + }//GEN-LAST:event_deleteTagButtonActionPerformed + + private void createTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createTagButtonActionPerformed + + }//GEN-LAST:event_createTagButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton createTagButton; + private javax.swing.JButton deleteTagButton; + private javax.swing.JMenu jMenu1; + private javax.swing.JPopupMenu jPopupMenu1; + private javax.swing.JPopupMenu jPopupMenu2; private javax.swing.JToolBar.Separator jSeparator1; private javax.swing.JToolBar.Separator jSeparator2; + private javax.swing.JToolBar.Separator jSeparator3; + private javax.swing.JToolBar.Separator jSeparator4; private javax.swing.JButton rotateLeftButton; private javax.swing.JButton rotateRightButton; private javax.swing.JTextField rotationTextField; @@ -618,107 +685,4 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan rotationTextField.setText((int) rotation + "°"); zoomTextField.setText((Math.round(zoomRatio * 100.0)) + "%"); } - - /** - * Enables users to 'tag' a region of an image by clicking and dragging a - * rectangle overtop. - */ - class ImageTaggingTool extends Rectangle { - - private final double imageWidth; - private final double imageHeight; - private final double imageOriginX; - private final double imageOriginY; - - //Origin of the drag event. - private double rectangleOriginX; - private double rectangleOriginY; - - //Rectangle lines should be 1.5% of the image. This level of thickness has - //a good balance between visual acuity and loss of selection at the borders - //of the image. - private double lineThicknessAsPercent = 1.5; - - /** - * Adds tagging support to an image, where the 'tag' rectangle will be - * the specified color. - * - * @param image Image to tag - * @param color Color of the 'tag' rectangle - */ - private ImageTaggingTool(ImageView image, Color color) { - defaultSettings(); - - imageWidth = image.getImage().getWidth(); - imageHeight = image.getImage().getHeight(); - imageOriginX = image.getX(); - imageOriginY = image.getY(); - - setStroke(color); - setFill(color.deriveColor(0, 0, 0, 0)); - - //Calculate how many pixels the stroke width should be to guarentee - //a consistent % of image consumed by the rectangle border. - double min = Math.min(imageWidth, imageHeight); - double lineThicknessPixels = min * lineThicknessAsPercent / 100.0; - setStrokeWidth(lineThicknessPixels); - setVisible(false); - - //Create a rectangle by left clicking on the image - image.setOnMousePressed((MouseEvent event) -> { - if (event.isSecondaryButtonDown()) { - return; - } - - //Reset box on new click. - defaultSettings(); - - rectangleOriginX = event.getX(); - rectangleOriginY = event.getY(); - - setX(rectangleOriginX); - setY(rectangleOriginY); - }); - - //Adjust the rectangle by dragging the left mouse button - image.setOnMouseDragged((MouseEvent event) -> { - if (event.isSecondaryButtonDown()) { - return; - } - - /** - * Ensure the rectangle is contained within image boundaries and - * that the line thickness is kept within bounds. - */ - double newX = Math.min(Math.max(event.getX(), imageOriginX) - + lineThicknessPixels / 2, imageWidth - lineThicknessPixels / 2); - double newY = Math.min(Math.max(event.getY(), imageOriginY) - + lineThicknessPixels / 2, imageHeight - lineThicknessPixels / 2); - - setVisible(true); - double offsetX = newX - rectangleOriginX; - if (offsetX < 0) { - setX(newX); - } - setWidth(Math.abs(offsetX)); - - double offsetY = newY - rectangleOriginY; - if (offsetY < 0) { - setY(newY); - } - setHeight(Math.abs(offsetY)); - }); - } - - /** - * Reset the rectangle to default dimensions. - */ - public final void defaultSettings() { - setX(0); - setY(0); - setWidth(0); - setHeight(0); - setVisible(false); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ControlType.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ControlType.java new file mode 100755 index 0000000000..6e0c6a2493 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ControlType.java @@ -0,0 +1,32 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import javafx.event.Event; +import javafx.event.EventType; + +/** + * + * @author dsmyda + */ +public class ControlType { + public static final EventType NOT_FOCUSED = new EventType<>("NOT_FOCUSED"); + public static final EventType FOCUSED = new EventType<>("FOCUSED"); + public static final EventType DELETE = new EventType<>("DELETE"); +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java new file mode 100755 index 0000000000..e487e01061 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java @@ -0,0 +1,49 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import java.util.EventObject; +import javafx.event.Event; +import javafx.event.EventType; +import javafx.scene.Node; + +/** + * + * @author dsmyda + */ +public final class FocusChangeEvent extends EventObject{ + + private final EventType type; + private final Node focused; + + public FocusChangeEvent(Object source, EventType type, Node focused) { + super(source); + this.type = type; + this.focused = focused; + } + + public EventType getType() { + return type; + } + + public Node getNode() { + return focused; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java new file mode 100755 index 0000000000..6699e57002 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java @@ -0,0 +1,18 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import java.util.EventListener; + +/** + * + * @author dsmyda + */ +@FunctionalInterface +public interface FocusChangeListener extends EventListener{ + + void focusChanged(FocusChangeEvent event); +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java new file mode 100755 index 0000000000..146fbe1281 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java @@ -0,0 +1,145 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import java.util.ArrayList; +import java.util.Collection; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; + +/** + * Creates image tags. This tool can be treated like any other JavaFX node. + */ +public final class ImageTagCreator extends Rectangle { + + //Origin of the drag event. + private double rectangleOriginX, rectangleOriginY; + + //Rectangle lines should be 1.5% of the image. This level of thickness has + //a good balance between visual acuity and loss of selection at the borders + //of the image. + private double lineThicknessAsPercent = 1.5; + private final double minArea; + private final Collection listeners; + + /** + * Adds tagging support to an image, where the 'tag' rectangle will be the + * specified color. + * + * @param image Image to tag + */ + public ImageTagCreator(ImageView image) { + listeners = new ArrayList<>(); + + setStroke(Color.RED); + setFill(Color.RED.deriveColor(0, 0, 0, 0)); + + //Calculate how many pixels the stroke width should be to guarentee + //a consistent % of image consumed by the rectangle border. + double min = Math.min(image.getImage().getWidth(), image.getImage().getHeight()); + double lineThicknessPixels = min * lineThicknessAsPercent / 100.0; + setStrokeWidth(lineThicknessPixels); + minArea = lineThicknessPixels * lineThicknessPixels; + setVisible(false); + + this.addEventHandler(ControlType.NOT_FOCUSED, (event) -> defaultSettings()); + + //Create a rectangle by left clicking on the image + image.setOnMousePressed((MouseEvent event) -> { + if (event.isSecondaryButtonDown()) { + return; + } + + //Reset box on new click. + defaultSettings(); + rectangleOriginX = event.getX(); + rectangleOriginY = event.getY(); + + setX(rectangleOriginX); + setY(rectangleOriginY); + }); + + //Adjust the rectangle by dragging the left mouse button + image.setOnMouseDragged((MouseEvent event) -> { + if (event.isSecondaryButtonDown()) { + return; + } + + double currentX = event.getX(), currentY = event.getY(); + + /** + * Ensure the rectangle is contained within image boundaries and + * that the line thickness is kept within bounds. + */ + double newX = Math.min(Math.max(currentX, image.getX()) + + lineThicknessPixels / 2, image.getImage().getWidth() - lineThicknessPixels / 2); + double newY = Math.min(Math.max(currentY, image.getY()) + + lineThicknessPixels / 2, image.getImage().getHeight() - lineThicknessPixels / 2); + + setVisible(true); + double offsetX = newX - rectangleOriginX; + if (offsetX < 0) { + setX(newX); + } + setWidth(Math.abs(offsetX)); + + double offsetY = newY - rectangleOriginY; + if (offsetY < 0) { + setY(newY); + } + setHeight(Math.abs(offsetY)); + }); + + image.setOnMouseReleased(event -> { + if ((this.getWidth() - this.getStrokeWidth()) * + (this.getHeight() - this.getStrokeWidth()) <= minArea) { + defaultSettings(); + return; + } + + //TODO - persist tag. + + //Notify listeners + StoredTagEvent newTagEvent = new StoredTagEvent(this, new StoredTag(image, this.getX(), this.getY(), + this.getX() + this.getWidth(), this.getY() + this.getHeight())); + + listeners.forEach((listener) -> { + listener.newTagEvent(newTagEvent); + }); + + defaultSettings(); + }); + } + + public void addNewTagListener(StoredTagListener listener) { + listeners.add(listener); + } + + /** + * Reset the rectangle to default dimensions. + */ + private void defaultSettings() { + setWidth(0); + setHeight(0); + setVisible(false); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java new file mode 100755 index 0000000000..6670639811 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java @@ -0,0 +1,239 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import com.sun.javafx.event.EventDispatchChainImpl; +import javafx.collections.ListChangeListener; +import javafx.scene.Cursor; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; + +/** + * + * @author dsmyda + */ +public final class StoredTag extends Group { + + private final EventDispatchChainImpl ALL_CHILDREN; + private final PhysicalTag physicalTag; + + public StoredTag(ImageView image, double x, double y, double x1, double y1) { + ALL_CHILDREN = new EventDispatchChainImpl(); + + this.getChildren().addListener((ListChangeListener) change -> { + change.next(); + change.getAddedSubList().forEach((node) -> ALL_CHILDREN.append(node.getEventDispatcher())); + }); + + double min = Math.min(image.getImage().getWidth(), image.getImage().getHeight()); + double lineThicknessPixels = min * 1.5 / 100.0; + physicalTag = new PhysicalTag(image); + physicalTag.setStrokeWidth(lineThicknessPixels); + + EditHandle bottomLeft = new EditHandle(); + bottomLeft.setPosition(Position.bottom(), Position.left()); + bottomLeft.setDrag(Drag.bottom(), Drag.left()); + + EditHandle bottomRight = new EditHandle(); + bottomRight.setPosition(Position.bottom(), Position.right()); + bottomRight.setDrag(Drag.bottom(), Drag.right()); + + EditHandle topLeft = new EditHandle(); + topLeft.setPosition(Position.top(), Position.left()); + topLeft.setDrag(Drag.top(), Drag.left()); + + EditHandle topRight = new EditHandle(); + topRight.setPosition(Position.top(), Position.right()); + topRight.setDrag(Drag.top(), Drag.right()); + + EditHandle bottomMiddle = new EditHandle(); + bottomMiddle.setPosition(Position.bottom(), Position.xMiddle()); + bottomMiddle.setDrag(Drag.bottom()); + + EditHandle topMiddle = new EditHandle(); + topMiddle.setPosition(Position.top(), Position.xMiddle()); + topMiddle.setDrag(Drag.top()); + + EditHandle rightMiddle = new EditHandle(); + rightMiddle.setPosition(Position.right(), Position.yMiddle()); + rightMiddle.setDrag(Drag.right()); + + EditHandle leftMiddle = new EditHandle(); + leftMiddle.setPosition(Position.left(), Position.yMiddle()); + leftMiddle.setDrag(Drag.left()); + + this.getChildren().addAll(physicalTag, bottomLeft, bottomRight, topLeft, + topRight, bottomMiddle, topMiddle, rightMiddle, leftMiddle); + + //Position the tag on the image. The edit knobs will be notified of + //the new coords and adjust themselves. + physicalTag.setX(x); + physicalTag.setY(y); + physicalTag.setWidth(x1 - x); + physicalTag.setHeight(y1 - y); + + this.addEventHandler(ControlType.NOT_FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); + this.addEventHandler(ControlType.FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); + this.addEventHandler(ControlType.DELETE, event -> ALL_CHILDREN.dispatchEvent(event)); + } + + class PhysicalTag extends Rectangle { + + private final ImageView image; + + public PhysicalTag(ImageView image) { + this.setStroke(Color.RED); + this.setFill(Color.RED.deriveColor(0, 0, 0, 0)); + + this.addEventHandler(ControlType.NOT_FOCUSED, event -> this.setOpacity(1)); + this.addEventHandler(ControlType.FOCUSED, event -> this.setOpacity(0.5)); + + this.addEventHandler(ControlType.DELETE, event -> { + this.setVisible(false); + //TODO - delete tag from persistent storage here. + }); + + this.image = image; + } + + private void save() { + //TODO - persist tag + } + + private ImageView getUnderlyingImage() { + return image; + } + } + + class EditHandle extends Circle { + + public EditHandle() { + super(physicalTag.getStrokeWidth(), physicalTag.getStroke()); + this.setVisible(false); + + //Manipulate the parent rectangle when this knob is dragged. + this.addEventHandler(ControlType.NOT_FOCUSED, event -> this.setVisible(false)); + this.addEventHandler(ControlType.FOCUSED, event -> this.setVisible(true)); + this.addEventHandler(ControlType.DELETE, event -> this.setVisible(false)); + this.setOnDragDetected(event -> this.getParent().setCursor(Cursor.CLOSED_HAND)); + this.setOnMouseReleased(event -> { + this.getParent().setCursor(Cursor.DEFAULT); + physicalTag.save(); + }); + } + + public void setPosition(Position... vals) { + for (Position pos : vals) { + physicalTag.widthProperty().addListener((obs, oldVal, newVal) -> pos.set(physicalTag, this)); + physicalTag.heightProperty().addListener((obs, oldVal, newVal) -> pos.set(physicalTag, this)); + } + } + + public void setDrag(Drag... vals) { + this.setOnMouseDragged((event) -> { + for (Drag drag : vals) { + drag.perform(physicalTag, event, physicalTag.getUnderlyingImage()); + } + }); + } + } + + static interface Position { + + void set(Rectangle parent, Circle knob); + + static Position left() { + return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty()); + } + + static Position right() { + return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth())); + } + + static Position top() { + return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty()); + } + + static Position bottom() { + return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight())); + } + + static Position xMiddle() { + return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth() / 2)); + } + + static Position yMiddle() { + return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight() / 2)); + } + } + + static interface Drag { + + void perform(Rectangle parent, MouseEvent event, ImageView image); + + static Drag bottom() { + return (parent, event, image) -> { + double deltaY = event.getY() - parent.getY(); + if (deltaY > 0 && event.getY() + < image.getY() + image.getImage().getHeight() + - parent.getStrokeWidth() / 2) { + parent.setHeight(deltaY); + } + }; + } + + static Drag top() { + return (parent, event, image) -> { + double deltaY = parent.getY() + parent.getHeight() - event.getY(); + if (deltaY < parent.getY() + parent.getHeight() && event.getY() + > image.getY() + parent.getStrokeWidth() / 2 && deltaY > 0) { + parent.setHeight(deltaY); + parent.setY(event.getY()); + } + }; + } + + static Drag left() { + return (parent, event, image) -> { + double deltaX = parent.getX() + parent.getWidth() - event.getX(); + if (deltaX < parent.getX() + parent.getWidth() + && event.getX() > image.getX() + parent.getStrokeWidth() / 2 + && deltaX > 0) { + parent.setWidth(deltaX); + parent.setX(event.getX()); + } + }; + } + + static Drag right() { + return (parent, event, image) -> { + double deltaX = event.getX() - parent.getX(); + if (deltaX > 0 && event.getX() < image.getX() + + image.getImage().getWidth() - parent.getStrokeWidth() / 2) { + parent.setWidth(deltaX); + } + }; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java new file mode 100755 index 0000000000..e1cba2d7f6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java @@ -0,0 +1,40 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.contentviewers.imagetagging; + + +import java.util.EventObject; + +/** + * + * @author dsmyda + */ +public final class StoredTagEvent extends EventObject { + private final StoredTag tag; + + public StoredTagEvent(Object source, StoredTag tag) { + super(source); + this.tag = tag; + } + + public StoredTag getTag() { + return tag; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java new file mode 100755 index 0000000000..a727cc4f53 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java @@ -0,0 +1,33 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.contentviewers.imagetagging; + + +import java.util.EventListener; + +/** + * + * @author dsmyda + */ +@FunctionalInterface +public interface StoredTagListener extends EventListener { + + void newTagEvent(StoredTagEvent tagEvent); +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java new file mode 100755 index 0000000000..6b7bca78e8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java @@ -0,0 +1,98 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import com.sun.javafx.event.EventDispatchChainImpl; +import java.util.ArrayList; +import java.util.Collection; +import javafx.event.Event; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; + +/** + * Top level group containing Image and all existing tags. + */ +public final class TopLevelTagsGroup extends Group { + private final EventDispatchChainImpl NO_OP_CHAIN = new EventDispatchChainImpl(); + private final Collection listeners; + + private Node lastFocus; + private final ImageView baseImage; + + public TopLevelTagsGroup(ImageView image) { + super(image); + baseImage = image; + listeners = new ArrayList<>(); + + //Manage focus, such that only one child can be set at a time. + this.addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> { + if (!e.isPrimaryButtonDown()) { + return; + } + + Node child = getTopLevelChild(e.getPickResult().getIntersectedNode()); + if (lastFocus == child) { + return; + } else if (lastFocus != null && lastFocus != child) { + resetFocus(lastFocus); + } + + child.getEventDispatcher().dispatchEvent(new Event(ControlType.FOCUSED), NO_OP_CHAIN); + listeners.forEach((listener) -> { + listener.focusChanged(new FocusChangeEvent(this, ControlType.FOCUSED, child)); + }); + + if(child != baseImage) { + child.toFront(); + } + lastFocus = child; + }); + } + + public void addFocusChangeListener(FocusChangeListener fcl) { + listeners.add(fcl); + } + + private void resetFocus(Node n) { + n.getEventDispatcher().dispatchEvent(new Event(ControlType.NOT_FOCUSED), NO_OP_CHAIN); + listeners.forEach((listener) -> { + listener.focusChanged(new FocusChangeEvent(this, ControlType.NOT_FOCUSED, n)); + }); + } + + public void deleteNode(Node dest) { + if(lastFocus == dest) { + resetFocus(lastFocus); + lastFocus = null; + } + + dest.getEventDispatcher().dispatchEvent(new Event(ControlType.DELETE), NO_OP_CHAIN); + } + + private Node getTopLevelChild(Node selected) { + Node curr = selected; + while (!this.getChildren().contains(curr)) { + curr = curr.getParent(); + } + return curr; + } +} From 40689ae751a31020828443b03951933be30b0c87 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 28 May 2019 12:48:38 -0400 Subject: [PATCH 003/453] Continued development checkpoint, more work to be done. --- .../ApplicationTagsManager.java | 67 +++++++++++++++ .../autopsy/contentviewers/Bundle.properties | 4 + .../contentviewers/Bundle.properties-MERGED | 4 + .../contentviewers/MediaViewImagePanel.form | 21 ++++- .../contentviewers/MediaViewImagePanel.java | 82 ++++++++++++++++--- .../imagetagging/FocusChangeEvent.java | 7 +- .../imagetagging/ImageTagCreator.java | 48 +++++++---- .../imagetagging/StoredTag.java | 30 +++++++ .../imagetagging/TopLevelTagsGroup.java | 73 +++++++++-------- 9 files changed, 271 insertions(+), 65 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java new file mode 100755 index 0000000000..65756d16c8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java @@ -0,0 +1,67 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.services.applicationtags; + +import java.util.EnumSet; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.CaseDbAccessManager; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A per case Autopsy service that manages the addition of application content + * tags to the case database. + */ +public final class ApplicationTagsManager { + + private static CaseDbAccessManager dbManager; + + private static final String TABLE_NAME = "beta_tag_app_data"; + private static final String TABLE_SCHEMA = "(app_data_id INTEGER PRIMARY KEY, " + + "content_tag_id INTEGER NOT NULL, app_data TEXT NOT NULL," + + "FOREIGN KEY(content_tag_id) REFERENCES content_tags(tag_id))"; + + private final String INSERT_OR_REPLACE_TAG_DATA = "(content_tag_id, app_data) VALUES (?, ?)"; + private final String SELECT_TAG_DATA = "* FROM " + TABLE_NAME + " WHERE content_tag_id = ?"; + private final String DELETE_TAG_DATA = "WHERE app_data_id = ?"; + + static { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> { + if(evt.getNewValue() != null) { + Case currentCase = (Case) evt.getNewValue(); + try { + CaseDbAccessManager caseDb = currentCase.getSleuthkitCase().getCaseDbAccessManager(); + //Create our custom application tags table, if need be. + if (!caseDb.tableExists(TABLE_NAME)) { + caseDb.createTable(TABLE_NAME, TABLE_SCHEMA); + } + + dbManager = caseDb; + } catch (TskCoreException ex) { + //Log + } + } + }); + } + + //public createTag + //public updateTag + //public getTag + //public deleteTag + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 652f8781ba..631bb91273 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -89,3 +89,7 @@ MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors +MediaViewImagePanel.deleteTagButton.text=Delete Tag +MediaViewImagePanel.createTagButton.text=Create Tag +MediaViewImagePanel.jMenu1.text=jMenu1 +MediaViewImagePanel.editTagButton.text=Edit Tag Info diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 236fddfada..b18b79580c 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -151,6 +151,10 @@ MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors +MediaViewImagePanel.deleteTagButton.text=Delete Tag +MediaViewImagePanel.createTagButton.text=Create Tag +MediaViewImagePanel.jMenu1.text=jMenu1 +MediaViewImagePanel.editTagButton.text=Edit Tag Info # {0} - tableName SQLiteViewer.readTable.errorText=Error getting rows for table: {0} # {0} - tableName diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form index fb17c441db..ccfe83b2cc 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form @@ -44,7 +44,7 @@ - + @@ -227,12 +227,15 @@ + + + @@ -242,12 +245,28 @@ + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index ebf7f48444..b6ec14fba1 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -52,15 +52,21 @@ import javafx.scene.transform.Scale; import javafx.scene.transform.Translate; import javax.imageio.ImageIO; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import org.controlsfx.control.MaskerPane; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.python.google.common.collect.Lists; +import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog; +import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog.TagNameAndComment; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.contentviewers.imagetagging.ControlType; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TskCoreException; /** * Image viewer part of the Media View layered pane. Uses JavaFX to display the @@ -77,10 +83,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private final boolean fxInited; private JFXPanel fxPanel; + private AbstractFile file; + private StoredTag lastFocused; private TopLevelTagsGroup imageGroup; private ImageTagCreator tagger; private ImageView fxImageView; - private Node focusedNode; private ScrollPane scrollPane; private final ProgressBar progressBar = new ProgressBar(); private final MaskerPane maskerPane = new MaskerPane(); @@ -127,14 +134,17 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan fxImageView = new ImageView(); // will hold image imageGroup = new TopLevelTagsGroup(fxImageView); deleteTagButton.setEnabled(false); + editTagButton.setEnabled(false); imageGroup.addFocusChangeListener((event) -> { - if(event.getType() == ControlType.NOT_FOCUSED || event.getNode() == fxImageView) { + if (event.getType() == ControlType.NOT_FOCUSED || event.getNode().equals(fxImageView)) { deleteTagButton.setEnabled(false); createTagButton.setEnabled(true); + editTagButton.setEnabled(false); } else if (event.getType() == ControlType.FOCUSED) { deleteTagButton.setEnabled(true); + editTagButton.setEnabled(true); createTagButton.setEnabled(false); - focusedNode = event.getNode(); + lastFocused = event.getNode(); } }); scrollPane = new ScrollPane(imageGroup); // scrolls and sizes imageview @@ -220,16 +230,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan Image fxImage = readImageTask.get(); imageGroup.getChildren().clear(); imageGroup.getChildren().add(fxImageView); + this.file = file; if (nonNull(fxImage)) { // We have a non-null image, so let's show it. fxImageView.setImage(fxImage); - tagger = new ImageTagCreator(fxImageView); - StoredTagListener newTagListener = (StoredTagEvent evt) -> { - StoredTag tag = evt.getTag(); - imageGroup.getChildren().add(tag); - }; - tagger.addNewTagListener(newTagListener); - imageGroup.getChildren().add(tagger); resetView(); scrollPane.setContent(imageGroup); } else { @@ -326,8 +330,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan jSeparator2 = new javax.swing.JToolBar.Separator(); zoomResetButton = new javax.swing.JButton(); jSeparator3 = new javax.swing.JToolBar.Separator(); + filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0)); createTagButton = new javax.swing.JButton(); jSeparator4 = new javax.swing.JToolBar.Separator(); + editTagButton = new javax.swing.JButton(); + jSeparator5 = new javax.swing.JToolBar.Separator(); deleteTagButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(jMenu1, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.jMenu1.text")); // NOI18N @@ -435,9 +442,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan }); toolbar.add(zoomResetButton); toolbar.add(jSeparator3); + toolbar.add(filler1); org.openide.awt.Mnemonics.setLocalizedText(createTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.createTagButton.text")); // NOI18N createTagButton.setFocusable(false); + createTagButton.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); createTagButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); createTagButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); createTagButton.addActionListener(new java.awt.event.ActionListener() { @@ -448,8 +457,21 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan toolbar.add(createTagButton); toolbar.add(jSeparator4); + org.openide.awt.Mnemonics.setLocalizedText(editTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.editTagButton.text")); // NOI18N + editTagButton.setFocusable(false); + editTagButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + editTagButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + editTagButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + editTagButtonActionPerformed(evt); + } + }); + toolbar.add(editTagButton); + toolbar.add(jSeparator5); + org.openide.awt.Mnemonics.setLocalizedText(deleteTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.deleteTagButton.text")); // NOI18N deleteTagButton.setFocusable(false); + deleteTagButton.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); deleteTagButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); deleteTagButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); deleteTagButton.addActionListener(new java.awt.event.ActionListener() { @@ -503,16 +525,53 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan }//GEN-LAST:event_formComponentResized private void deleteTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTagButtonActionPerformed - imageGroup.deleteNode(focusedNode); + Platform.runLater(() -> { + imageGroup.deleteNode(lastFocused); + }); + try { + Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(lastFocused.getContentTag()); + } catch (TskCoreException ex) { + + } }//GEN-LAST:event_deleteTagButtonActionPerformed private void createTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createTagButtonActionPerformed + Platform.runLater(() -> { + tagger = new ImageTagCreator(fxImageView); + StoredTagListener newTagListener = (StoredTagEvent event) -> { + StoredTag tag = event.getTag(); + imageGroup.getChildren().add(tag); + SwingUtilities.invokeLater(() -> { + TagNameAndComment result = GetTagNameAndCommentDialog.doDialog(); + if (result != null) { + try { + ContentTag t = Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, + result.getTagName(), result.getComment()); + tag.addContentTag(t); + } catch (TskCoreException ex) { + Platform.runLater(() -> imageGroup.deleteNode(tag)); + } + } else { + Platform.runLater(() -> imageGroup.deleteNode(tag)); + } + }); + imageGroup.deleteNode(tagger); + }; + tagger.addNewTagListener(newTagListener); + imageGroup.getChildren().add(tagger); + }); }//GEN-LAST:event_createTagButtonActionPerformed + private void editTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editTagButtonActionPerformed + ContentTag t = lastFocused.getContentTag(); + }//GEN-LAST:event_editTagButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton createTagButton; private javax.swing.JButton deleteTagButton; + private javax.swing.JButton editTagButton; + private javax.swing.Box.Filler filler1; private javax.swing.JMenu jMenu1; private javax.swing.JPopupMenu jPopupMenu1; private javax.swing.JPopupMenu jPopupMenu2; @@ -520,6 +579,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private javax.swing.JToolBar.Separator jSeparator2; private javax.swing.JToolBar.Separator jSeparator3; private javax.swing.JToolBar.Separator jSeparator4; + private javax.swing.JToolBar.Separator jSeparator5; private javax.swing.JButton rotateLeftButton; private javax.swing.JButton rotateRightButton; private javax.swing.JTextField rotationTextField; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java index e487e01061..f8cac1a128 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.contentviewers.imagetagging; import java.util.EventObject; import javafx.event.Event; import javafx.event.EventType; -import javafx.scene.Node; /** * @@ -30,9 +29,9 @@ import javafx.scene.Node; public final class FocusChangeEvent extends EventObject{ private final EventType type; - private final Node focused; + private final StoredTag focused; - public FocusChangeEvent(Object source, EventType type, Node focused) { + public FocusChangeEvent(Object source, EventType type, StoredTag focused) { super(source); this.type = type; this.focused = focused; @@ -42,7 +41,7 @@ public final class FocusChangeEvent extends EventObject{ return type; } - public Node getNode() { + public StoredTag getNode() { return focused; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java index 146fbe1281..b0c114ada5 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.contentviewers.imagetagging; import java.util.ArrayList; import java.util.Collection; +import javafx.event.EventHandler; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; @@ -40,6 +41,10 @@ public final class ImageTagCreator extends Rectangle { private double lineThicknessAsPercent = 1.5; private final double minArea; private final Collection listeners; + + private final EventHandler mousePressed; + private final EventHandler mouseDragged; + private final EventHandler mouseReleased; /** * Adds tagging support to an image, where the 'tag' rectangle will be the @@ -60,11 +65,8 @@ public final class ImageTagCreator extends Rectangle { setStrokeWidth(lineThicknessPixels); minArea = lineThicknessPixels * lineThicknessPixels; setVisible(false); - - this.addEventHandler(ControlType.NOT_FOCUSED, (event) -> defaultSettings()); - - //Create a rectangle by left clicking on the image - image.setOnMousePressed((MouseEvent event) -> { + + this.mousePressed = (MouseEvent event) -> { if (event.isSecondaryButtonDown()) { return; } @@ -76,10 +78,11 @@ public final class ImageTagCreator extends Rectangle { setX(rectangleOriginX); setY(rectangleOriginY); - }); - - //Adjust the rectangle by dragging the left mouse button - image.setOnMouseDragged((MouseEvent event) -> { + }; + + image.addEventHandler(MouseEvent.MOUSE_PRESSED, this.mousePressed); + + this.mouseDragged = (MouseEvent event) -> { if (event.isSecondaryButtonDown()) { return; } @@ -107,16 +110,16 @@ public final class ImageTagCreator extends Rectangle { setY(newY); } setHeight(Math.abs(offsetY)); - }); - - image.setOnMouseReleased(event -> { + }; + + image.addEventHandler(MouseEvent.MOUSE_DRAGGED, this.mouseDragged); + + this.mouseReleased = event -> { if ((this.getWidth() - this.getStrokeWidth()) * (this.getHeight() - this.getStrokeWidth()) <= minArea) { defaultSettings(); return; - } - - //TODO - persist tag. + } //Notify listeners StoredTagEvent newTagEvent = new StoredTagEvent(this, new StoredTag(image, this.getX(), this.getY(), @@ -127,7 +130,22 @@ public final class ImageTagCreator extends Rectangle { }); defaultSettings(); + }; + + image.addEventHandler(MouseEvent.MOUSE_RELEASED, this.mouseReleased); + this.addEventHandler(ControlType.NOT_FOCUSED, (event) -> { + defaultSettings(); + image.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleased); + image.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragged); + image.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressed); }); + + this.addEventHandler(ControlType.DELETE, event -> { + defaultSettings(); + image.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleased); + image.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragged); + image.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressed); + }); } public void addNewTagListener(StoredTagListener listener) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java index 6670639811..1a97918dee 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java @@ -28,6 +28,7 @@ import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; +import org.sleuthkit.datamodel.ContentTag; /** * @@ -92,15 +93,32 @@ public final class StoredTag extends Group { physicalTag.setY(y); physicalTag.setWidth(x1 - x); physicalTag.setHeight(y1 - y); + + this.focusedProperty().addListener((ov, oldV, newV) -> { + if(!newV) { + System.out.println("NOT FOCUSED"); + } else { + System.out.println("GAINED FOCUS"); + } + }); this.addEventHandler(ControlType.NOT_FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); this.addEventHandler(ControlType.FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); this.addEventHandler(ControlType.DELETE, event -> ALL_CHILDREN.dispatchEvent(event)); } + + public void addContentTag(ContentTag t) { + physicalTag.linkContentTag(t); + } + + public ContentTag getContentTag() { + return physicalTag.getContentTag(); + } class PhysicalTag extends Rectangle { private final ImageView image; + private ContentTag tag; public PhysicalTag(ImageView image) { this.setStroke(Color.RED); @@ -120,10 +138,22 @@ public final class StoredTag extends Group { private void save() { //TODO - persist tag } + + public void linkContentTag(ContentTag t) { + tag = t; + } + + public ContentTag getContentTag() { + return tag; + } private ImageView getUnderlyingImage() { return image; } + + private class ImageRegion { + + } } class EditHandle extends Circle { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java index 6b7bca78e8..2a00df6f7a 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java @@ -16,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.contentviewers.imagetagging; import com.sun.javafx.event.EventDispatchChainImpl; @@ -32,6 +31,7 @@ import javafx.scene.input.MouseEvent; * Top level group containing Image and all existing tags. */ public final class TopLevelTagsGroup extends Group { + private final EventDispatchChainImpl NO_OP_CHAIN = new EventDispatchChainImpl(); private final Collection listeners; @@ -49,50 +49,55 @@ public final class TopLevelTagsGroup extends Group { return; } - Node child = getTopLevelChild(e.getPickResult().getIntersectedNode()); - if (lastFocus == child) { - return; - } else if (lastFocus != null && lastFocus != child) { - resetFocus(lastFocus); + Node topLevelChild = e.getPickResult().getIntersectedNode(); + while (!this.getChildren().contains(topLevelChild)) { + topLevelChild = topLevelChild.getParent(); } - - child.getEventDispatcher().dispatchEvent(new Event(ControlType.FOCUSED), NO_OP_CHAIN); - listeners.forEach((listener) -> { - listener.focusChanged(new FocusChangeEvent(this, ControlType.FOCUSED, child)); - }); - - if(child != baseImage) { - child.toFront(); - } - lastFocus = child; + + requestFocus(topLevelChild); }); } - + public void addFocusChangeListener(FocusChangeListener fcl) { listeners.add(fcl); } - + private void resetFocus(Node n) { n.getEventDispatcher().dispatchEvent(new Event(ControlType.NOT_FOCUSED), NO_OP_CHAIN); - listeners.forEach((listener) -> { - listener.focusChanged(new FocusChangeEvent(this, ControlType.NOT_FOCUSED, n)); - }); - } - - public void deleteNode(Node dest) { - if(lastFocus == dest) { - resetFocus(lastFocus); - lastFocus = null; + if(n instanceof StoredTag) { + listeners.forEach((listener) -> { + listener.focusChanged(new FocusChangeEvent(this, ControlType.NOT_FOCUSED, (StoredTag) n)); + }); } - - dest.getEventDispatcher().dispatchEvent(new Event(ControlType.DELETE), NO_OP_CHAIN); } - private Node getTopLevelChild(Node selected) { - Node curr = selected; - while (!this.getChildren().contains(curr)) { - curr = curr.getParent(); + public void deleteNode(Node n) { + if (lastFocus == n) { + resetFocus(n); + lastFocus = null; } - return curr; + n.getEventDispatcher().dispatchEvent(new Event(ControlType.DELETE), NO_OP_CHAIN); + this.getChildren().remove(n); + } + + public void requestFocus(Node n) { + if (lastFocus == n) { + return; + } else if (lastFocus != null && lastFocus != n) { + resetFocus(lastFocus); + } + + n.getEventDispatcher().dispatchEvent(new Event(ControlType.FOCUSED), NO_OP_CHAIN); + if(n instanceof StoredTag) { + listeners.forEach((listener) -> { + listener.focusChanged(new FocusChangeEvent(this, ControlType.FOCUSED, (StoredTag) n)); + }); + } + + if (n != baseImage) { + n.toFront(); + } + + lastFocus = n; } } From 4c5aaf929627bcddb2eeb83d0bb2f6792a6f0888 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 28 May 2019 15:21:10 -0400 Subject: [PATCH 004/453] Inital pass of completed thread code --- .../thunderbirdparser/EmailMessage.java | 28 + .../EmailMessageThreader.java | 612 +++++++++++++++--- 2 files changed, 534 insertions(+), 106 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java index 7b0b2eb30e..6b2af09f21 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java @@ -48,6 +48,8 @@ class EmailMessage { private String messageID = ""; private String inReplyToID = ""; private List references = null; + private String simplifiedSubject = ""; + private boolean isReplySubject = false; boolean hasAttachment() { return hasAttachment; @@ -80,8 +82,34 @@ class EmailMessage { void setSubject(String subject) { if (subject != null) { this.subject = subject; + if(subject.matches("^[R|r][E|e].*?:.*")) { + this.simplifiedSubject = subject.replaceAll("[R|r][E|e].*?:", "").trim(); + isReplySubject = true; + } else { + this.simplifiedSubject = subject; + } + } else { + this.simplifiedSubject = null; } } + + /** + * Returns the orginal subject with the "RE:" stripped off". + * + * @return Message subject with the "RE" stripped off + */ + String getSimplifiedSubject() { + return simplifiedSubject; + } + + /** + * Returns whether or not the message subject started with "RE:" + * + * @return true if the original subject started with RE otherwise false. + */ + boolean isReplySubject() { + return isReplySubject; + } String getHeaders() { return headers; diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java index 0061ef6c88..c92bdb2fa8 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java @@ -1,107 +1,180 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.thunderbirdparser; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; /** - * + * Given a list of email messages arranges the message into threads using the message + * reference lists. + * + * This threader is based heavely off of the algorithum found at + * "message threading." by Jamie Zawinski * */ final class EmailMessageThreader { + + private int bogus_id_count = 0; - public void threadMessages(List emailMessages) { + public Set threadMessages(List emailMessages) { HashMap id_table = createIDTable(emailMessages); Set rootSet = getRootSet(id_table); - for(Container container: rootSet) { - System.out.println(container.getMessage().getSubject()); - - Container child = container.getChild(); - String tabs = "\t"; - while(child != null) { - System.out.println(tabs + child.getMessage().getSubject()); - - child = child.getChild(); - tabs = tabs + "\t"; - } - } + pruneEmptyContainers(rootSet); + + Set finalSet = groupBySubject(rootSet); + + printContainerSet(finalSet, ""); + + return finalSet; } - + + /** + * Walks the list of emailMessages creating a Container object for each + * unique message ID found. Adds the emailMessage to the container where + * possible. + * + * @param emailMessages + * @return + */ private HashMap createIDTable(List emailMessages) { HashMap id_table = new HashMap<>(); for(EmailMessage message: emailMessages) { String messageID = message.getMessageID(); + // Check the id_table for an existing Container for message-id Container container = id_table.get(messageID); + + // An existing container for message-id was found + if(container != null) { + // If the existing Container has a message already assocated with it + // emailMessage maybe a duplicate, so we don't lose the existance of + // the duplicate message assign it a bogus message-id + if(container.hasMessage()) { + messageID = String.format("", bogus_id_count++); + container = null; + } else { + container.setMessage(message); + } + } + if(container == null) { container = new Container(message); id_table.put(messageID, container); - } else { - if(container.getMessage() == null) { - container.setMessage(message); - } } - - - List referenceList = message.getReferences(); - - if(referenceList != null) { - Container parent = null; - Container current; - - for(String refID: referenceList){ - current = id_table.get(refID); - - if(current == null ) { - current = new Container(); - id_table.put(refID, current); - } - - // If a link already exists don't change it; - - if(parent != null) { - parent.setChild(current); - - Container grandparent = parent.getParent(); - if(grandparent != null) { - grandparent.setNextSibling(current); - } - - } - - current.setParent(parent); - parent = current; - } - - // - parent = id_table.get(referenceList.get(referenceList.size() - 1)); - Container oldParent = container.getParent(); - if(oldParent != parent) { - container.setParent(parent); - parent.setChild(container); - parent.setNextSibling(container.getChild()); - - if(oldParent != null) { - oldParent.setChild(null); - oldParent.setNextSibling(null); - } - } - } + processMessageReferences(message, container, id_table); } return id_table; } - Set getRootSet(HashMap id_table) { + /** + * Loops throught message's list of references, creating objects as needed + * and setting up the parent child relationships amoung the messages. + * + * @param message The current email messags + * @param container Container object for message + * @param id_table Hashtable of known message-id\container pairs + */ + void processMessageReferences(EmailMessage message, Container container, Map id_table) { + List referenceList = message.getReferences(); + + // Make sure the inReplyToID is in the list of references + String inReplyToID = message.getInReplyToID(); + if(inReplyToID != null && !inReplyToID.isEmpty()) { + if(referenceList == null) { + referenceList = new ArrayList<>(); + } + + referenceList.add(inReplyToID); + } + + // No references, nothing to do + if(referenceList == null) { + return; + } + + Container parent_ref = null; + Container ref; + + for(String refID: referenceList){ + // Check id_table to see if there is already a container for this + // reference id, if not create a new Container and add to table + ref = id_table.get(refID); + + if(ref == null ) { + ref = new Container(); + id_table.put(refID, ref); + } + + // Set the parent\child relationship between parent_ref and ref + if (parent_ref != null + && !ref.hasParent() + && parent_ref != ref + && !parent_ref.isChild(ref)) { + ref.setParent(parent_ref); + parent_ref.addChild(ref); + } + + parent_ref = ref; + } + + // If the parent_ref and container are already linked, don't change + // anything + if (parent_ref != null + && (parent_ref == container + || container.isChild(parent_ref))) { + parent_ref = null; + } + + // If container already has a parent, the parent was assumed based on + // the list of references from another message. parent_ref will be + // the real parent of container so throw away the old parent and set a + // new one. + if(container.hasParent()) { + container.getParent().removeChild(container); + container.setParent(null); + } + + if(parent_ref != null) { + container.setParent(container); + parent_ref.addChild(container); + } + } + + /** + * Creates a set of root container messages from the message-ID hashtable. + * A root Container is container that does not have a parent container. + * + * @param id_table Table of all known Containers + * + * @return A set of the root containers. + */ + Set getRootSet(HashMap id_table) { HashSet rootSet = new HashSet<>(); id_table.values().stream().filter((container) -> @@ -112,94 +185,421 @@ final class EmailMessageThreader { return rootSet; } - void pruneEmptyContainers(Set rootSet) { - for(Container container: rootSet) { - if(!container.hasMessage()) { // Container doesn't have message - if(!container.hasChild()) { - rootSet.remove(container); + /** + * Remove Containers from containerSet if they do not have a message or + * children. + * + * @param containerSet A set of Container objects + */ + void pruneEmptyContainers(Set containerSet) { + containerSet.forEach((container) -> { + if(!container.hasMessage() && !container.hasChildren()) { + containerSet.remove(container); + } else { + pruneChildren(container); + } + }); + } + + /** + * Recursively work through the list of parent's children removing + * empy containers. + * + * @param parent + */ + void pruneChildren(Container parent) { + if(parent == null) { + return; + } + + Set children = parent.getChildren(); + Container grandParent = parent.getParent(); + + if (children == null) { + return; + } + + children.stream().map((child) -> { + // Parent is an empty container. Reparent the children to their + // grandparent. + if (!parent.hasMessage() && grandParent != null) { + child.setParent(grandParent); + grandParent.addChild(child); + grandParent.removeChild(parent); + parent.setParent(null); + parent.clearChildren(); + } + return child; + }).forEachOrdered((child) -> { + pruneChildren(child); + }); + } + + /** + * Now that the emails are grouped together by references\message ID take another + * pass through and group together messages with the same simplified subject. + * + * This may cause "root" messages with identical subjects to get grouped + * together as children of an empty container. The code that uses the + * thread information can decide what to do in that sisiuation as those message + * maybe part of a common thread or maybe their own unique messages. + * + * @param rootSet + * @return Final set of threaded messages. + */ + Set groupBySubject(Set rootSet) { + HashMap subject_table = createSubjectTable(rootSet); + + Set finalSet = new HashSet<>(); + + for (Container rootSetContainer : rootSet) { + String rootSubject = rootSetContainer.getSimplifiedSubject(); + + Container tableContainer = subject_table.get(rootSubject); + if(tableContainer == null || tableContainer == rootSetContainer) { + finalSet.add(rootSetContainer); + continue; + } + + // If both containers are dummy/empty append the children of one to the other + if(tableContainer.getMessage() == null && rootSetContainer.getMessage() == null) { + tableContainer.addChildren(rootSetContainer.getChildren()); + rootSetContainer.clearChildren(); + continue; + } + + // one container is empty, but the other is not, make the non-empty one be a + // child of the empty + if( (tableContainer.getMessage() == null && rootSetContainer.getMessage() != null) || + (tableContainer.getMessage() != null && rootSetContainer.getMessage() == null)){ + + if(tableContainer.getMessage() == null) { + tableContainer.addChild(rootSetContainer); + } else { - if(!container.hasNextSibling()) { // Container has no message, but one child - rootSet.remove(container); // prompot the child to the rootSet - rootSet.add(container.child); - } else { - pruneChildren(container); + rootSetContainer.addChild(tableContainer); + subject_table.remove(rootSubject, tableContainer); + subject_table.put(rootSubject, rootSetContainer); + + finalSet.add(rootSetContainer); + } + + continue; + } + + // tableContainer is non-empty and it's message's subject does not begin + // with 'RE:' but rootSetContainer's message does begin with 'RE:', then + // make rootSetContainer a child of tableContainer + if(tableContainer.getMessage() != null && + !tableContainer.isReplySubject() && + rootSetContainer.isReplySubject()) { + tableContainer.addChild(rootSetContainer); + continue; + } + + // If table container is non-empy, and table container's subject does + // begin with 'RE:', but rootSetContainer does not start with 'RE:' + // make tableContainer a child of rootSetContainer + if(tableContainer.getMessage() != null && + tableContainer.isReplySubject() && + !rootSetContainer.isReplySubject()) { + rootSetContainer.addChild(tableContainer); + subject_table.put(rootSubject, rootSetContainer); + finalSet.add(rootSetContainer); + continue; + } + + // rootSetContainer and tableContainer either both have 'RE' or + // don't. Create a new dummy container with both containers as + // children. + + Container newParent = new Container(); + newParent.addChild(tableContainer); + newParent.addChild(rootSetContainer); + subject_table.remove(rootSubject, tableContainer); + subject_table.put(rootSubject, newParent); + + finalSet.add(newParent); + } + return finalSet; + } + + /** + * Creates a Hashtable of Container and subjects. There will be one Container + * subject pair for each unique subject. + * @param rootSet The set of "root" Containers + * @return + */ + HashMap createSubjectTable(Set rootSet) { + HashMap subject_table = new HashMap<>(); + + for (Container rootSetContainer : rootSet) { + String subject = ""; + boolean reSubject = false; + + if (rootSetContainer.hasMessage()) { + subject = rootSetContainer.getMessage().getSimplifiedSubject(); + reSubject = rootSetContainer.getMessage().isReplySubject(); + } else if(rootSetContainer.hasChildren()){ + Iterator childrenIterator = rootSetContainer.getChildren().iterator(); + while(childrenIterator.hasNext()) { + EmailMessage childMessage = childrenIterator.next().getMessage(); + if(childMessage != null) { + subject = childMessage.getSimplifiedSubject(); + if(!subject.isEmpty()) { + reSubject = childMessage.isReplySubject(); + break; + } } } } - } - } - - void pruneChildren(Container container) { - if(!container.hasMessage()) { - if(container.hasChild()) { - Container parent = container.getParent(); - parent.setChild(container.getChild()); - parent.setNextSibling(container.getNextSibling()); - container.getChild().setParent(parent); - - pruneChildren(container.child); + + if (subject.isEmpty()) { + continue; // Give up on this container } + + Container tableContainer = subject_table.get(subject); + + if(tableContainer == null || // Not in table + (tableContainer.getMessage() != null && rootSetContainer.getMessage() == null) || // One in the table is empty, but current is not + (!reSubject && (tableContainer.getMessage() != null && tableContainer.getMessage().isReplySubject()))) { //current doesn't have RE in it, use current instead of the one in the table + subject_table.put(subject, rootSetContainer); + } + } + + return subject_table; } + /** + * Prints a set of containers and their children. + * + * @param containerSet Set of containers to print out + * @param prefix A prefix for each line to show child depth. + */ + private void printContainerSet(Set containerSet, String prefix) { + containerSet.stream().map((container) -> { + if(container.getMessage() != null) { + System.out.println(prefix + container.getMessage().getSubject()); + } else { + System.out.println(""); + } + return container; + }).filter((container) -> (container.hasChildren())).forEachOrdered((container) -> { + printContainerSet(container.getChildren(), prefix+ "\t"); + }); + } + + /** + * The container object is used to wrap and email message and track the + * messages parent and child messages. + */ final class Container{ private EmailMessage message; private Container parent; - private Container child; // firt Child - private Container next; // next element in siblint list or null; + private Set children; + /** + * Constructs an empy container. + */ Container() {} + /** + * + * @param message + */ Container(EmailMessage message) { this.message = message; } + /** + * Returns the EmailMessage object + * + * @return + */ EmailMessage getMessage() { return message; } + /** + * Set the Container EmailMessage object. + * + * @param message - The container EmailMessage + */ void setMessage(EmailMessage message) { this.message = message; } + /** + * Return whether or not this Container has a valid EmailMessage object. + * + * @return True if EmailMessage has been set otherwise false + */ boolean hasMessage() { return message != null; } + /** + * Returns the Simplified Subject (original subject without RE:) of the + * EmailMessage or if this is an empty Container with Children, return + * the simplified subject of one of the children. + * + * @return Simplified subject of this Container + */ + String getSimplifiedSubject() { + String subject = ""; + if(message != null) { + subject = message.getSimplifiedSubject(); + } else if(children != null) { + for(Container child: children) { + if(child.hasMessage()) { + subject = child.getSimplifiedSubject(); + } + + if(subject != null && !subject.isEmpty()) { + break; + } + } + } + return subject; + } + + /** + * Simialar to getSimplifiedSubject, isReplySubject is a helper function + * that will return the isReplySubject of the Containers message or if + * this is an empty container, the state of one of the children. + * + * @return + */ + boolean isReplySubject() { + if(message != null) { + return message.isReplySubject(); + } else if(children != null) { + for(Container child: children) { + if(child.hasMessage()) { + boolean isReply = child.isReplySubject(); + + if(isReply) { + return isReply; + } + } + } + } + + return false; + } + + /** + * Returns the parent Container of this Container. + * + * @return The Container parent or null if one is not set + */ Container getParent() { return parent; } + /** + * + * @param container + */ void setParent(Container container) { parent = container; } + /** + * + * @return + */ boolean hasParent() { return parent != null; } - - Container getChild() { - return child; + + /** + * + * @param child + * @return + */ + boolean addChild(Container child) { + if(children == null) { + children = new HashSet<>(); + } + + return children.add(child); } - void setChild(Container container) { - child = container; + /** + * + * @param children + * @return + */ + boolean addChildren(Set children) { + if(children == null || children.isEmpty()) { + return false; + } + + if(this.children == null) { + this.children = new HashSet<>(); + } + + return this.children.addAll(children); } - boolean hasChild() { - return child != null; + /** + * + */ + void clearChildren() { + children.clear(); } - Container getNextSibling() { - return next; + /** + * + * @param child + * @return + */ + boolean removeChild(Container child) { + return children.remove(child); } - void setNextSibling(Container container) { - next = container; + /** + * + * @return + */ + boolean hasChildren() { + return children != null && !children.isEmpty(); } - boolean hasNextSibling() { - return next != null; + /** + * + * @return + */ + Set getChildren() { + return children; + } + + /** + * + * @return + */ +// boolean hasSiblings() { +// return children == null ? false : (children.size() > 1); +// } + + /** + * + * @param container + * @return + */ + boolean isChild(Container container) { + if(children == null || children.isEmpty()) { + return false; + } else if(children.contains(container)) { + return true; + } else { + if (children.stream().anyMatch((child) -> (child.isChild(container)))) { + return true; + } + return false; + } } } } From 85a3124f9d5765e5822d53092d44345131692ecd Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 28 May 2019 16:20:03 -0400 Subject: [PATCH 005/453] 5061 add initial commit of Bing translator --- Core/ivy.xml | 3 + Core/nbproject/project.properties | 4 + Core/nbproject/project.xml | 8 + .../translators/BingTranslator.java | 158 ++++++++++++++++++ .../translators/BingTranslatorTest.java | 89 ++++++++++ 5 files changed, 262 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java create mode 100644 Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java diff --git a/Core/ivy.xml b/Core/ivy.xml index 9dfd0fcf85..54ed532feb 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -45,6 +45,9 @@ + + + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index cf21c9a068..74a08fd53d 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -111,6 +111,10 @@ file.reference.grpc-context-1.19.0.jar=release/modules/ext/grpc-context-1.19.0.j file.reference.opencensus-api-0.19.2.jar=release/modules/ext/opencensus-api-0.19.2.jar file.reference.opencensus-contrib-http-util-0.19.2.jar=release/modules/ext/opencensus-contrib-http-util-0.19.2.jar file.reference.threetenbp-1.3.3.jar=release/modules/ext/threetenbp-1.3.3.jar +file.reference.okhttp-2.7.5-javadoc.jar=release/modules/ext/okhttp-2.7.5-javadoc.jar +file.reference.okhttp-2.7.5-sources.jar=release/modules/ext/okhttp-2.7.5-sources.jar +file.reference.okhttp-2.7.5.jar=release/modules/ext/okhttp-2.7.5.jar +file.reference.okio-1.6.0.jar=release/modules/ext/okio-1.6.0.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 2a2fcd6676..624421be48 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -773,6 +773,14 @@ ext/google-api-services-translate-v2-rev20170525-1.27.0.jar release/modules/ext/google-api-services-translate-v2-rev20170525-1.27.0.jar + + ext/okhttp-2.7.5.jar + release/modules/ext/okhttp-2.7.5.jar + + + ext/okio-1.6.0.jar + release/modules/ext/okio-1.6.0.jar + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java new file mode 100644 index 0000000000..892ecb5ac6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -0,0 +1,158 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.texttranslation.translators; + +import java.io.*; +import com.google.gson.*; +import com.squareup.okhttp.*; +import java.awt.Component; +import javax.swing.JLabel; +import org.openide.util.NbBundle.Messages; + +/** + * Translates text by making HTTP requests to Bing Translator. + * This requires a valid subscription key for a Microsoft Azure account. + */ +@ServiceProvider(service = TextTranslator.class) +public class BingTranslator implements TextTranslator{ + // Insert the subscription key here. + private String subscriptionKey = ""; + + //In the String below, "en" is the target language. You can include multiple target + //languages separated by commas. A full list of supported languages is here: + //https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support + private static final String URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=en"; + + // This sends messages to Microsoft. + private final OkHttpClient CLIENT = new OkHttpClient(); + + //We might want to make this a configurable setting for anyone who has a + //paid account that's willing to pay for long documents. + private final int MAX_STRING_LENGTH = 5000; + + /** + * Converts an input test to the JSON format required by Bing Translator, + * posts it to Microsoft, and returns the JSON text response. + * + * @param string The input text to be translated. + * @return The translation response as a JSON string + * @throws IOException if the request could not be executed due to cancellation, a connectivity problem or timeout. + */ + public String postTranslationRequest(String string) throws IOException { + MediaType mediaType = MediaType.parse("application/json"); + + JsonArray jsonArray = new JsonArray(); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("Text", string); + jsonArray.add(jsonObject); + String bodyString = jsonArray.toString(); + + RequestBody body = RequestBody.create(mediaType, + bodyString); + Request request = new Request.Builder() + .url(URL).post(body) + .addHeader("Ocp-Apim-Subscription-Key", subscriptionKey) + .addHeader("Content-type", "application/json").build(); + Response response = CLIENT.newCall(request).execute(); + return response.body().string(); + } + + @Override + public String translate(String string) throws TranslationException { + if (subscriptionKey == null || subscriptionKey.isEmpty()) { + throw new TranslationException("Bing Translator has not been configured, credentials need to be specified"); + } + + //Translates some text into English, without specifying the source langauge. + + // HTML files were producing lots of white space at the end + string = string.trim(); + + //Google Translate required us to replace (\r\n|\n) with
+ //but Bing Translator doesn not have that requirement. + + //The free account has a maximum file size. If you have a paid account, + //you probably still want to limit file size to prevent accidentally + //translating very large documents. + if (string.length() > MAX_STRING_LENGTH) { + string = string.substring(0, MAX_STRING_LENGTH); + } + + try { + String response = postTranslationRequest(string); + return parseJSONResponse(response); + } catch (Throwable e) { + throw new TranslationException(e.getMessage()); + } + } + + @Messages({"BingTranslator.name.text=Bing Translator"}) + @Override + public String getName() { + return Bundle.BingTranslator_name_text(); + } + + @Override + public Component getComponent() { + return new JLabel("There are no settings to configure for Bing Translator"); + } + + @Override + public void saveSettings() { + //There are no settings to configure for Bing Translator + //Possible settings for the future: + //source language, target language, API key, path to JSON file of API key. + //We'll need test code to make sure that exceptions are thrown in all of + //those scenarios. + return; + } + + private String parseJSONResponse(String json_text) throws TranslationException { + /* Here is an example of the text we get from Bing when input is "gato", + the Spanish word for cat: + [ + { + "detectedLanguage": { + "language": "es", + "score": 1.0 + }, + "translations": [ + { + "text": "cat", + "to": "en" + } + ] + } + ] + */ + JsonParser parser = new JsonParser(); + try { + JsonArray responses = parser.parse(json_text).getAsJsonArray(); + //As far as I know, there's always exactly one item in the array. + JsonObject response0 = responses.get(0).getAsJsonObject(); + JsonArray translations = response0.getAsJsonArray("translations"); + JsonObject translation0 = translations.get(0).getAsJsonObject(); + String text = translation0.get("text").getAsString(); + return text; + } catch (IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) { + throw new TranslationException("JSON text does not match Bing Translator scheme: " + e); + } + } +} \ No newline at end of file diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java new file mode 100644 index 0000000000..97512faa3c --- /dev/null +++ b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java @@ -0,0 +1,89 @@ + +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.texttranslation.translators; + +import java.io.IOException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +public class BingTranslatorTest { + @Test + public void testTranslate() throws Exception { + BingTranslator translator = new BingTranslator(); + String input = "gato"; + String expectedTranslation = "cat"; + runTest(translator, input, expectedTranslation); + } + + @Test + public void testQuickStartSentence() throws Exception { + BingTranslator translator = new BingTranslator(); + String input = "Willkommen bei Microsoft Translator. Raten Sie mal, wie viele Sprachen ich spreche."; + String expectedTranslation = "Welcome to Microsoft Translator. Guess how many languages I speak."; + runTest(translator, input, expectedTranslation); + } + + @Test + public void testCharacterEscapes() throws Exception { + BingTranslator translator = new BingTranslator(); + String input = "\"gato\"";; + String expectedTranslation = "Cat"; + runTest(translator, input, expectedTranslation); + } + + @Test + public void testLineBreaks() throws Exception { + BingTranslator translator = new BingTranslator(); + String input = "gato\nperro";; + String expectedTranslation = "cat\nDog"; + runTest(translator, input, expectedTranslation); + } + + /** + * Test whether translator throws an error. This should not be part of our + * regular testing, because we are limited to only 2MB of free translations + * ever. + * @param translator A BingTranslator + * @param input Text to translate + * @param expectedTranslation Not used unless you uncomment those lines. + */ + public void runTest(BingTranslator translator, String input, String expectedTranslation) { + String translation; + try { + translation = translator.translate(input); + } + catch (Throwable e) { + fail("Bing translation produced an exception: " + e.getMessage()); + return; + }; + + /* + //It's unrealistic to expect the same answer every time, but sometimes + //it's helpful to have this in your debug process. + System.out.println(translation); + assertEquals(expectedTranslation, translation); + */ + } +} From 1408fd289bd3aa7d61f93e7fa50b188b91f34e42 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 28 May 2019 18:08:24 -0400 Subject: [PATCH 006/453] 5061 allow Bing Translator credentials to be set by user --- .../translators/BingTranslator.java | 44 +++-- .../translators/BingTranslatorSettings.java | 80 ++++++++ .../BingTranslatorSettingsPanel.form | 85 +++++++++ .../BingTranslatorSettingsPanel.java | 171 ++++++++++++++++++ .../translators/Bundle.properties | 1 + .../translators/Bundle.properties-MERGED | 2 + .../translators/GoogleTranslator.java | 2 +- 7 files changed, 368 insertions(+), 17 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java create mode 100644 Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index 892ecb5ac6..1f790d14c3 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -19,12 +19,20 @@ package org.sleuthkit.autopsy.texttranslation.translators; -import java.io.*; -import com.google.gson.*; -import com.squareup.okhttp.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; import java.awt.Component; -import javax.swing.JLabel; +import java.io.IOException; import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.texttranslation.TextTranslator; +import org.sleuthkit.autopsy.texttranslation.TranslationException; /** * Translates text by making HTTP requests to Bing Translator. @@ -32,13 +40,13 @@ import org.openide.util.NbBundle.Messages; */ @ServiceProvider(service = TextTranslator.class) public class BingTranslator implements TextTranslator{ - // Insert the subscription key here. - private String subscriptionKey = ""; - //In the String below, "en" is the target language. You can include multiple target //languages separated by commas. A full list of supported languages is here: //https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support private static final String URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=en"; + private final BingTranslatorSettingsPanel settingsPanel; + private final BingTranslatorSettings settings = new BingTranslatorSettings(); + // This sends messages to Microsoft. private final OkHttpClient CLIENT = new OkHttpClient(); @@ -47,6 +55,15 @@ public class BingTranslator implements TextTranslator{ //paid account that's willing to pay for long documents. private final int MAX_STRING_LENGTH = 5000; + + public BingTranslator(){ + settingsPanel = new BingTranslatorSettingsPanel(settings.getCredentials()); + } + + static String getMicrosftTranlatorUrl(){ + return URL; + } + /** * Converts an input test to the JSON format required by Bing Translator, * posts it to Microsoft, and returns the JSON text response. @@ -68,7 +85,7 @@ public class BingTranslator implements TextTranslator{ bodyString); Request request = new Request.Builder() .url(URL).post(body) - .addHeader("Ocp-Apim-Subscription-Key", subscriptionKey) + .addHeader("Ocp-Apim-Subscription-Key", settings.getCredentials()) .addHeader("Content-type", "application/json").build(); Response response = CLIENT.newCall(request).execute(); return response.body().string(); @@ -76,7 +93,7 @@ public class BingTranslator implements TextTranslator{ @Override public String translate(String string) throws TranslationException { - if (subscriptionKey == null || subscriptionKey.isEmpty()) { + if (settings.getCredentials() == null || settings.getCredentials().isEmpty()) { throw new TranslationException("Bing Translator has not been configured, credentials need to be specified"); } @@ -111,17 +128,12 @@ public class BingTranslator implements TextTranslator{ @Override public Component getComponent() { - return new JLabel("There are no settings to configure for Bing Translator"); + return settingsPanel; } @Override public void saveSettings() { - //There are no settings to configure for Bing Translator - //Possible settings for the future: - //source language, target language, API key, path to JSON file of API key. - //We'll need test code to make sure that exceptions are thrown in all of - //those scenarios. - return; + settings.setCredentials(settingsPanel.getCredentials()); } private String parseJSONResponse(String json_text) throws TranslationException { diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java new file mode 100644 index 0000000000..6d6ef47b39 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java @@ -0,0 +1,80 @@ +/* + * Autopsy + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.texttranslation.translators; + +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; + +/** + * Class to handle the settings associated with the GoogleTranslator + */ +public final class BingTranslatorSettings { + + private static final String CREDENTIALS_KEY = "Credentials"; + private static final String BING_TRANSLATE_NAME = "BingTranslate"; + private static final String DEFAULT_CREDENTIALS = ""; + private String credentials; + + /** + * Construct a new GoogleTranslatorSettingsObject + */ + BingTranslatorSettings() { + loadSettings(); + } + + /** + * Get the path to the JSON credentials file + * + * @return the path to the credentials file + */ + String getCredentials() { + return credentials; + } + + /** + * Set the path to the JSON credentials file + * + * @param path the path to the credentials file + */ + void setCredentials(String creds) { + credentials = creds; + } + + + /** + * Load the settings into memory from their on disk storage + */ + void loadSettings() { + if (!ModuleSettings.configExists(BING_TRANSLATE_NAME)) { + ModuleSettings.makeConfigFile(BING_TRANSLATE_NAME); + } + if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, CREDENTIALS_KEY)) { + credentials = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, CREDENTIALS_KEY); + } else { + credentials = DEFAULT_CREDENTIALS; + } + } + + /** + * Save the setting from memory to their location on disk + */ + void saveSettings() { + ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, CREDENTIALS_KEY, credentials); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form new file mode 100644 index 0000000000..ae87b5a187 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java new file mode 100644 index 0000000000..ae8da0adcf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -0,0 +1,171 @@ +/* + * Autopsy + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.texttranslation.translators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import com.google.gson.JsonObject; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; +import java.io.IOException; +import java.util.logging.Logger; + +/** + * Settings panel for the GoogleTranslator + */ +public class BingTranslatorSettingsPanel extends javax.swing.JPanel { + + private static final Logger logger = Logger.getLogger(BingTranslatorSettingsPanel.class.getName()); + private static final long serialVersionUID = 1L; + + /** + * Creates new form GoogleTranslatorSettingsPanel + */ + public BingTranslatorSettingsPanel(String credentials) { + initComponents(); + credentialsField.setText(credentials); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + credentialsLabel = new javax.swing.JLabel(); + credentialsField = new javax.swing.JTextField(); + warningLabel = new javax.swing.JLabel(); + testButton = new javax.swing.JButton(); + + org.openide.awt.Mnemonics.setLocalizedText(credentialsLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N + + warningLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.warningLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(testButton, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.testButton.text")); // NOI18N + testButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + testButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addComponent(credentialsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 86, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(credentialsField, javax.swing.GroupLayout.DEFAULT_SIZE, 463, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(testButton) + .addGap(8, 8, 8)))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(credentialsLabel) + .addComponent(credentialsField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(testButton)) + .addGap(31, 31, 31) + .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(29, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed + if (testTranslationSetup()) { + warningLabel.setText(""); + } else { + warningLabel.setText("Invalid translation credentials"); + } + }//GEN-LAST:event_testButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTextField credentialsField; + private javax.swing.JLabel credentialsLabel; + private javax.swing.JButton testButton; + private javax.swing.JLabel warningLabel; + // End of variables declaration//GEN-END:variables + /** + * Converts an input test to the JSON format required by Bing Translator, + * posts it to Microsoft, and returns the JSON text response. + * + * @param string The input text to be translated. + * + * @return The translation response as a JSON string + * + * @throws IOException if the request could not be executed due to + * cancellation, a connectivity problem or timeout. + */ + private boolean testTranslationSetup() { + String testString = "forense"; + MediaType mediaType = MediaType.parse("application/json"); + + JsonArray jsonArray = new JsonArray(); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("Text", testString); + jsonArray.add(jsonObject); + String bodyString = jsonArray.toString(); + + RequestBody body = RequestBody.create(mediaType, + bodyString); + Request request = new Request.Builder() + .url(BingTranslator.getMicrosftTranlatorUrl()).post(body) + .addHeader("Ocp-Apim-Subscription-Key", credentialsField.getText()) + .addHeader("Content-type", "application/json").build(); + try { + Response response = new OkHttpClient().newCall(request).execute(); + JsonParser parser = new JsonParser(); + JsonArray responses = parser.parse(response.body().string()).getAsJsonArray(); + //As far as I know, there's always exactly one item in the array. + JsonObject response0 = responses.get(0).getAsJsonObject(); + JsonArray translations = response0.getAsJsonArray("translations"); + JsonObject translation0 = translations.get(0).getAsJsonObject(); + translation0.get("text").getAsString(); + return true; + } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) { + return false; + } + + } + + /** + * Get the currently set path to the JSON credentials file + * + * @return the path to the credentials file specified in the textarea + */ + String getCredentials() { + return credentialsField.getText(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties index 56782cdf33..024ddfadcd 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties @@ -2,3 +2,4 @@ GoogleTranslatorSettingsPanel.browseButton.text=Browse GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials: GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: +BingTranslatorSettingsPanel.testButton.text=Test diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED index b0e30f632c..bfe84cb856 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED @@ -1,3 +1,4 @@ +BingTranslator.name.text=Bing Translator GoogleTranslator.name.text=Google Translate GoogleTranslatorSettingsPanel.browseButton.text=Browse GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials: @@ -11,3 +12,4 @@ GoogleTranslatorSettingsPanel.fileChooser.confirmButton=Select GoogleTranslatorSettingsPanel.json.description=JSON Files GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: +BingTranslatorSettingsPanel.jButton1.text=Test diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index 38506ef936..f40b2129e2 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -106,7 +106,7 @@ public final class GoogleTranslator implements TextTranslator { } /** - * Load the Google Cloud Translation service give the currently saved + * Load the Google Cloud Translation service given the currently saved * settings */ private void loadTranslator() { From f4e36841b116d35ed1a1c63409a6f13c359426fa Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 28 May 2019 18:09:24 -0400 Subject: [PATCH 007/453] 5061 minor ui panel tweak --- .../translators/BingTranslatorSettingsPanel.form | 4 ++-- .../translators/BingTranslatorSettingsPanel.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form index ae87b5a187..6acf950f40 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -44,9 +44,9 @@ - + - + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index ae8da0adcf..b49c3645b8 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -97,9 +97,9 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addComponent(credentialsLabel) .addComponent(credentialsField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(testButton)) - .addGap(31, 31, 31) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(29, Short.MAX_VALUE)) + .addContainerGap(49, Short.MAX_VALUE)) ); }// //GEN-END:initComponents From 5d63ab2bb8c76a37afbb5a7ec216c24b361aedb2 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 29 May 2019 12:41:36 -0400 Subject: [PATCH 008/453] 4963: offload computation of S C & O columns to a background task, in order to speed up the loading of Data Results View table. --- .../datamodel/AbstractAbstractFileNode.java | 44 +++++++----- .../autopsy/datamodel/GetSCOTask.java | 71 +++++++++++++++++++ .../sleuthkit/autopsy/datamodel/SCOData.java | 56 +++++++++++++++ 3 files changed, 155 insertions(+), 16 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index fa4529e44b..d6b589f977 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -82,7 +82,8 @@ public abstract class AbstractAbstractFileNode extends A private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED); - private static final ExecutorService translationPool; + // pool to run long running translation and getSCO tasks in backgound + private static final ExecutorService translationSCOPool; private static final Integer MAX_POOL_SIZE = 10; /** @@ -101,7 +102,7 @@ public abstract class AbstractAbstractFileNode extends A } if (UserPreferences.displayTranslatedFileNames()) { - AbstractAbstractFileNode.translationPool.submit(new TranslationTask( + AbstractAbstractFileNode.translationSCOPool.submit(new TranslationTask( new WeakReference<>(this), weakPcl)); } @@ -113,8 +114,8 @@ public abstract class AbstractAbstractFileNode extends A static { //Initialize this pool only once! This will be used by every instance of AAFN //to do their heavy duty SCO column and translation updates. - translationPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, - new ThreadFactoryBuilder().setNameFormat("translation-task-thread-%d").build()); + translationSCOPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, + new ThreadFactoryBuilder().setNameFormat("translation-and-sco-task-thread-%d").build()); } /** @@ -145,6 +146,7 @@ public abstract class AbstractAbstractFileNode extends A */ enum NodeSpecificEvents { TRANSLATION_AVAILABLE, + SCO_AVAILABLE } private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { @@ -223,6 +225,18 @@ public abstract class AbstractAbstractFileNode extends A //Set the tooltip this.setShortDescription(content.getName()); updateSheet(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, content.getName())); + } else if (eventType.equals(NodeSpecificEvents.SCO_AVAILABLE.toString())) { + SCOData scoData = (SCOData)evt.getNewValue(); + if (scoData.getScoreAndDescription() != null) { + updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft())); + } + if (scoData.getComment() != null) { + updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, scoData.getComment())); + } + if (scoData.getCountAndDescription() != null && + !UserPreferences.hideCentralRepoCommentsAndOccurrences()) { + updateSheet(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft())); + } } }; /** @@ -368,18 +382,16 @@ public abstract class AbstractAbstractFileNode extends A properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, "")); } - //SCO column prereq info.. - List tags = getContentTagsFromDatabase(); - CorrelationAttributeInstance attribute = getCorrelationAttributeInstance(); - - Pair scoreAndDescription = getScorePropertyAndDescription(tags); - properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoreAndDescription.getRight(), scoreAndDescription.getLeft())); - DataResultViewerTable.HasCommentStatus comment = getCommentProperty(tags, attribute); - properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, comment)); - if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) { - Pair countAndDescription = getCountPropertyAndDescription(attribute); - properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), countAndDescription.getRight(), countAndDescription.getLeft())); - } + // Create place holders for S C O + properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), NO_DESCR, "")); + properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, "")); + properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), NO_DESCR, "")); + + + // Get the SCO columns data in a background task + AbstractAbstractFileNode.translationSCOPool.submit(new GetSCOTask( + new WeakReference<>(this), weakPcl)); + properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content))); properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getMtime(), content))); properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getCtime(), content))); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java new file mode 100644 index 0000000000..e8a7b5b1f5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java @@ -0,0 +1,71 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; +import java.util.List; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.events.AutopsyEvent; +import org.sleuthkit.datamodel.ContentTag; + +/** + * Background task to get Score, Comment and Occurrences values for a Abstract file node. + * + */ +class GetSCOTask implements Runnable { + + private final WeakReference> weakNodeRef; + private final PropertyChangeListener listener; + + public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { + this.weakNodeRef = weakContentRef; + this.listener = listener; + } + + @Override + public void run() { + AbstractAbstractFileNode fileNode = weakNodeRef.get(); + + //Check for stale reference + if (fileNode == null) { + return; + } + + // get the SCO column values + List tags = fileNode.getContentTagsFromDatabase(); + CorrelationAttributeInstance attribute = fileNode.getCorrelationAttributeInstance(); + + SCOData scoData = new SCOData(); + scoData.setScoreAndDescription(fileNode.getScorePropertyAndDescription(tags)); + scoData.setComment(fileNode.getCommentProperty(tags, attribute)); + if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) { + scoData.setCountAndDescription(fileNode.getCountPropertyAndDescription(attribute)); + } + + if (listener != null) { + listener.propertyChange(new PropertyChangeEvent( + AutopsyEvent.SourceType.LOCAL.toString(), + AbstractAbstractFileNode.NodeSpecificEvents.SCO_AVAILABLE.toString(), + null, scoData)); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java b/Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java new file mode 100644 index 0000000000..a9dd99369d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SCOData.java @@ -0,0 +1,56 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import org.apache.commons.lang3.tuple.Pair; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; + +/** + * Container to bag the S C & O data for an abstract file node. + * + */ +class SCOData { + + private Pair scoreAndDescription = null; + private DataResultViewerTable.HasCommentStatus comment = null; + private Pair countAndDescription = null; + + Pair getScoreAndDescription() { + return scoreAndDescription; + } + + DataResultViewerTable.HasCommentStatus getComment() { + return comment; + } + + Pair getCountAndDescription() { + return countAndDescription; + } + + void setScoreAndDescription(Pair scoreAndDescription) { + this.scoreAndDescription = scoreAndDescription; + } + void setComment(DataResultViewerTable.HasCommentStatus comment) { + this.comment = comment; + } + void setCountAndDescription(Pair countAndDescription) { + this.countAndDescription = countAndDescription; + } + +} From 6c866185058486c0560f829ef6e4cbdbf2e7b1ab Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 29 May 2019 17:01:20 -0400 Subject: [PATCH 009/453] intermitten commit while I work on something else --- .../EmailMessageThreader.java | 506 ++++++++++-------- 1 file changed, 277 insertions(+), 229 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java index c92bdb2fa8..a23c91ab25 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java @@ -27,106 +27,108 @@ import java.util.Map; import java.util.Set; /** - * Given a list of email messages arranges the message into threads using the message - * reference lists. - * - * This threader is based heavely off of the algorithum found at + * Given a list of email messages arranges the message into threads using the + * message reference lists. + * + * This threader is based heavely off of the algorithum found at + * * "message threading." by Jamie Zawinski - * + * */ final class EmailMessageThreader { - + private int bogus_id_count = 0; public Set threadMessages(List emailMessages) { HashMap id_table = createIDTable(emailMessages); Set rootSet = getRootSet(id_table); - + pruneEmptyContainers(rootSet); - + Set finalSet = groupBySubject(rootSet); printContainerSet(finalSet, ""); - + return finalSet; } /** - * Walks the list of emailMessages creating a Container object for each - * unique message ID found. Adds the emailMessage to the container where + * Walks the list of emailMessages creating a Container object for each + * unique message ID found. Adds the emailMessage to the container where * possible. - * + * * @param emailMessages - * @return + * + * @return */ private HashMap createIDTable(List emailMessages) { HashMap id_table = new HashMap<>(); - - for(EmailMessage message: emailMessages) { + + for (EmailMessage message : emailMessages) { String messageID = message.getMessageID(); - + // Check the id_table for an existing Container for message-id Container container = id_table.get(messageID); - + // An existing container for message-id was found - if(container != null) { + if (container != null) { // If the existing Container has a message already assocated with it // emailMessage maybe a duplicate, so we don't lose the existance of // the duplicate message assign it a bogus message-id - if(container.hasMessage()) { + if (container.hasMessage()) { messageID = String.format("", bogus_id_count++); container = null; } else { container.setMessage(message); } - } - - if(container == null) { + } + + if (container == null) { container = new Container(message); id_table.put(messageID, container); } - + processMessageReferences(message, container, id_table); } - + return id_table; } - + /** - * Loops throught message's list of references, creating objects as needed + * Loops throught message's list of references, creating objects as needed * and setting up the parent child relationships amoung the messages. - * - * @param message The current email messags + * + * @param message The current email messags * @param container Container object for message - * @param id_table Hashtable of known message-id\container pairs + * @param id_table Hashtable of known message-id\container pairs */ void processMessageReferences(EmailMessage message, Container container, Map id_table) { List referenceList = message.getReferences(); - + // Make sure the inReplyToID is in the list of references String inReplyToID = message.getInReplyToID(); - if(inReplyToID != null && !inReplyToID.isEmpty()) { - if(referenceList == null) { + if (inReplyToID != null && !inReplyToID.isEmpty()) { + if (referenceList == null) { referenceList = new ArrayList<>(); } - + referenceList.add(inReplyToID); } - + // No references, nothing to do - if(referenceList == null) { + if (referenceList == null) { return; } Container parent_ref = null; Container ref; - for(String refID: referenceList){ + for (String refID : referenceList) { // Check id_table to see if there is already a container for this // reference id, if not create a new Container and add to table ref = id_table.get(refID); - if(ref == null ) { + if (ref == null) { ref = new Container(); id_table.put(refID, ref); } @@ -142,12 +144,12 @@ final class EmailMessageThreader { parent_ref = ref; } - + // If the parent_ref and container are already linked, don't change // anything if (parent_ref != null - && (parent_ref == container - || container.isChild(parent_ref))) { + && (parent_ref == container + || container.isChild(parent_ref))) { parent_ref = null; } @@ -155,96 +157,113 @@ final class EmailMessageThreader { // the list of references from another message. parent_ref will be // the real parent of container so throw away the old parent and set a // new one. - if(container.hasParent()) { - container.getParent().removeChild(container); - container.setParent(null); + if (container.hasParent()) { + container.getParent().removeChild(container); + container.setParent(null); } - if(parent_ref != null) { + if (parent_ref != null) { container.setParent(container); parent_ref.addChild(container); } } - + /** - * Creates a set of root container messages from the message-ID hashtable. - * A root Container is container that does not have a parent container. - * + * Creates a set of root container messages from the message-ID hashtable. A + * root Container is container that does not have a parent container. + * * @param id_table Table of all known Containers - * + * * @return A set of the root containers. */ Set getRootSet(HashMap id_table) { HashSet rootSet = new HashSet<>(); - - id_table.values().stream().filter((container) -> - (!container.hasParent())).forEachOrdered((container) -> { + + id_table.values().stream().filter((container) + -> (!container.hasParent())).forEachOrdered((container) -> { rootSet.add(container); }); - + return rootSet; } - + /** * Remove Containers from containerSet if they do not have a message or * children. - * + * * @param containerSet A set of Container objects */ void pruneEmptyContainers(Set containerSet) { + Set containersToRemove = new HashSet<>(); containerSet.forEach((container) -> { - if(!container.hasMessage() && !container.hasChildren()) { - containerSet.remove(container); + if (!container.hasMessage() && !container.hasChildren()) { +// containerSet.remove(container); + containersToRemove.add(container); } else { pruneChildren(container); } }); + + containerSet.removeAll(containersToRemove); } - + /** - * Recursively work through the list of parent's children removing - * empy containers. - * - * @param parent + * Recursively work through the list of parent's children removing empy + * containers. + * + * @param parent */ - void pruneChildren(Container parent) { - if(parent == null) { - return; - } - - Set children = parent.getChildren(); - Container grandParent = parent.getParent(); - - if (children == null) { - return; + boolean pruneChildren(Container parent) { + if (parent == null) { + return false; } - children.stream().map((child) -> { - // Parent is an empty container. Reparent the children to their - // grandparent. - if (!parent.hasMessage() && grandParent != null) { - child.setParent(grandParent); - grandParent.addChild(child); - grandParent.removeChild(parent); - parent.setParent(null); - parent.clearChildren(); + Set children = parent.getChildren(); + Container grandParent = parent.getParent(); + + if (children == null) { + return false; + } + + Set remove = new HashSet<>(); + Set add = new HashSet<>(); + for (Container child : parent.getChildren()) { + if (pruneChildren(child)) { +// parent.addChildren(child.getChildren()); +// parent.removeChild(child); + remove.add(child); + add.addAll(child.getChildren()); + child.setParent(null); + child.clearChildren(); + } - return child; - }).forEachOrdered((child) -> { - pruneChildren(child); - }); + } + + parent.addChildren(add); + parent.removeChildren(remove); + + if (!parent.hasMessage() && grandParent != null) { + for (Container child : children) { + child.setParent(grandParent); + } + return true; + } + + return false; } - + /** - * Now that the emails are grouped together by references\message ID take another - * pass through and group together messages with the same simplified subject. - * - * This may cause "root" messages with identical subjects to get grouped - * together as children of an empty container. The code that uses the - * thread information can decide what to do in that sisiuation as those message + * Now that the emails are grouped together by references\message ID take + * another pass through and group together messages with the same simplified + * subject. + * + * This may cause "root" messages with identical subjects to get grouped + * together as children of an empty container. The code that uses the thread + * information can decide what to do in that sisiuation as those message * maybe part of a common thread or maybe their own unique messages. - * - * @param rootSet + * + * @param rootSet + * * @return Final set of threaded messages. */ Set groupBySubject(Set rootSet) { @@ -254,65 +273,64 @@ final class EmailMessageThreader { for (Container rootSetContainer : rootSet) { String rootSubject = rootSetContainer.getSimplifiedSubject(); - + Container tableContainer = subject_table.get(rootSubject); - if(tableContainer == null || tableContainer == rootSetContainer) { + if (tableContainer == null || tableContainer == rootSetContainer) { finalSet.add(rootSetContainer); continue; } - + // If both containers are dummy/empty append the children of one to the other - if(tableContainer.getMessage() == null && rootSetContainer.getMessage() == null) { + if (tableContainer.getMessage() == null && rootSetContainer.getMessage() == null) { tableContainer.addChildren(rootSetContainer.getChildren()); rootSetContainer.clearChildren(); continue; } - + // one container is empty, but the other is not, make the non-empty one be a // child of the empty - if( (tableContainer.getMessage() == null && rootSetContainer.getMessage() != null) || - (tableContainer.getMessage() != null && rootSetContainer.getMessage() == null)){ - - if(tableContainer.getMessage() == null) { - tableContainer.addChild(rootSetContainer); - + if ((tableContainer.getMessage() == null && rootSetContainer.getMessage() != null) + || (tableContainer.getMessage() != null && rootSetContainer.getMessage() == null)) { + + if (tableContainer.getMessage() == null) { + tableContainer.addChild(rootSetContainer); + } else { rootSetContainer.addChild(tableContainer); subject_table.remove(rootSubject, tableContainer); subject_table.put(rootSubject, rootSetContainer); - + finalSet.add(rootSetContainer); } - + continue; } - + // tableContainer is non-empty and it's message's subject does not begin // with 'RE:' but rootSetContainer's message does begin with 'RE:', then // make rootSetContainer a child of tableContainer - if(tableContainer.getMessage() != null && - !tableContainer.isReplySubject() && - rootSetContainer.isReplySubject()) { + if (tableContainer.getMessage() != null + && !tableContainer.isReplySubject() + && rootSetContainer.isReplySubject()) { tableContainer.addChild(rootSetContainer); continue; } - + // If table container is non-empy, and table container's subject does // begin with 'RE:', but rootSetContainer does not start with 'RE:' // make tableContainer a child of rootSetContainer - if(tableContainer.getMessage() != null && - tableContainer.isReplySubject() && - !rootSetContainer.isReplySubject()) { + if (tableContainer.getMessage() != null + && tableContainer.isReplySubject() + && !rootSetContainer.isReplySubject()) { rootSetContainer.addChild(tableContainer); subject_table.put(rootSubject, rootSetContainer); finalSet.add(rootSetContainer); continue; } - + // rootSetContainer and tableContainer either both have 'RE' or // don't. Create a new dummy container with both containers as // children. - Container newParent = new Container(); newParent.addChild(tableContainer); newParent.addChild(rootSetContainer); @@ -323,12 +341,14 @@ final class EmailMessageThreader { } return finalSet; } - + /** - * Creates a Hashtable of Container and subjects. There will be one Container - * subject pair for each unique subject. + * Creates a Hashtable of Container and subjects. There will be one + * Container subject pair for each unique subject. + * * @param rootSet The set of "root" Containers - * @return + * + * @return */ HashMap createSubjectTable(Set rootSet) { HashMap subject_table = new HashMap<>(); @@ -340,13 +360,13 @@ final class EmailMessageThreader { if (rootSetContainer.hasMessage()) { subject = rootSetContainer.getMessage().getSimplifiedSubject(); reSubject = rootSetContainer.getMessage().isReplySubject(); - } else if(rootSetContainer.hasChildren()){ + } else if (rootSetContainer.hasChildren()) { Iterator childrenIterator = rootSetContainer.getChildren().iterator(); - while(childrenIterator.hasNext()) { + while (childrenIterator.hasNext()) { EmailMessage childMessage = childrenIterator.next().getMessage(); - if(childMessage != null) { + if (childMessage != null) { subject = childMessage.getSimplifiedSubject(); - if(!subject.isEmpty()) { + if (!subject.isEmpty()) { reSubject = childMessage.isReplySubject(); break; } @@ -354,251 +374,279 @@ final class EmailMessageThreader { } } - if (subject.isEmpty()) { + if (subject == null || subject.isEmpty()) { continue; // Give up on this container } - + Container tableContainer = subject_table.get(subject); - - if(tableContainer == null || // Not in table - (tableContainer.getMessage() != null && rootSetContainer.getMessage() == null) || // One in the table is empty, but current is not - (!reSubject && (tableContainer.getMessage() != null && tableContainer.getMessage().isReplySubject()))) { //current doesn't have RE in it, use current instead of the one in the table + + if (tableContainer == null + || // Not in table + (tableContainer.getMessage() != null && rootSetContainer.getMessage() == null) + || // One in the table is empty, but current is not + (!reSubject && (tableContainer.getMessage() != null && tableContainer.getMessage().isReplySubject()))) { //current doesn't have RE in it, use current instead of the one in the table subject_table.put(subject, rootSetContainer); - } + } } - + return subject_table; } - + /** - * Prints a set of containers and their children. - * + * Prints a set of containers and their children. + * * @param containerSet Set of containers to print out - * @param prefix A prefix for each line to show child depth. - */ + * @param prefix A prefix for each line to show child depth. + */ private void printContainerSet(Set containerSet, String prefix) { containerSet.stream().map((container) -> { - if(container.getMessage() != null) { + if (container.getMessage() != null) { System.out.println(prefix + container.getMessage().getSubject()); } else { System.out.println(""); } return container; }).filter((container) -> (container.hasChildren())).forEachOrdered((container) -> { - printContainerSet(container.getChildren(), prefix+ "\t"); + printContainerSet(container.getChildren(), prefix + "\t"); }); } - + /** * The container object is used to wrap and email message and track the * messages parent and child messages. */ - final class Container{ + final class Container { + private EmailMessage message; private Container parent; - private Set children; - + private Set children; + /** * Constructs an empy container. */ - Container() {} - + Container() { + } + /** - * - * @param message + * + * @param message */ Container(EmailMessage message) { this.message = message; } - + /** * Returns the EmailMessage object - * - * @return + * + * @return */ EmailMessage getMessage() { return message; } - + /** * Set the Container EmailMessage object. - * + * * @param message - The container EmailMessage */ void setMessage(EmailMessage message) { this.message = message; } - + /** * Return whether or not this Container has a valid EmailMessage object. - * + * * @return True if EmailMessage has been set otherwise false */ boolean hasMessage() { return message != null; } - + /** * Returns the Simplified Subject (original subject without RE:) of the - * EmailMessage or if this is an empty Container with Children, return + * EmailMessage or if this is an empty Container with Children, return * the simplified subject of one of the children. - * + * * @return Simplified subject of this Container */ String getSimplifiedSubject() { String subject = ""; - if(message != null) { - subject = message.getSimplifiedSubject(); - } else if(children != null) { - for(Container child: children) { - if(child.hasMessage()) { + if (message != null) { + subject = message.getSimplifiedSubject(); + } else if (children != null) { + for (Container child : children) { + if (child.hasMessage()) { subject = child.getSimplifiedSubject(); } - - if(subject != null && !subject.isEmpty()) { + + if (subject != null && !subject.isEmpty()) { break; } } } return subject; } - + /** * Simialar to getSimplifiedSubject, isReplySubject is a helper function * that will return the isReplySubject of the Containers message or if * this is an empty container, the state of one of the children. - * - * @return + * + * @return */ boolean isReplySubject() { - if(message != null) { + if (message != null) { return message.isReplySubject(); - } else if(children != null) { - for(Container child: children) { - if(child.hasMessage()) { + } else if (children != null) { + for (Container child : children) { + if (child.hasMessage()) { boolean isReply = child.isReplySubject(); - - if(isReply) { + + if (isReply) { return isReply; } } - } + } } - + return false; } - + /** * Returns the parent Container of this Container. - * + * * @return The Container parent or null if one is not set */ Container getParent() { return parent; } - + /** + * Sets the given container as the parent of this object. * - * @param container + * @param container - the object to set as the parent */ void setParent(Container container) { parent = container; } - + /** - * - * @return + * + * @return */ boolean hasParent() { return parent != null; } - + /** - * - * @param child - * @return + * Adds the specified Container to the list of children. + * + * @param child - the Container to add to the child list + * + * @return true, if the element was added to the children list */ boolean addChild(Container child) { - if(children == null) { - children = new HashSet<>(); + if (children == null) { + children = new HashSet<>(); } - + return children.add(child); } - + /** - * - * @param children - * @return + * Adds to the list of children all of the elements that are contained + * in the specified collection. + * + * @param children - set containing the Containers to be added to the + * list of children + * + * @return true if the list of childrent was changed as a result of this + * call */ boolean addChildren(Set children) { - if(children == null || children.isEmpty()) { + if (children == null || children.isEmpty()) { return false; } - - if(this.children == null) { - this.children = new HashSet<>(); + + if (this.children == null) { + this.children = new HashSet<>(); } - + return this.children.addAll(children); } - + /** - * + * Removes from the children list all of the elements that are contained + * in the specified collection. + * + * @param children - set containing the elements to be removed from the + * list of children + * + * @return true if the set was changed as a result of this call + */ + boolean removeChildren(Set children) { + if (children != null) { + return this.children.removeAll(children); + } + + return false; + } + + /** + * Clears the Containers list of children. + * */ void clearChildren() { children.clear(); } - + /** - * - * @param child - * @return + * Removes the given container from the list of children. + * + * @param child - the container to remove from the children list + * + * @return - True if the given container successfully removed from the + * list of childrent */ boolean removeChild(Container child) { return children.remove(child); } - + /** - * - * @return + * Returns whether or not this container has children. + * + * @return True if the child list is not null or empty */ boolean hasChildren() { return children != null && !children.isEmpty(); } - + /** - * - * @return + * Returns the list of children of this container. + * + * @return The child list. */ Set getChildren() { return children; } - + /** - * - * @return - */ -// boolean hasSiblings() { -// return children == null ? false : (children.size() > 1); -// } - - /** - * - * @param container - * @return + * Search all of this containers children to make sure that the given + * container is not a related. + * + * @param container - the container object to search for + * + * @return True if the given container is in the child tree of this + * container, false otherwise. */ boolean isChild(Container container) { - if(children == null || children.isEmpty()) { + if (children == null || children.isEmpty()) { return false; - } else if(children.contains(container)) { + } else if (children.contains(container)) { return true; } else { - if (children.stream().anyMatch((child) -> (child.isChild(container)))) { - return true; - } - return false; + return children.stream().anyMatch((child) -> (child.isChild(container))); } } } From 3ea254c7ae77d3865e74dc68f06c53f27f5434e8 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 30 May 2019 11:06:20 -0400 Subject: [PATCH 010/453] LAst clean up before PR --- .../thunderbirdparser/EmailMessage.java | 19 +++++++++ .../EmailMessageThreader.java | 41 ++++++++++++++----- .../ThunderbirdMboxFileIngestModule.java | 4 +- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java index 6b2af09f21..b633963e9f 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java @@ -50,6 +50,7 @@ class EmailMessage { private List references = null; private String simplifiedSubject = ""; private boolean isReplySubject = false; + private String messageThreadID = ""; boolean hasAttachment() { return hasAttachment; @@ -266,6 +267,24 @@ class EmailMessage { void setReferences(List references) { this.references = references; } + + /** + * Sets the ThreadID of this message. + * + * @param threadID - the thread ID to set + */ + void setMessageThreadID(String threadID) { + this.messageThreadID = threadID; + } + + /** + * Returns the ThreadID for this message. + * + * @return - the message thread ID or "" is non is available + */ + String getMessageThreadID() { + return this.messageThreadID; + } /** * A Record to hold generic information about attachments. diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java index a23c91ab25..bc21720126 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java @@ -39,7 +39,7 @@ final class EmailMessageThreader { private int bogus_id_count = 0; - public Set threadMessages(List emailMessages) { + public void threadMessages(List emailMessages, String threadIDPrefix) { HashMap id_table = createIDTable(emailMessages); Set rootSet = getRootSet(id_table); @@ -47,9 +47,7 @@ final class EmailMessageThreader { Set finalSet = groupBySubject(rootSet); - printContainerSet(finalSet, ""); - - return finalSet; + assignThreadIDs(finalSet, threadIDPrefix); } /** @@ -59,7 +57,7 @@ final class EmailMessageThreader { * * @param emailMessages * - * @return + * @return - HashMap of all message where the key is the message-ID of the message */ private HashMap createIDTable(List emailMessages) { HashMap id_table = new HashMap<>(); @@ -197,7 +195,6 @@ final class EmailMessageThreader { Set containersToRemove = new HashSet<>(); containerSet.forEach((container) -> { if (!container.hasMessage() && !container.hasChildren()) { -// containerSet.remove(container); containersToRemove.add(container); } else { pruneChildren(container); @@ -229,8 +226,6 @@ final class EmailMessageThreader { Set add = new HashSet<>(); for (Container child : parent.getChildren()) { if (pruneChildren(child)) { -// parent.addChildren(child.getChildren()); -// parent.removeChild(child); remove.add(child); add.addAll(child.getChildren()); child.setParent(null); @@ -243,9 +238,9 @@ final class EmailMessageThreader { parent.removeChildren(remove); if (!parent.hasMessage() && grandParent != null) { - for (Container child : children) { + children.forEach((child) -> { child.setParent(grandParent); - } + }); return true; } @@ -392,6 +387,32 @@ final class EmailMessageThreader { return subject_table; } + + private void assignThreadIDs(Set containerSet, String IDPrefix) { + int threadCounter = 0; + + for(Container container: containerSet) { + String threadID = String.format("%s-%d", IDPrefix, threadCounter++); + addThreadID(container, threadID); + } + } + + private void addThreadID(Container container, String threadID) { + if(container == null) { + return; + } + + EmailMessage message = container.getMessage(); + if(message != null) { + message.setMessageThreadID(threadID); + } + + if(container.hasChildren()) { + for(Container child: container.getChildren()) { + addThreadID(child, threadID); + } + } + } /** * Prints a set of containers and their children. diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index a6728507ea..c0771c996e 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -411,7 +411,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { List derivedFiles = new ArrayList<>(); EmailMessageThreader threader = new EmailMessageThreader(); - threader.threadMessages(emails); + threader.threadMessages(emails, String.format("%d", abstractFile.getId())); for (EmailMessage email : emails) { BlackboardArtifact msgArtifact = addEmailArtifact(email, abstractFile); @@ -511,6 +511,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String subject = email.getSubject(); long id = email.getId(); String localPath = email.getLocalPath(); + String threadID = email.getMessageThreadID(); List senderAddressList = new ArrayList<>(); String senderAddress; @@ -568,6 +569,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes); addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes); addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes); + addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes); try { From 5221f35fad788b85b6b2359ef756006f70196850 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 30 May 2019 11:24:04 -0400 Subject: [PATCH 011/453] 5061 disable bing tests since service won't have credentials --- ...guageWrapper.java => LanguageWrapper.java} | 0 .../translators/BingTranslatorTest.java | 117 +++++++++--------- 2 files changed, 60 insertions(+), 57 deletions(-) rename Core/src/org/sleuthkit/autopsy/texttranslation/translators/{GoogleLanguageWrapper.java => LanguageWrapper.java} (100%) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleLanguageWrapper.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java similarity index 100% rename from Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleLanguageWrapper.java rename to Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java index 97512faa3c..48c29543d3 100644 --- a/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java @@ -1,4 +1,3 @@ - /* * Autopsy Forensic Browser * @@ -17,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.texttranslation.translators; import java.io.IOException; @@ -28,62 +26,67 @@ import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; -public class BingTranslatorTest { +/** + * Tests for the BingTranslator translation service, these tests have been + * commented out because they require credentials to perform + */ +public class BingTranslatorTest { + @Test public void testTranslate() throws Exception { - BingTranslator translator = new BingTranslator(); - String input = "gato"; - String expectedTranslation = "cat"; - runTest(translator, input, expectedTranslation); - } - - @Test - public void testQuickStartSentence() throws Exception { - BingTranslator translator = new BingTranslator(); - String input = "Willkommen bei Microsoft Translator. Raten Sie mal, wie viele Sprachen ich spreche."; - String expectedTranslation = "Welcome to Microsoft Translator. Guess how many languages I speak."; - runTest(translator, input, expectedTranslation); - } - - @Test - public void testCharacterEscapes() throws Exception { - BingTranslator translator = new BingTranslator(); - String input = "\"gato\"";; - String expectedTranslation = "Cat"; - runTest(translator, input, expectedTranslation); - } - - @Test - public void testLineBreaks() throws Exception { - BingTranslator translator = new BingTranslator(); - String input = "gato\nperro";; - String expectedTranslation = "cat\nDog"; - runTest(translator, input, expectedTranslation); - } - - /** - * Test whether translator throws an error. This should not be part of our - * regular testing, because we are limited to only 2MB of free translations - * ever. - * @param translator A BingTranslator - * @param input Text to translate - * @param expectedTranslation Not used unless you uncomment those lines. - */ - public void runTest(BingTranslator translator, String input, String expectedTranslation) { - String translation; - try { - translation = translator.translate(input); - } - catch (Throwable e) { - fail("Bing translation produced an exception: " + e.getMessage()); - return; - }; - - /* - //It's unrealistic to expect the same answer every time, but sometimes - //it's helpful to have this in your debug process. - System.out.println(translation); - assertEquals(expectedTranslation, translation); - */ +// BingTranslator translator = new BingTranslator(); +// String input = "gato"; +// String expectedTranslation = "cat"; +// runTest(translator, input, expectedTranslation); } + +// @Test +// public void testQuickStartSentence() throws Exception { +// BingTranslator translator = new BingTranslator(); +// String input = "Willkommen bei Microsoft Translator. Raten Sie mal, wie viele Sprachen ich spreche."; +// String expectedTranslation = "Welcome to Microsoft Translator. Guess how many languages I speak."; +// runTest(translator, input, expectedTranslation); +// } +// +// @Test +// public void testCharacterEscapes() throws Exception { +// BingTranslator translator = new BingTranslator(); +// String input = "\"gato\"";; +// String expectedTranslation = "Cat"; +// runTest(translator, input, expectedTranslation); +// } +// +// @Test +// public void testLineBreaks() throws Exception { +// BingTranslator translator = new BingTranslator(); +// String input = "gato\nperro";; +// String expectedTranslation = "cat\nDog"; +// runTest(translator, input, expectedTranslation); +// } +// +// /** +// * Test whether translator throws an error. This should not be part of our +// * regular testing, because we are limited to only 2MB of free translations +// * ever. +// * @param translator A BingTranslator +// * @param input Text to translate +// * @param expectedTranslation Not used unless you uncomment those lines. +// */ +// public void runTest(BingTranslator translator, String input, String expectedTranslation) { +// String translation; +// try { +// translation = translator.translate(input); +// } +// catch (Throwable e) { +// fail("Bing translation produced an exception: " + e.getMessage()); +// return; +// }; +// +// /* +// //It's unrealistic to expect the same answer every time, but sometimes +// //it's helpful to have this in your debug process. +// System.out.println(translation); +// assertEquals(expectedTranslation, translation); +// */ +// } } From 0c5ac83644ad09a3e72b1371dfb58c2a8ea2ddf4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 30 May 2019 11:24:38 -0400 Subject: [PATCH 012/453] 5061 adjust BingTranslator to use user specified settings --- .../translators/BingTranslator.java | 20 +-- .../translators/BingTranslatorSettings.java | 33 +++- .../BingTranslatorSettingsPanel.form | 80 ++++++++- .../BingTranslatorSettingsPanel.java | 169 ++++++++++++++---- .../translators/Bundle.properties | 3 + .../translators/Bundle.properties-MERGED | 5 +- .../GoogleTranslatorSettingsPanel.form | 4 +- .../GoogleTranslatorSettingsPanel.java | 8 +- .../translators/LanguageWrapper.java | 32 +++- 9 files changed, 284 insertions(+), 70 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index 1f790d14c3..4aaba09ee5 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -43,7 +43,7 @@ public class BingTranslator implements TextTranslator{ //In the String below, "en" is the target language. You can include multiple target //languages separated by commas. A full list of supported languages is here: //https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support - private static final String URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=en"; + private static final String BASE_URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to="; private final BingTranslatorSettingsPanel settingsPanel; private final BingTranslatorSettings settings = new BingTranslatorSettings(); @@ -57,11 +57,11 @@ public class BingTranslator implements TextTranslator{ public BingTranslator(){ - settingsPanel = new BingTranslatorSettingsPanel(settings.getCredentials()); + settingsPanel = new BingTranslatorSettingsPanel(settings.getCredentials(), settings.getTargetLanguageCode()); } - static String getMicrosftTranlatorUrl(){ - return URL; + private String getTranlatorUrl(){ + return BASE_URL + settings.getTargetLanguageCode(); } /** @@ -84,7 +84,7 @@ public class BingTranslator implements TextTranslator{ RequestBody body = RequestBody.create(mediaType, bodyString); Request request = new Request.Builder() - .url(URL).post(body) + .url(getTranlatorUrl()).post(body) .addHeader("Ocp-Apim-Subscription-Key", settings.getCredentials()) .addHeader("Content-type", "application/json").build(); Response response = CLIENT.newCall(request).execute(); @@ -96,11 +96,10 @@ public class BingTranslator implements TextTranslator{ if (settings.getCredentials() == null || settings.getCredentials().isEmpty()) { throw new TranslationException("Bing Translator has not been configured, credentials need to be specified"); } - + String toTranslate = string.trim(); //Translates some text into English, without specifying the source langauge. // HTML files were producing lots of white space at the end - string = string.trim(); //Google Translate required us to replace (\r\n|\n) with
//but Bing Translator doesn not have that requirement. @@ -108,12 +107,12 @@ public class BingTranslator implements TextTranslator{ //The free account has a maximum file size. If you have a paid account, //you probably still want to limit file size to prevent accidentally //translating very large documents. - if (string.length() > MAX_STRING_LENGTH) { - string = string.substring(0, MAX_STRING_LENGTH); + if (toTranslate.length() > MAX_STRING_LENGTH) { + toTranslate = toTranslate.substring(0, MAX_STRING_LENGTH); } try { - String response = postTranslationRequest(string); + String response = postTranslationRequest(toTranslate); return parseJSONResponse(response); } catch (Throwable e) { throw new TranslationException(e.getMessage()); @@ -134,6 +133,7 @@ public class BingTranslator implements TextTranslator{ @Override public void saveSettings() { settings.setCredentials(settingsPanel.getCredentials()); + settings.setTargetLanguageCode(settingsPanel.getTargetLanguageCode()); } private String parseJSONResponse(String json_text) throws TranslationException { diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java index 6d6ef47b39..ca68adb77c 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java @@ -29,7 +29,10 @@ public final class BingTranslatorSettings { private static final String CREDENTIALS_KEY = "Credentials"; private static final String BING_TRANSLATE_NAME = "BingTranslate"; private static final String DEFAULT_CREDENTIALS = ""; + private static final String DEFAULT_TARGET_LANGUAGE = "en"; + private static final String TARGET_LANGUAGE_CODE_KEY = "TargetLanguageCode"; private String credentials; + private String targetLanguageCode; /** * Construct a new GoogleTranslatorSettingsObject @@ -56,7 +59,6 @@ public final class BingTranslatorSettings { credentials = creds; } - /** * Load the settings into memory from their on disk storage */ @@ -69,6 +71,34 @@ public final class BingTranslatorSettings { } else { credentials = DEFAULT_CREDENTIALS; } + if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) { + targetLanguageCode = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY); + } else { + targetLanguageCode = DEFAULT_TARGET_LANGUAGE; + } + } + + /** + * Get the target language code + * + * @return the code used to identify the target language + */ + String getTargetLanguageCode() { + return targetLanguageCode; + } + + /** + * Set the target language code. If a blank code is specified it sets the + * default code instead. + * + * @param code the target language code to set + */ + void setTargetLanguageCode(String code) { + if (StringUtils.isBlank(code)) { + targetLanguageCode = DEFAULT_TARGET_LANGUAGE; + } else { + targetLanguageCode = code; + } } /** @@ -76,5 +106,6 @@ public final class BingTranslatorSettings { */ void saveSettings() { ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, CREDENTIALS_KEY, credentials); + ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY, targetLanguageCode); } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form index 6acf950f40..3e7e151bbf 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -16,13 +16,9 @@ - + - - - - @@ -31,6 +27,24 @@ + + + + + + + + + + + + + + + + + + @@ -44,9 +58,20 @@ - + + + + + + + + + + + + - + @@ -81,5 +106,46 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index b49c3645b8..5488ca9c62 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.texttranslation.translators; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.gson.JsonObject; import com.squareup.okhttp.MediaType; @@ -27,7 +28,13 @@ import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.io.IOException; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.logging.Level; import java.util.logging.Logger; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.apache.commons.lang3.StringUtils; /** * Settings panel for the GoogleTranslator @@ -36,13 +43,70 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(BingTranslatorSettingsPanel.class.getName()); private static final long serialVersionUID = 1L; + private static final String GET_TARGET_LANGUAGES_URL = "https://api.cognitive.microsofttranslator.com/languages?api-version=3.0&scope=translation"; + private String targetLanguageCode = ""; /** * Creates new form GoogleTranslatorSettingsPanel */ - public BingTranslatorSettingsPanel(String credentials) { + public BingTranslatorSettingsPanel(String credentials, String code) { initComponents(); credentialsField.setText(credentials); + credentialsField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + firePropertyChange("SettingChanged", true, false); + } + + @Override + public void removeUpdate(DocumentEvent e) { + firePropertyChange("SettingChanged", true, false); + } + + @Override + public void changedUpdate(DocumentEvent e) { + firePropertyChange("SettingChanged", true, false); + } + + }); + targetLanguageCode = code; + populateComboBox(); + selectLanguageByCode(targetLanguageCode); + } + + private void populateComboBox() { + Request get_request = new Request.Builder() + .url(GET_TARGET_LANGUAGES_URL).build(); + try { + Response response = new OkHttpClient().newCall(get_request).execute(); + JsonParser parser = new JsonParser(); + String responseBody = response.body().string(); + JsonElement elementBody = parser.parse(responseBody); + JsonObject asObject = elementBody.getAsJsonObject(); + JsonElement translationElement = asObject.get("translation"); + JsonObject responses = translationElement.getAsJsonObject(); + for (Entry entry : responses.entrySet()) { + targetLanguageComboBox.addItem(new LanguageWrapper(entry.getKey(), entry.getValue().getAsJsonObject().get("name").getAsString())); + } + } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException ex) { + logger.log(Level.WARNING, "Unable to get list of target languages or parse the result that was received", ex); + } + + } + + /** + * Given a language code select the corresponding language in the combo box + * if it is present + * + * @param code language code such as "en" for English + */ + private void selectLanguageByCode(String code) { + for (int i = 0; i < targetLanguageComboBox.getModel().getSize(); i++) { + if (targetLanguageComboBox.getModel().getElementAt(i).getLanguageCode().equals(code)) { + targetLanguageComboBox.setSelectedIndex(i); + break; + } + } } /** @@ -58,6 +122,11 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { credentialsField = new javax.swing.JTextField(); warningLabel = new javax.swing.JLabel(); testButton = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + targetLanguageComboBox = new javax.swing.JComboBox<>(); + jLabel2 = new javax.swing.JLabel(); + jSpinner1 = new javax.swing.JSpinner(); + jLabel3 = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(credentialsLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N @@ -71,6 +140,20 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { } }); + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.jLabel1.text")); // NOI18N + + targetLanguageComboBox.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + targetLanguageComboBoxSelected(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.jLabel2.text")); // NOI18N + + jSpinner1.setModel(new javax.swing.SpinnerNumberModel(5000, 5000, 500000, 5000)); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.jLabel3.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -78,16 +161,27 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addComponent(credentialsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 86, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(credentialsField, javax.swing.GroupLayout.DEFAULT_SIZE, 463, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(testButton) - .addGap(8, 8, 8)))) + .addGap(8, 8, 8)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, 77, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel3))) + .addGap(0, 0, Short.MAX_VALUE)))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -97,9 +191,18 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addComponent(credentialsLabel) .addComponent(credentialsField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(testButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(49, Short.MAX_VALUE)) + .addContainerGap()) ); }// //GEN-END:initComponents @@ -111,9 +214,22 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { } }//GEN-LAST:event_testButtonActionPerformed + private void targetLanguageComboBoxSelected(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_targetLanguageComboBoxSelected + String selectedCode = ((LanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguageCode(); + if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) { + targetLanguageCode = selectedCode; + firePropertyChange("SettingChanged", true, false); + } + }//GEN-LAST:event_targetLanguageComboBoxSelected + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField credentialsField; private javax.swing.JLabel credentialsLabel; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JSpinner jSpinner1; + private javax.swing.JComboBox targetLanguageComboBox; private javax.swing.JButton testButton; private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables @@ -129,35 +245,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { * cancellation, a connectivity problem or timeout. */ private boolean testTranslationSetup() { - String testString = "forense"; - MediaType mediaType = MediaType.parse("application/json"); - - JsonArray jsonArray = new JsonArray(); - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("Text", testString); - jsonArray.add(jsonObject); - String bodyString = jsonArray.toString(); - - RequestBody body = RequestBody.create(mediaType, - bodyString); - Request request = new Request.Builder() - .url(BingTranslator.getMicrosftTranlatorUrl()).post(body) - .addHeader("Ocp-Apim-Subscription-Key", credentialsField.getText()) - .addHeader("Content-type", "application/json").build(); - try { - Response response = new OkHttpClient().newCall(request).execute(); - JsonParser parser = new JsonParser(); - JsonArray responses = parser.parse(response.body().string()).getAsJsonArray(); - //As far as I know, there's always exactly one item in the array. - JsonObject response0 = responses.get(0).getAsJsonObject(); - JsonArray translations = response0.getAsJsonArray("translations"); - JsonObject translation0 = translations.get(0).getAsJsonObject(); - translation0.get("text").getAsString(); - return true; - } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) { - return false; - } - + return true; } /** @@ -168,4 +256,13 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { String getCredentials() { return credentialsField.getText(); } + + /** + * Get the currently selected target language code + * + * @return the target language code of the language selected in the combobox + */ + String getTargetLanguageCode() { + return targetLanguageCode; + } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties index 024ddfadcd..888cb51018 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties @@ -3,3 +3,6 @@ GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials: GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.testButton.text=Test +BingTranslatorSettingsPanel.jLabel1.text=Target Language: +BingTranslatorSettingsPanel.jLabel2.text=Translation Size: +BingTranslatorSettingsPanel.jLabel3.text=characters diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED index bfe84cb856..2fb4ea5ef4 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED @@ -12,4 +12,7 @@ GoogleTranslatorSettingsPanel.fileChooser.confirmButton=Select GoogleTranslatorSettingsPanel.json.description=JSON Files GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: -BingTranslatorSettingsPanel.jButton1.text=Test +BingTranslatorSettingsPanel.testButton.text=Test +BingTranslatorSettingsPanel.jLabel1.text=Target Language: +BingTranslatorSettingsPanel.jLabel2.text=Translation Size: +BingTranslatorSettingsPanel.jLabel3.text=characters diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form index 97a10b500f..1a78f115ac 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form @@ -98,7 +98,7 @@
- + @@ -119,4 +119,4 @@ - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java index fbb957413b..83ad5cf302 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java @@ -118,7 +118,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { targetLanguageComboBox.removeAllItems(); if (!listSupportedLanguages.isEmpty()) { listSupportedLanguages.forEach((lang) -> { - targetLanguageComboBox.addItem(new GoogleLanguageWrapper(lang)); + targetLanguageComboBox.addItem(new LanguageWrapper(lang)); }); selectLanguageByCode(targetLanguageCode); targetLanguageComboBox.addItemListener(listener); @@ -145,7 +145,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { */ private void selectLanguageByCode(String code) { for (int i = 0; i < targetLanguageComboBox.getModel().getSize(); i++) { - if (targetLanguageComboBox.getItemAt(i).getLanguage().getCode().equals(code)) { + if (targetLanguageComboBox.getItemAt(i).getLanguageCode().equals(code)) { targetLanguageComboBox.setSelectedIndex(i); return; } @@ -253,7 +253,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { private javax.swing.JButton browseButton; private javax.swing.JLabel credentialsLabel; private javax.swing.JTextField credentialsPathField; - private javax.swing.JComboBox targetLanguageComboBox; + private javax.swing.JComboBox targetLanguageComboBox; private javax.swing.JLabel targetLanguageLabel; private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables @@ -284,7 +284,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { @Override public void itemStateChanged(java.awt.event.ItemEvent evt) { - String selectedCode = ((GoogleLanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguage().getCode(); + String selectedCode = ((LanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguageCode(); if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) { targetLanguageCode = selectedCode; populateTargetLanguageComboBox(); diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java index c61ed1c948..3c3c048469 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java @@ -21,19 +21,33 @@ package org.sleuthkit.autopsy.texttranslation.translators; import com.google.cloud.translate.Language; /** - * Wrapper for the Language class + * Wrapper for Language definitions used by translators */ -class GoogleLanguageWrapper { +class LanguageWrapper { - private final Language language; + private final String languageCode; + private final String languageDisplayName; /** - * Create a new GoogleLanguageWrapper + * Create a new LanguageWrapper to wrap the google language object * * @param lang the Language object to wrap */ - GoogleLanguageWrapper(Language lang) { - language = lang; + LanguageWrapper(Language language) { + languageCode = language.getCode(); + languageDisplayName = language.getName(); + } + + /** + * Create a new LanguageWrapper to wrap json elements that identify a + * language for microsofts translation service + * + * @param code the code which uniquely identifies a language + * @param name the name of the language + */ + LanguageWrapper(String code, String name) { + languageCode = code; + languageDisplayName = name; } /** @@ -41,14 +55,14 @@ class GoogleLanguageWrapper { * * @return the wrapped Language */ - Language getLanguage() { - return language; + String getLanguageCode() { + return languageCode; } @Override public String toString() { //toString overridden so that the jComboBox in the GoogleTranslatorSettingsPanel will display the name of the language - return language.getName(); + return languageDisplayName; } } From 90a002efee982071e665ac8156f44f1adaa088c7 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 30 May 2019 12:21:05 -0400 Subject: [PATCH 013/453] 5061 add test button to Bing translator settings panel --- .../translators/BingTranslator.java | 6 +- .../BingTranslatorSettingsPanel.form | 93 ++++++++++---- .../BingTranslatorSettingsPanel.java | 117 +++++++++++++----- .../translators/Bundle.properties | 9 +- .../translators/Bundle.properties-MERGED | 9 +- .../translators/LanguageWrapper.java | 2 +- 6 files changed, 175 insertions(+), 61 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index 4aaba09ee5..1d27d8ce33 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -60,8 +60,8 @@ public class BingTranslator implements TextTranslator{ settingsPanel = new BingTranslatorSettingsPanel(settings.getCredentials(), settings.getTargetLanguageCode()); } - private String getTranlatorUrl(){ - return BASE_URL + settings.getTargetLanguageCode(); + static String getTranlatorUrl(String languageCode){ + return BASE_URL + languageCode; } /** @@ -84,7 +84,7 @@ public class BingTranslator implements TextTranslator{ RequestBody body = RequestBody.create(mediaType, bodyString); Request request = new Request.Builder() - .url(getTranlatorUrl()).post(body) + .url(getTranlatorUrl(settings.getTargetLanguageCode())).post(body) .addHeader("Ocp-Apim-Subscription-Key", settings.getCredentials()) .addHeader("Content-type", "application/json").build(); Response response = CLIENT.newCall(request).execute(); diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form index 3e7e151bbf..944cc3d124 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -23,24 +23,38 @@ - - - + - + - + - + + + + - - - + + + + + + + + + + + + + + + + @@ -56,20 +70,27 @@ - - + - - - + + + - + + + + + + + + + @@ -106,10 +127,10 @@ - + - + @@ -126,24 +147,52 @@ - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index 5488ca9c62..4282ecbc63 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -44,6 +44,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(BingTranslatorSettingsPanel.class.getName()); private static final long serialVersionUID = 1L; private static final String GET_TARGET_LANGUAGES_URL = "https://api.cognitive.microsofttranslator.com/languages?api-version=3.0&scope=translation"; + private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of translation successful private String targetLanguageCode = ""; /** @@ -51,6 +52,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { */ public BingTranslatorSettingsPanel(String credentials, String code) { initComponents(); + credentialsField.setText(credentials); credentialsField.getDocument().addDocumentListener(new DocumentListener() { @Override @@ -122,11 +124,15 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { credentialsField = new javax.swing.JTextField(); warningLabel = new javax.swing.JLabel(); testButton = new javax.swing.JButton(); - jLabel1 = new javax.swing.JLabel(); + targetLanguageLabel = new javax.swing.JLabel(); targetLanguageComboBox = new javax.swing.JComboBox<>(); - jLabel2 = new javax.swing.JLabel(); - jSpinner1 = new javax.swing.JSpinner(); - jLabel3 = new javax.swing.JLabel(); + translationSizeLabel = new javax.swing.JLabel(); + translationSizeSpinner = new javax.swing.JSpinner(); + unitsLabel = new javax.swing.JLabel(); + testUntranslatedTextField = new javax.swing.JTextField(); + untranslatedLabel = new javax.swing.JLabel(); + resultLabel = new javax.swing.JLabel(); + testResultValueLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(credentialsLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N @@ -140,7 +146,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.jLabel1.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(targetLanguageLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.targetLanguageLabel.text")); // NOI18N targetLanguageComboBox.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { @@ -148,11 +154,19 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.jLabel2.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(translationSizeLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.translationSizeLabel.text")); // NOI18N - jSpinner1.setModel(new javax.swing.SpinnerNumberModel(5000, 5000, 500000, 5000)); + translationSizeSpinner.setModel(new javax.swing.SpinnerNumberModel(5000, 5000, 500000, 5000)); - org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.jLabel3.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(unitsLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.unitsLabel.text")); // NOI18N + + testUntranslatedTextField.setText(DEFUALT_TEST_STRING); + + org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(resultLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.resultLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -165,22 +179,32 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addComponent(credentialsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 86, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(credentialsField, javax.swing.GroupLayout.DEFAULT_SIZE, 463, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(testButton) - .addGap(8, 8, 8)) + .addGap(67, 67, 67)) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(layout.createSequentialGroup() - .addComponent(jLabel1) + .addComponent(targetLanguageLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() - .addComponent(jLabel2) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(translationSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(testButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, 77, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel3))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(translationSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 77, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(unitsLabel)) + .addGroup(layout.createSequentialGroup() + .addComponent(untranslatedLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(resultLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))) .addGap(0, 0, Short.MAX_VALUE)))) ); layout.setVerticalGroup( @@ -189,18 +213,24 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(credentialsLabel) - .addComponent(credentialsField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(testButton)) + .addComponent(credentialsField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) + .addComponent(targetLanguageLabel) .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel2) - .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel3)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(translationSizeLabel) + .addComponent(translationSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(unitsLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(testButton) + .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(untranslatedLabel) + .addComponent(resultLabel) + .addComponent(testResultValueLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); @@ -225,12 +255,16 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField credentialsField; private javax.swing.JLabel credentialsLabel; - private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel3; - private javax.swing.JSpinner jSpinner1; + private javax.swing.JLabel resultLabel; private javax.swing.JComboBox targetLanguageComboBox; + private javax.swing.JLabel targetLanguageLabel; private javax.swing.JButton testButton; + private javax.swing.JLabel testResultValueLabel; + private javax.swing.JTextField testUntranslatedTextField; + private javax.swing.JLabel translationSizeLabel; + private javax.swing.JSpinner translationSizeSpinner; + private javax.swing.JLabel unitsLabel; + private javax.swing.JLabel untranslatedLabel; private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables /** @@ -245,7 +279,32 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { * cancellation, a connectivity problem or timeout. */ private boolean testTranslationSetup() { - return true; + MediaType mediaType = MediaType.parse("application/json"); + JsonArray jsonArray = new JsonArray(); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("Text", testUntranslatedTextField.getText()); + jsonArray.add(jsonObject); + String bodyString = jsonArray.toString(); + + RequestBody body = RequestBody.create(mediaType, + bodyString); + Request request = new Request.Builder() + .url(BingTranslator.getTranlatorUrl(targetLanguageCode)).post(body) + .addHeader("Ocp-Apim-Subscription-Key", credentialsField.getText()) + .addHeader("Content-type", "application/json").build(); + try { + Response response = new OkHttpClient().newCall(request).execute(); + JsonParser parser = new JsonParser(); + JsonArray responses = parser.parse(response.body().string()).getAsJsonArray(); + //As far as I know, there's always exactly one item in the array. + JsonObject response0 = responses.get(0).getAsJsonObject(); + JsonArray translations = response0.getAsJsonArray("translations"); + JsonObject translation0 = translations.get(0).getAsJsonObject(); + testResultValueLabel.setText(translation0.get("text").getAsString()); + return true; + } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) { + return false; + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties index 888cb51018..adf27cddfa 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties @@ -3,6 +3,9 @@ GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials: GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.testButton.text=Test -BingTranslatorSettingsPanel.jLabel1.text=Target Language: -BingTranslatorSettingsPanel.jLabel2.text=Translation Size: -BingTranslatorSettingsPanel.jLabel3.text=characters +BingTranslatorSettingsPanel.testResultValueLabel.text= +BingTranslatorSettingsPanel.resultLabel.text=Result: +BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: +BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: +BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: +BingTranslatorSettingsPanel.unitsLabel.text=characters diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED index 2fb4ea5ef4..01c6123934 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED @@ -13,6 +13,9 @@ GoogleTranslatorSettingsPanel.json.description=JSON Files GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.testButton.text=Test -BingTranslatorSettingsPanel.jLabel1.text=Target Language: -BingTranslatorSettingsPanel.jLabel2.text=Translation Size: -BingTranslatorSettingsPanel.jLabel3.text=characters +BingTranslatorSettingsPanel.testResultValueLabel.text= +BingTranslatorSettingsPanel.resultLabel.text=Result: +BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: +BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: +BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: +BingTranslatorSettingsPanel.unitsLabel.text=characters diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java index 3c3c048469..2fa308291b 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java @@ -61,7 +61,7 @@ class LanguageWrapper { @Override public String toString() { - //toString overridden so that the jComboBox in the GoogleTranslatorSettingsPanel will display the name of the language + //toString overridden so that the jComboBox in the TranslatorSettingsPanels will display the name of the language return languageDisplayName; } From d2bf1777f293b26d1fbde974274da0d1a7889e56 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 30 May 2019 12:35:24 -0400 Subject: [PATCH 014/453] 5061 reword credentials to be authentication key for bing translator --- .../translators/BingTranslator.java | 15 ++-- .../translators/BingTranslatorSettings.java | 32 +++++---- .../BingTranslatorSettingsPanel.form | 37 ++++++---- .../BingTranslatorSettingsPanel.java | 68 ++++++++++--------- .../translators/Bundle.properties | 3 +- 5 files changed, 86 insertions(+), 69 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index 1d27d8ce33..db17236edc 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -46,18 +46,15 @@ public class BingTranslator implements TextTranslator{ private static final String BASE_URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to="; private final BingTranslatorSettingsPanel settingsPanel; private final BingTranslatorSettings settings = new BingTranslatorSettings(); - - // This sends messages to Microsoft. - private final OkHttpClient CLIENT = new OkHttpClient(); - + private final OkHttpClient CLIENT = new OkHttpClient(); //We might want to make this a configurable setting for anyone who has a //paid account that's willing to pay for long documents. private final int MAX_STRING_LENGTH = 5000; public BingTranslator(){ - settingsPanel = new BingTranslatorSettingsPanel(settings.getCredentials(), settings.getTargetLanguageCode()); + settingsPanel = new BingTranslatorSettingsPanel(settings.getAuthenticationKey(), settings.getTargetLanguageCode()); } static String getTranlatorUrl(String languageCode){ @@ -85,7 +82,7 @@ public class BingTranslator implements TextTranslator{ bodyString); Request request = new Request.Builder() .url(getTranlatorUrl(settings.getTargetLanguageCode())).post(body) - .addHeader("Ocp-Apim-Subscription-Key", settings.getCredentials()) + .addHeader("Ocp-Apim-Subscription-Key", settings.getAuthenticationKey()) .addHeader("Content-type", "application/json").build(); Response response = CLIENT.newCall(request).execute(); return response.body().string(); @@ -93,8 +90,8 @@ public class BingTranslator implements TextTranslator{ @Override public String translate(String string) throws TranslationException { - if (settings.getCredentials() == null || settings.getCredentials().isEmpty()) { - throw new TranslationException("Bing Translator has not been configured, credentials need to be specified"); + if (settings.getAuthenticationKey() == null || settings.getAuthenticationKey().isEmpty()) { + throw new TranslationException("Bing Translator has not been configured, authentication key needs to be specified"); } String toTranslate = string.trim(); //Translates some text into English, without specifying the source langauge. @@ -132,7 +129,7 @@ public class BingTranslator implements TextTranslator{ @Override public void saveSettings() { - settings.setCredentials(settingsPanel.getCredentials()); + settings.setAuthenticationKey(settingsPanel.getAuthenticationKey()); settings.setTargetLanguageCode(settingsPanel.getTargetLanguageCode()); } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java index ca68adb77c..039c24c480 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java @@ -26,12 +26,12 @@ import org.sleuthkit.autopsy.coreutils.ModuleSettings; */ public final class BingTranslatorSettings { - private static final String CREDENTIALS_KEY = "Credentials"; + private static final String AUTHENTICATION_KEY = "Credentials"; private static final String BING_TRANSLATE_NAME = "BingTranslate"; - private static final String DEFAULT_CREDENTIALS = ""; + private static final String DEFAULT_AUTHENTICATION = ""; private static final String DEFAULT_TARGET_LANGUAGE = "en"; private static final String TARGET_LANGUAGE_CODE_KEY = "TargetLanguageCode"; - private String credentials; + private String authenticationKey; private String targetLanguageCode; /** @@ -42,21 +42,23 @@ public final class BingTranslatorSettings { } /** - * Get the path to the JSON credentials file + * Get the Authentication key to be used for the Microsoft translation + * service * - * @return the path to the credentials file + * @return the Authentication key for the service */ - String getCredentials() { - return credentials; + String getAuthenticationKey() { + return authenticationKey; } /** - * Set the path to the JSON credentials file + * Set the Authentication key to be used for the Microsoft translation + * service * - * @param path the path to the credentials file + * @param authKey the Authentication key for the service */ - void setCredentials(String creds) { - credentials = creds; + void setAuthenticationKey(String authKey) { + authenticationKey = authKey; } /** @@ -66,10 +68,10 @@ public final class BingTranslatorSettings { if (!ModuleSettings.configExists(BING_TRANSLATE_NAME)) { ModuleSettings.makeConfigFile(BING_TRANSLATE_NAME); } - if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, CREDENTIALS_KEY)) { - credentials = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, CREDENTIALS_KEY); + if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, AUTHENTICATION_KEY)) { + authenticationKey = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, AUTHENTICATION_KEY); } else { - credentials = DEFAULT_CREDENTIALS; + authenticationKey = DEFAULT_AUTHENTICATION; } if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) { targetLanguageCode = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY); @@ -105,7 +107,7 @@ public final class BingTranslatorSettings { * Save the setting from memory to their location on disk */ void saveSettings() { - ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, CREDENTIALS_KEY, credentials); + ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, AUTHENTICATION_KEY, authenticationKey); ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY, targetLanguageCode); } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form index 944cc3d124..5b275b52e3 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -19,31 +19,37 @@ - - - - - - - + + + + + + + + + + + + - + + @@ -57,7 +63,7 @@ - + @@ -68,8 +74,8 @@ - - + + @@ -98,14 +104,19 @@ - + - + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index 4282ecbc63..b95887cea1 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -28,7 +28,6 @@ import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.io.IOException; -import java.util.Iterator; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; @@ -44,17 +43,17 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(BingTranslatorSettingsPanel.class.getName()); private static final long serialVersionUID = 1L; private static final String GET_TARGET_LANGUAGES_URL = "https://api.cognitive.microsofttranslator.com/languages?api-version=3.0&scope=translation"; - private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of translation successful + private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of "successful translation" private String targetLanguageCode = ""; /** * Creates new form GoogleTranslatorSettingsPanel */ - public BingTranslatorSettingsPanel(String credentials, String code) { + public BingTranslatorSettingsPanel(String authenticationKey, String code) { initComponents(); - - credentialsField.setText(credentials); - credentialsField.getDocument().addDocumentListener(new DocumentListener() { + + authenticationKeyField.setText(authenticationKey); + authenticationKeyField.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { firePropertyChange("SettingChanged", true, false); @@ -69,7 +68,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { public void changedUpdate(DocumentEvent e) { firePropertyChange("SettingChanged", true, false); } - + }); targetLanguageCode = code; populateComboBox(); @@ -87,9 +86,9 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { JsonObject asObject = elementBody.getAsJsonObject(); JsonElement translationElement = asObject.get("translation"); JsonObject responses = translationElement.getAsJsonObject(); - for (Entry entry : responses.entrySet()) { + responses.entrySet().forEach((entry) -> { targetLanguageComboBox.addItem(new LanguageWrapper(entry.getKey(), entry.getValue().getAsJsonObject().get("name").getAsString())); - } + }); } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException ex) { logger.log(Level.WARNING, "Unable to get list of target languages or parse the result that was received", ex); } @@ -120,8 +119,8 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - credentialsLabel = new javax.swing.JLabel(); - credentialsField = new javax.swing.JTextField(); + authenticationKeyLabel = new javax.swing.JLabel(); + authenticationKeyField = new javax.swing.JTextField(); warningLabel = new javax.swing.JLabel(); testButton = new javax.swing.JButton(); targetLanguageLabel = new javax.swing.JLabel(); @@ -134,7 +133,9 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { resultLabel = new javax.swing.JLabel(); testResultValueLabel = new javax.swing.JLabel(); - org.openide.awt.Mnemonics.setLocalizedText(credentialsLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(authenticationKeyLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N + + authenticationKeyField.setToolTipText(org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.authenticationKeyField.toolTipText")); // NOI18N warningLabel.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.warningLabel.text")); // NOI18N @@ -175,28 +176,32 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(credentialsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 86, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(credentialsField, javax.swing.GroupLayout.DEFAULT_SIZE, 463, Short.MAX_VALUE) - .addGap(67, 67, 67)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(layout.createSequentialGroup() .addComponent(targetLanguageLabel) + .addGap(18, 18, 18) + .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(authenticationKeyLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, 486, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 20, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(translationSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(testButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGap(25, 25, 25) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(translationSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 77, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(unitsLabel)) + .addComponent(unitsLabel) + .addGap(0, 0, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addComponent(untranslatedLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -205,15 +210,15 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addComponent(resultLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))) - .addGap(0, 0, Short.MAX_VALUE)))) + .addContainerGap()))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(credentialsLabel) - .addComponent(credentialsField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(authenticationKeyLabel) + .addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(targetLanguageLabel) @@ -240,7 +245,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { if (testTranslationSetup()) { warningLabel.setText(""); } else { - warningLabel.setText("Invalid translation credentials"); + warningLabel.setText("Invalid translation authentication key"); } }//GEN-LAST:event_testButtonActionPerformed @@ -253,8 +258,8 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { }//GEN-LAST:event_targetLanguageComboBoxSelected // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTextField credentialsField; - private javax.swing.JLabel credentialsLabel; + private javax.swing.JTextField authenticationKeyField; + private javax.swing.JLabel authenticationKeyLabel; private javax.swing.JLabel resultLabel; private javax.swing.JComboBox targetLanguageComboBox; private javax.swing.JLabel targetLanguageLabel; @@ -290,7 +295,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { bodyString); Request request = new Request.Builder() .url(BingTranslator.getTranlatorUrl(targetLanguageCode)).post(body) - .addHeader("Ocp-Apim-Subscription-Key", credentialsField.getText()) + .addHeader("Ocp-Apim-Subscription-Key", authenticationKeyField.getText()) .addHeader("Content-type", "application/json").build(); try { Response response = new OkHttpClient().newCall(request).execute(); @@ -308,12 +313,13 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { } /** - * Get the currently set path to the JSON credentials file + * Get the currently set authentication key to be used for the Microsoft + * translation service * - * @return the path to the credentials file specified in the textarea + * @return the authentication key specified in the textarea */ - String getCredentials() { - return credentialsField.getText(); + String getAuthenticationKey() { + return authenticationKeyField.getText(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties index adf27cddfa..1d6a9318be 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties @@ -1,5 +1,5 @@ GoogleTranslatorSettingsPanel.browseButton.text=Browse -GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials: +GoogleTranslatorSettingsPanel.credentialsLabel.text=Authentication key: GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.testButton.text=Test @@ -9,3 +9,4 @@ BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.unitsLabel.text=characters +BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the From fd399e4cfb7c080e358ea4dfd6e47ab3fb87f2d3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 30 May 2019 13:04:32 -0400 Subject: [PATCH 015/453] 5061 clean up loading of settings --- .../texttranslation/translators/BingTranslatorSettings.java | 6 ++++-- .../texttranslation/translators/Bundle.properties-MERGED | 3 ++- .../translators/GoogleTranslatorSettings.java | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java index 039c24c480..dcd54fd6f9 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java @@ -70,12 +70,14 @@ public final class BingTranslatorSettings { } if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, AUTHENTICATION_KEY)) { authenticationKey = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, AUTHENTICATION_KEY); - } else { + } + if (authenticationKey == null || StringUtils.isBlank(authenticationKey)) { authenticationKey = DEFAULT_AUTHENTICATION; } if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) { targetLanguageCode = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY); - } else { + } + if (targetLanguageCode == null || StringUtils.isBlank(targetLanguageCode)) { targetLanguageCode = DEFAULT_TARGET_LANGUAGE; } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED index 01c6123934..89f7d8b02a 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED @@ -1,7 +1,7 @@ BingTranslator.name.text=Bing Translator GoogleTranslator.name.text=Google Translate GoogleTranslatorSettingsPanel.browseButton.text=Browse -GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials: +GoogleTranslatorSettingsPanel.credentialsLabel.text=Authentication key: GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file. GoogleTranslatorSettingsPanel.errorMessage.noFileSelected=A JSON file must be selected to provide your credentials for Google Translate. GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file. @@ -19,3 +19,4 @@ BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.unitsLabel.text=characters +BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java index 55771b8c7a..34ebfd67d4 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java @@ -92,12 +92,14 @@ public final class GoogleTranslatorSettings { } if (ModuleSettings.settingExists(GOOGLE_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) { targetLanguageCode = ModuleSettings.getConfigSetting(GOOGLE_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY); - } else { + } + if (targetLanguageCode == null || StringUtils.isBlank(targetLanguageCode)) { targetLanguageCode = DEFAULT_TARGET_LANGUAGE; } if (ModuleSettings.settingExists(GOOGLE_TRANSLATE_NAME, CREDENTIAL_PATH_KEY)) { credentialPath = ModuleSettings.getConfigSetting(GOOGLE_TRANSLATE_NAME, CREDENTIAL_PATH_KEY); - } else { + } + if (credentialPath == null) { credentialPath = DEFAULT_CREDENTIAL_PATH; } } From e6670f34bab10ab51a7bbc82d53d35668d0c54fe Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 30 May 2019 13:46:15 -0400 Subject: [PATCH 016/453] 5061 test button added to google translate options panel --- .../translators/BingTranslator.java | 75 ++++++-------- .../BingTranslatorSettingsPanel.java | 2 +- .../translators/Bundle.properties | 6 +- .../translators/Bundle.properties-MERGED | 6 +- .../GoogleTranslatorSettingsPanel.form | 71 ++++++++++++-- .../GoogleTranslatorSettingsPanel.java | 97 +++++++++++++++---- 6 files changed, 184 insertions(+), 73 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index db17236edc..78f1cf9afd 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -16,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.texttranslation.translators; import com.google.gson.JsonArray; @@ -35,11 +34,12 @@ import org.sleuthkit.autopsy.texttranslation.TextTranslator; import org.sleuthkit.autopsy.texttranslation.TranslationException; /** - * Translates text by making HTTP requests to Bing Translator. - * This requires a valid subscription key for a Microsoft Azure account. + * Translates text by making HTTP requests to Bing Translator. This requires a + * valid subscription key for a Microsoft Azure account. */ @ServiceProvider(service = TextTranslator.class) -public class BingTranslator implements TextTranslator{ +public class BingTranslator implements TextTranslator { + //In the String below, "en" is the target language. You can include multiple target //languages separated by commas. A full list of supported languages is here: //https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support @@ -47,43 +47,45 @@ public class BingTranslator implements TextTranslator{ private final BingTranslatorSettingsPanel settingsPanel; private final BingTranslatorSettings settings = new BingTranslatorSettings(); // This sends messages to Microsoft. - private final OkHttpClient CLIENT = new OkHttpClient(); + private final OkHttpClient CLIENT = new OkHttpClient(); //We might want to make this a configurable setting for anyone who has a //paid account that's willing to pay for long documents. private final int MAX_STRING_LENGTH = 5000; - - - public BingTranslator(){ + + public BingTranslator() { settingsPanel = new BingTranslatorSettingsPanel(settings.getAuthenticationKey(), settings.getTargetLanguageCode()); } - - static String getTranlatorUrl(String languageCode){ + + static String getTranlatorUrl(String languageCode) { return BASE_URL + languageCode; } - + /** * Converts an input test to the JSON format required by Bing Translator, * posts it to Microsoft, and returns the JSON text response. - * + * * @param string The input text to be translated. + * * @return The translation response as a JSON string - * @throws IOException if the request could not be executed due to cancellation, a connectivity problem or timeout. + * + * @throws IOException if the request could not be executed due to + * cancellation, a connectivity problem or timeout. */ public String postTranslationRequest(String string) throws IOException { MediaType mediaType = MediaType.parse("application/json"); - + JsonArray jsonArray = new JsonArray(); JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("Text", string); jsonArray.add(jsonObject); String bodyString = jsonArray.toString(); - + RequestBody body = RequestBody.create(mediaType, - bodyString); + bodyString); Request request = new Request.Builder() - .url(getTranlatorUrl(settings.getTargetLanguageCode())).post(body) - .addHeader("Ocp-Apim-Subscription-Key", settings.getAuthenticationKey()) - .addHeader("Content-type", "application/json").build(); + .url(getTranlatorUrl(settings.getTargetLanguageCode())).post(body) + .addHeader("Ocp-Apim-Subscription-Key", settings.getAuthenticationKey()) + .addHeader("Content-type", "application/json").build(); Response response = CLIENT.newCall(request).execute(); return response.body().string(); } @@ -95,27 +97,25 @@ public class BingTranslator implements TextTranslator{ } String toTranslate = string.trim(); //Translates some text into English, without specifying the source langauge. - + // HTML files were producing lots of white space at the end - //Google Translate required us to replace (\r\n|\n) with
//but Bing Translator doesn not have that requirement. - //The free account has a maximum file size. If you have a paid account, //you probably still want to limit file size to prevent accidentally //translating very large documents. if (toTranslate.length() > MAX_STRING_LENGTH) { toTranslate = toTranslate.substring(0, MAX_STRING_LENGTH); } - + try { String response = postTranslationRequest(toTranslate); return parseJSONResponse(response); } catch (Throwable e) { - throw new TranslationException(e.getMessage()); + throw new TranslationException(e.getMessage()); } } - + @Messages({"BingTranslator.name.text=Bing Translator"}) @Override public String getName() { @@ -131,26 +131,15 @@ public class BingTranslator implements TextTranslator{ public void saveSettings() { settings.setAuthenticationKey(settingsPanel.getAuthenticationKey()); settings.setTargetLanguageCode(settingsPanel.getTargetLanguageCode()); + settings.saveSettings(); } private String parseJSONResponse(String json_text) throws TranslationException { - /* Here is an example of the text we get from Bing when input is "gato", - the Spanish word for cat: - [ - { - "detectedLanguage": { - "language": "es", - "score": 1.0 - }, - "translations": [ - { - "text": "cat", - "to": "en" - } - ] - } - ] - */ + /* + * Here is an example of the text we get from Bing when input is "gato", + * the Spanish word for cat: [ { "detectedLanguage": { "language": "es", + * "score": 1.0 }, "translations": [ { "text": "cat", "to": "en" } ] } ] + */ JsonParser parser = new JsonParser(); try { JsonArray responses = parser.parse(json_text).getAsJsonArray(); @@ -164,4 +153,4 @@ public class BingTranslator implements TextTranslator{ throw new TranslationException("JSON text does not match Bing Translator scheme: " + e); } } -} \ No newline at end of file +} diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index b95887cea1..054f65a2f5 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -51,7 +51,6 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { */ public BingTranslatorSettingsPanel(String authenticationKey, String code) { initComponents(); - authenticationKeyField.setText(authenticationKey); authenticationKeyField.getDocument().addDocumentListener(new DocumentListener() { @Override @@ -284,6 +283,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { * cancellation, a connectivity problem or timeout. */ private boolean testTranslationSetup() { + testResultValueLabel.setText(""); MediaType mediaType = MediaType.parse("application/json"); JsonArray jsonArray = new JsonArray(); JsonObject jsonObject = new JsonObject(); diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties index 1d6a9318be..8eebeeab58 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties @@ -1,5 +1,5 @@ GoogleTranslatorSettingsPanel.browseButton.text=Browse -GoogleTranslatorSettingsPanel.credentialsLabel.text=Authentication key: +GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials Path: GoogleTranslatorSettingsPanel.warningLabel.text= GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.testButton.text=Test @@ -10,3 +10,7 @@ BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.unitsLabel.text=characters BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the +GoogleTranslatorSettingsPanel.testButton.text=Test +GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: +GoogleTranslatorSettingsPanel.resultLabel.text=Result: +GoogleTranslatorSettingsPanel.testResultValueLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED index 89f7d8b02a..fec94d3419 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED @@ -1,7 +1,7 @@ BingTranslator.name.text=Bing Translator GoogleTranslator.name.text=Google Translate GoogleTranslatorSettingsPanel.browseButton.text=Browse -GoogleTranslatorSettingsPanel.credentialsLabel.text=Authentication key: +GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials Path: GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file. GoogleTranslatorSettingsPanel.errorMessage.noFileSelected=A JSON file must be selected to provide your credentials for Google Translate. GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file. @@ -20,3 +20,7 @@ BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: BingTranslatorSettingsPanel.unitsLabel.text=characters BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the +GoogleTranslatorSettingsPanel.testButton.text=Test +GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: +GoogleTranslatorSettingsPanel.resultLabel.text=Result: +GoogleTranslatorSettingsPanel.testResultValueLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form index 1a78f115ac..7d728fd011 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form @@ -16,13 +16,9 @@ - + - - - - @@ -31,7 +27,7 @@ - + @@ -42,6 +38,18 @@ + + + + + + + + + + + + @@ -60,9 +68,16 @@ - + + + + + + + + + -
@@ -118,5 +133,43 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java index 83ad5cf302..8bccc01485 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java @@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.texttranslation.translators; import com.google.auth.Credentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.translate.Language; +import com.google.cloud.translate.Translate; import com.google.cloud.translate.TranslateOptions; +import com.google.cloud.translate.Translation; import java.awt.event.ItemListener; import java.io.File; import java.io.FileInputStream; @@ -44,6 +46,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(GoogleTranslatorSettingsPanel.class.getName()); private static final String JSON_EXTENSION = "json"; + private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of "successful translation" private static final long serialVersionUID = 1L; private final ItemListener listener = new ComboBoxSelectionListener(); private String targetLanguageCode = ""; @@ -60,16 +63,15 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { /** * Private method to make a temporary translation service given the current - * settings and use it to retrieve the available target languages for - * population of combobox with target language with unsaved settings. + * settings with unsaved settings. * - * @return A list of Languages + * @return A Translate object which is the translation service */ @Messages({"GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file.", "GoogleTranslatorSettingsPanel.errorMessage.unableToReadCredentials=Unable to read credentials from credentials file, please set the location to be a valid JSON credentials file.", "GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file.", "GoogleTranslatorSettingsPanel.errorMessage.unknownFailureGetting=Failure getting list of supported languages with current credentials file.",}) - private List getListOfTargetLanguages() { + private Translate getTemporaryTranslationService() { //This method also has the side effect of more or less validating the JSON file which was selected as it is necessary to get the list of target languages try { InputStream credentialStream; @@ -77,31 +79,31 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { credentialStream = new FileInputStream(credentialsPathField.getText()); } catch (FileNotFoundException ignored) { warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_fileNotFound()); - return new ArrayList<>(); + return null; } Credentials creds; try { creds = ServiceAccountCredentials.fromStream(credentialStream); } catch (IOException ignored) { warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unableToMakeCredentials()); - return new ArrayList<>(); + return null; } if (creds == null) { warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unableToReadCredentials()); logger.log(Level.WARNING, "Credentials were not successfully made, no translations will be available from the GoogleTranslator"); - return new ArrayList<>(); + return null; } else { TranslateOptions.Builder builder = TranslateOptions.newBuilder(); builder.setCredentials(creds); builder.setTargetLanguage(targetLanguageCode); //localize the list to the currently selected target language warningLabel.setText(""); //clear any previous warning text - return builder.build().getService().listSupportedLanguages(); + return builder.build().getService(); } } catch (Throwable throwable) { //Catching throwables because some of this Google Translate code throws throwables warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unknownFailureGetting()); logger.log(Level.WARNING, "Throwable caught while getting list of supported languages", throwable); - return new ArrayList<>(); + return null; } } @@ -114,7 +116,13 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { targetLanguageComboBox.removeItemListener(listener); try { if (!StringUtils.isBlank(credentialsPathField.getText())) { - List listSupportedLanguages = getListOfTargetLanguages(); + List listSupportedLanguages; + Translate tempService = getTemporaryTranslationService(); + if (tempService != null) { + listSupportedLanguages = tempService.listSupportedLanguages(); + } else { + listSupportedLanguages = new ArrayList<>(); + } targetLanguageComboBox.removeAllItems(); if (!listSupportedLanguages.isEmpty()) { listSupportedLanguages.forEach((lang) -> { @@ -167,6 +175,11 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { targetLanguageComboBox = new javax.swing.JComboBox<>(); targetLanguageLabel = new javax.swing.JLabel(); warningLabel = new javax.swing.JLabel(); + testResultValueLabel = new javax.swing.JLabel(); + resultLabel = new javax.swing.JLabel(); + untranslatedLabel = new javax.swing.JLabel(); + testUntranslatedTextField = new javax.swing.JTextField(); + testButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(credentialsLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N @@ -186,6 +199,21 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { warningLabel.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.warningLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(resultLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.resultLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N + + testUntranslatedTextField.setText(DEFUALT_TEST_STRING); + + org.openide.awt.Mnemonics.setLocalizedText(testButton, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testButton.text")); // NOI18N + testButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + testButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -193,9 +221,6 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(credentialsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) @@ -203,13 +228,24 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(credentialsPathField, javax.swing.GroupLayout.DEFAULT_SIZE, 443, Short.MAX_VALUE) + .addComponent(credentialsPathField, javax.swing.GroupLayout.DEFAULT_SIZE, 451, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(browseButton) .addGap(14, 14, 14)) .addGroup(layout.createSequentialGroup() .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 317, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)))))) + .addGap(0, 0, Short.MAX_VALUE)))) + .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(testButton, javax.swing.GroupLayout.PREFERRED_SIZE, 83, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(untranslatedLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(resultLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -223,9 +259,15 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(targetLanguageLabel) .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(23, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 15, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(testButton) + .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(untranslatedLabel) + .addComponent(resultLabel) + .addComponent(testResultValueLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)) ); }// //GEN-END:initComponents @@ -249,12 +291,31 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { } }//GEN-LAST:event_browseButtonActionPerformed + private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed + testResultValueLabel.setText(""); + Translate tempTranslate = getTemporaryTranslationService(); + if (tempTranslate != null) { + try { + Translation translation = tempTranslate.translate(testUntranslatedTextField.getText()); + testResultValueLabel.setText(translation.getTranslatedText()); + warningLabel.setText(""); + } catch (Exception ex) { + warningLabel.setText("Invalid translation credentials path"); + } + } + }//GEN-LAST:event_testButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton browseButton; private javax.swing.JLabel credentialsLabel; private javax.swing.JTextField credentialsPathField; + private javax.swing.JLabel resultLabel; private javax.swing.JComboBox targetLanguageComboBox; private javax.swing.JLabel targetLanguageLabel; + private javax.swing.JButton testButton; + private javax.swing.JLabel testResultValueLabel; + private javax.swing.JTextField testUntranslatedTextField; + private javax.swing.JLabel untranslatedLabel; private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables From 670ef2cfb1d041c1654e84be1234bdae83893171 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 30 May 2019 16:05:30 -0400 Subject: [PATCH 017/453] 5061 fix label on bing translate options panel which was wrong --- .../BingTranslatorSettingsPanel.form | 18 +++++++++--------- .../BingTranslatorSettingsPanel.java | 11 +++++------ .../translators/Bundle.properties | 1 + .../translators/Bundle.properties-MERGED | 1 + 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form index 5b275b52e3..64227561b3 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -38,7 +38,7 @@ - + @@ -74,8 +74,8 @@ - + @@ -104,13 +104,6 @@ - - - - - - - @@ -207,5 +200,12 @@ + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index 054f65a2f5..e477fbf662 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -28,7 +28,6 @@ import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.io.IOException; -import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.DocumentEvent; @@ -118,7 +117,6 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - authenticationKeyLabel = new javax.swing.JLabel(); authenticationKeyField = new javax.swing.JTextField(); warningLabel = new javax.swing.JLabel(); testButton = new javax.swing.JButton(); @@ -131,8 +129,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { untranslatedLabel = new javax.swing.JLabel(); resultLabel = new javax.swing.JLabel(); testResultValueLabel = new javax.swing.JLabel(); - - org.openide.awt.Mnemonics.setLocalizedText(authenticationKeyLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N + authenticationKeyLabel = new javax.swing.JLabel(); authenticationKeyField.setToolTipText(org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.authenticationKeyField.toolTipText")); // NOI18N @@ -168,6 +165,8 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(authenticationKeyLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.authenticationKeyLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -216,8 +215,8 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(authenticationKeyLabel) - .addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(authenticationKeyLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(targetLanguageLabel) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties index 8eebeeab58..6ebd3002ef 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties @@ -14,3 +14,4 @@ GoogleTranslatorSettingsPanel.testButton.text=Test GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: GoogleTranslatorSettingsPanel.resultLabel.text=Result: GoogleTranslatorSettingsPanel.testResultValueLabel.text= +BingTranslatorSettingsPanel.authenticationKeyLabel.text=Authentication Key: diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED index fec94d3419..35e06bda99 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED @@ -24,3 +24,4 @@ GoogleTranslatorSettingsPanel.testButton.text=Test GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: GoogleTranslatorSettingsPanel.resultLabel.text=Result: GoogleTranslatorSettingsPanel.testResultValueLabel.text= +BingTranslatorSettingsPanel.authenticationKeyLabel.text=Authentication Key: From 46b14869f7d236c64cf9d8050c20916c9640c0b5 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 31 May 2019 14:26:36 -0400 Subject: [PATCH 018/453] Rewrote interesting files doc --- .../images/InterestingFiles/bomb_png.png | Bin 0 -> 23271 bytes .../InterestingFiles/download_archive.png | Bin 0 -> 23544 bytes .../images/InterestingFiles/ingest.png | Bin 0 -> 54843 bytes .../images/InterestingFiles/main.png | Bin 0 -> 67991 bytes .../InterestingFiles/new_large_files.png | Bin 0 -> 24102 bytes .../images/InterestingFiles/new_rule.png | Bin 0 -> 22696 bytes .../images/InterestingFiles/new_rule_set.png | Bin 0 -> 9392 bytes .../InterestingFiles/private_folder.png | Bin 0 -> 22816 bytes .../images/InterestingFiles/results.png | Bin 0 -> 97994 bytes docs/doxygen-user/interesting_files.dox | 137 +++++++++++------- 10 files changed, 85 insertions(+), 52 deletions(-) create mode 100644 docs/doxygen-user/images/InterestingFiles/bomb_png.png create mode 100644 docs/doxygen-user/images/InterestingFiles/download_archive.png create mode 100644 docs/doxygen-user/images/InterestingFiles/ingest.png create mode 100644 docs/doxygen-user/images/InterestingFiles/main.png create mode 100644 docs/doxygen-user/images/InterestingFiles/new_large_files.png create mode 100644 docs/doxygen-user/images/InterestingFiles/new_rule.png create mode 100644 docs/doxygen-user/images/InterestingFiles/new_rule_set.png create mode 100644 docs/doxygen-user/images/InterestingFiles/private_folder.png create mode 100644 docs/doxygen-user/images/InterestingFiles/results.png diff --git a/docs/doxygen-user/images/InterestingFiles/bomb_png.png b/docs/doxygen-user/images/InterestingFiles/bomb_png.png new file mode 100644 index 0000000000000000000000000000000000000000..016ff5dea5026f0a27c3d16e7f35b5c5eab6eb05 GIT binary patch literal 23271 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 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/InterestingFiles/download_archive.png b/docs/doxygen-user/images/InterestingFiles/download_archive.png new file mode 100644 index 0000000000000000000000000000000000000000..aa457e929c1ea97720efed9a2592f2ad10dae95b GIT binary patch literal 23544 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

lzgns?PBq8t>3Sq3>{l$s>Z_b&W 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Ï>E#S=@@5>0W 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 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/InterestingFiles/ingest.png b/docs/doxygen-user/images/InterestingFiles/ingest.png new file mode 100644 index 0000000000000000000000000000000000000000..aadf558768a1024048229335fb656b4292e0f20d GIT binary patch literal 54843 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 W(0%RB%7&J)L@bH($`JS!BMYou`dS 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 W5WPNn7of`UFSXK0KhFRO;uw6 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 D8Lc)G literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/InterestingFiles/results.png b/docs/doxygen-user/images/InterestingFiles/results.png new file mode 100644 index 0000000000000000000000000000000000000000..9ce29ceaead402642e67fe642b76aad3cc72a0eb GIT binary patch literal 97994 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 q#^#zia)t~%$CZySIet;&+h_aj)O90Wf$!wOTN +

  • A rule is a set of conditions that must be true about a file for it to match the rule. All conditions in the rule must be true. For example, if a rule has conditions "file size > 1 MB" and "file extension = .txt", only files that match both conditions will be considered a match. +
  • A rule set is a collection of rules. If a file matches any rule in the rule set it will be flagged as a match for this rule set. Rule sets can be enabled and disabled at ingest time. + -All rules need to be part of a set. Select "New set" on the left side panel to create a new set. Sets need to have the following defined: +\section interesting_files_config Configuration -- Set Name (required) -- Set Description (optional) +To create and edit your rule sets, go to "Tools", "Options" and then select the "Interesting Files" tab. The area on the left side will show you a list of all the rule sets that are currently available. Selecting a rule set will display its description and information about each of its rules on the right side of the panel. -Sets can be renamed, edited, copied, and imported and exported from the left side panel. +\image html InterestingFiles/main.png -Rules specify what to look for in a data source. Each rule specifies: -- Type: If the rule should be applied to only files, only directories, or both files and directories. -- Name Pattern: String to match the file name against. Note that you can enter multiple extensions in a comma-separated list. -- Name Pattern Type: Should the pattern be matched against the full file type or just the extension. -- Path Pattern: A substring of the parent path that must be matched. This allows you to restrict generic names to a specific structure (such as an application name). A substring match is performed. -- Rule Name: Additional details that are displayed in the UI when that rule is matched. This allows you to determine which rule in the set matched. +The buttons on the bottom of the left side of the panel control the rule sets. -\image html interesting_files_configuration.PNG +
      +
    • New Set - Allows you to create a new rule set (rules will be added later). You will see a new window asking for the name of the new rule set, an optional description, and whether known files should be ignored (i.e., if a file is in the NSRL, then it won't show up on the list of matches even if it satisfies the conditions of one of the rules in the set). +\image html InterestingFiles/new_rule_set.png +
    • Edit Set - Brings up the same window as "New Set" and allows you to change any of the fields. +
    • Delete Set - Removes the selected rule set +
    • Copy Set - Makes a copy of the selected rule set. It will bring up the same window as "New Set". You must change the rule set name in order to save the copy. +
    • Import Set - Imports a previously exported rule set. Once imported, you will not need the original copy. +
    • Export Set - Exports the selected rule set in a format that can be shared with other Autopsy users. +
    +Selecting a rule set will display its description, whether it ignores known files, and the rules contained in the set. Selecting a rule will display the conditions for that rule in the "Rule Details" section. -VMWare Example --------- -This set of rules is to detect VMWare Player or vmdk files. This would help to make sure you look into the virtual machines for additional evidence. +The buttons under the list of rules allow you to create new rules and edit or delete existing rules. Selecting "New Rule" will bring up a new window to create the rule. -NOTE: This is not extensive and is simply a minimal example: +\image html InterestingFiles/new_rule.png +The top line allows you to choose whether you want to match only files, only directories, or both. If you select directories or both, some of the condition types will be unavailable since they only apply to files. -- Set Name: VMWare -- Rule 1: - - Type: Files - - Full Name: vmplayer.exe - - Name: Program EXE -- Rule 2: - - Type: Files - - Extension: vmdk - - Name: VMDK File +Each rule must have at least one condition. To create conditions, check the box to the left of the condition you want to enable. The following is a description of each condition, with some full examples after. -iPhone Backups Example -------------- -This set of rules is to detect a folder for iPhone Backups. These are typically in a folder such as "%AppData%\Roaming\Apple Computer\MobileSync\Backup" on Windows. Here is a rule that you could use for that. +
      +
    • Name - Enter either the full file name or one or more extensions, and select whether this is an exact match or a substring/regex match. If substring/regex match is enabled, it will automatically add wildcards to the beginning and end of the text. If you're only matching directories, this will match the directory name. If you're using a comma-separated list of extensions, make sure the regex checkbox is disabled - the two features do not work together. The following table shows some examples of what the different combinations can be used for. -- Set Name: iPhone Backups -- Rule 1: - - Type: Directory - - Name: Backup - - Path: Apple Computer/MobileSync + + + + + + + + +
      TypeSubstring/RegexTextDescriptionSample match
      Full Namefalse\verbatim test.txt \endverbatimWill match files named "test.txt"text.txt
      Full Nametrue\verbatim bomb \endverbatimWill match files with "bomb" anywhere their namePipe bomb.png
      Full Nametrue\verbatim virus.*\.exe \endverbatimWill match files with "virus" followed by ".exe" anywhere their namebad_virus.exe
      Extension Onlyfalse\verbatim zip \endverbatimWill match .zip filesmyArchive.zip
      Extension Onlyfalse\verbatim zip,rar,7z \endverbatimWill match .zip, .rar, and .7z filesanotherArchive.rar
      Extension Onlytrue\verbatim jp \endverbatimWill match .jpg, .jpeg files, and any others with "jp" in the extensionmyImage.jpg
      +
    • Path Substring - Enter a folder name that must be part of file's path for it to be a match. If you only want to specify that a word appears somewhere in the path, use the regex option. + + + + + +
      RegexTextDescriptionSample match
      false\verbatim Documents \endverbatimMatch any file that has a folder named "Documents" in its path/folder1/Documents/fileA.doc
      true\verbatim bomb \endverbatimMatch any file with "bomb" in the path/folder1/bomb making/file2.doc
      true\verbatim Users/.*/Downloads \endverbatimMatch any file with "Users" and "Downloads" in the pathC:/Users/user1/Downloads/myFile.txt
      -Using the Module -====== +
    • MIME Type - Use the pull-down list to select a MIME type. Only a single MIME type can be selected. -When you enable the Interesting Files module, you can choose what rule sets to enable. To add rules, use the "Advanced" button from the ingest module panel. +
    • File Size - Select whether you want to match files equal to, smaller than, or larger than a given size. -When files are found, they will be in the Interesting Files area of the tree. You should see the set and rule names with the match. +
    • Modified Within - Select how recently a file must have been modified to match the rule. +
    +Finally you can optionally enter a name for the rule. This will be displayed in the UI for each match. -Ingest Settings ------- +\subsection interesting_files_examples Examples +Here are a few examples of rules being created. -When running the ingest modules, the user can choose which interesting file rules to enable . -
    -\image html interesting_files_ingest_settings.PNG +This is a rule that matches any file with "bomb" in the name that also has an "image/png" MIME type. -Seeing Results ------- -The results show up in the tree under "Results", "Interesting Items". +\image html InterestingFiles/bomb_png.png -\image html interesting_files_results.PNG +This is a rule that matches folders named "Private". + +\image html InterestingFiles/private_folder.png + +This rule is looking for archives in the user download directory. It requires "Users" and "Downloads" in the file's path, and an extension of .zip, .rar, or .7z. + +\image html InterestingFiles/download_archive.png + +This is a rule that matches files with size at least 50MB that have been modified in the last week. + +\image html InterestingFiles/new_large_files.png + +\section interesting_files_running Running the Module + +At runtime, you can select which rule sets you would like to run on your data source. + +\image html InterestingFiles/ingest.png + +\section interesting_files_results Viewing Results + +Files that match any of the rules in the enabled rule sets will be shown in the Results section of the \ref tree_viewer_page under "Interesting Items" and then the name of the rule set that matched. Note that other modules besides Interesting Files put results in this section of the tree, so there may be more than just what matched your rule sets. Selecting the "Interesting Files" node under one of your rule sets will display all matching files in the \ref result_viewer_page. + +\image html InterestingFiles/results.png + +You can see which rule matched in the "Category" column. You can export some or all of the files for further analysis. To do this, first use the standard Windows file +selection methods to highlight the files you want to export in the \ref result_viewer_page : +
      +
    • Hold down Ctrl and click on each file you want to export +
    • Hold down Shift to select a range of files +
    • Click on any file in the Result Viewer and then hit Ctrl+A to select all the files +
    +Once you have your desired files selected, right click and select “Extract Files” to save copies of them. */ From 1d0e0ad8800083d45681cf3326953b42edb1e79d Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 31 May 2019 14:32:24 -0400 Subject: [PATCH 019/453] Removed old screenshots --- .../images/interesting_files_configuration.PNG | Bin 55280 -> 0 bytes .../interesting_files_ingest_settings.PNG | Bin 44726 -> 0 bytes .../images/interesting_files_results.PNG | Bin 20981 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/doxygen-user/images/interesting_files_configuration.PNG delete mode 100644 docs/doxygen-user/images/interesting_files_ingest_settings.PNG delete mode 100644 docs/doxygen-user/images/interesting_files_results.PNG diff --git a/docs/doxygen-user/images/interesting_files_configuration.PNG b/docs/doxygen-user/images/interesting_files_configuration.PNG deleted file mode 100644 index 2c37a4f27b72d52a68ee7cf22e78ddd780f880fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55280 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
    Gf<{ZZ)kRUhYp+!;+})AP%N)B2aZO%^ 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 Ib4q9e02eON&Hw-a diff --git a/docs/doxygen-user/images/interesting_files_ingest_settings.PNG b/docs/doxygen-user/images/interesting_files_ingest_settings.PNG deleted file mode 100644 index 854a4958624d38752209190d85ab35a65feebe76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44726 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%0Dp~SUH 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 From 99bf8686a32739b1ca05d6aabbb1d0fc00eff3df Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 31 May 2019 14:52:13 -0400 Subject: [PATCH 020/453] Fixing doxygen warnings --- .../autopsy/casemodule/LogicalImagerDSProcessor.java | 11 +++++++---- .../autopsy/communications/FiltersPanel.java | 6 ++++-- .../relationships/RelationshipBrowser.java | 2 +- .../sleuthkit/autopsy/contentviewers/FileViewer.java | 6 +++--- .../autopsy/datamodel/FileTypesByExtension.java | 1 + Core/src/org/sleuthkit/autopsy/rejview/HexView.java | 12 ++++++------ .../sleuthkit/autopsy/rejview/RejTreeKeyView.java | 8 ++++---- .../sleuthkit/autopsy/rejview/RejTreeValueView.java | 10 +++++----- 8 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerDSProcessor.java index ff01e1c540..b817b77b21 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerDSProcessor.java @@ -186,13 +186,16 @@ public class LogicalImagerDSProcessor implements DataSourceProcessor { * associated with the data source that is * intended to be unique across multiple cases * (e.g., a UUID). - * @param imageFilePath Path to the image file. + * @param imagePath Path to the image file. + * @param sectorSize The sector size (use '0' for autodetect). * @param timeZone The time zone to use when processing dates * and times for the image, obtained from * java.util.TimeZone.getID. - * @param chunkSize The maximum size of each chunk of the raw - * data source as it is divided up into virtual - * unallocated space files. + * @param ignoreFatOrphanFiles Whether to parse orphans if the image has a + * FAT filesystem. + * @param md5 The MD5 hash of the image, may be null. + * @param sha1 The SHA-1 hash of the image, may be null. + * @param sha256 The SHA-256 hash of the image, may be null. * @param src The source directory of image. * @param dest The destination directory to copy the source. * @param progressMonitor Progress monitor for reporting progress diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 22da4a81e9..a6cdf63b63 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -272,9 +272,11 @@ final public class FiltersPanel extends JPanel { } }); } - + /** * Populate the devices filter widgets + * + * @param initialState */ private void updateDeviceFilter(boolean initialState) { try { @@ -301,7 +303,7 @@ final public class FiltersPanel extends JPanel { * Given a list of subFilters, set the states of the panel controls * accordingly. * - * @param subFilters A list of subFilters + * @param commFilter Contains a list of subFilters */ public void setFilters(CommunicationsFilter commFilter) { List subFilters = commFilter.getAndFilters(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java index 03729540df..b8c19f518e 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java @@ -61,7 +61,7 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider /** * Sets the value of currentSelection and passes the SelectionInfo onto the - * currently selected\visible tab. + * currently selected or visible tab. * * @param info Currently selected account nodes */ diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index a32dce1bbf..ead4199941 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -77,12 +77,12 @@ public class FileViewer extends javax.swing.JPanel implements DataContentViewer } /** - * Get the FileTypeViewer for a given mimetype + * Get the FileTypeViewer for a given file * - * @param mimeType + * @param file * * @return FileTypeViewer, null if no known content viewer supports the - * mimetype + * file */ private FileTypeViewer getSupportingViewer(AbstractFile file) { FileTypeViewer viewer = mimeTypeToViewerMap.get(file.getMIMEType()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 152e8a6bac..b3005c28b4 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -390,6 +390,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * @param skCase * @param o Observable that will notify when there could be new * data to display + * @param nodeName */ private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) { super(nodeName, new ViewsKnownAndSlackFilter<>()); diff --git a/Core/src/org/sleuthkit/autopsy/rejview/HexView.java b/Core/src/org/sleuthkit/autopsy/rejview/HexView.java index f78f9ac1ad..932bcf627a 100644 --- a/Core/src/org/sleuthkit/autopsy/rejview/HexView.java +++ b/Core/src/org/sleuthkit/autopsy/rejview/HexView.java @@ -251,10 +251,10 @@ final class HexView extends JPanel { this.setHighlight(startByte, endByte); if (startByte != endByte) { - /** - * @param 1 Start - * @param 2 End - * @param 3 Len + /* + * param 1 Start + * param 2 End + * param 3 Len */ int length = endByte - startByte; String text = Bundle.HexView_statusTemplate_nonZeroLength( @@ -266,8 +266,8 @@ final class HexView extends JPanel { String.format("0x%1$x", length)); statusLabel.setText(text); } else { - /** - * @param 1 Start + /* + * param 1 Start */ String text = Bundle.HexView_statusTemplate_zeroLength(startByte, String.format("0x%1$x", startByte)); statusLabel.setText(text); diff --git a/Core/src/org/sleuthkit/autopsy/rejview/RejTreeKeyView.java b/Core/src/org/sleuthkit/autopsy/rejview/RejTreeKeyView.java index dacb2e8177..a74d4d26b4 100644 --- a/Core/src/org/sleuthkit/autopsy/rejview/RejTreeKeyView.java +++ b/Core/src/org/sleuthkit/autopsy/rejview/RejTreeKeyView.java @@ -58,10 +58,10 @@ public final class RejTreeKeyView extends RejTreeNodeView { public RejTreeKeyView(RejTreeKeyNode node) { super(new BorderLayout()); - /** - * @param 1 Name - * @param 2 Number of subkeys - * @param 3 Number of values + /* + * param 1 Name + * param 2 Number of subkeys + * param 3 Number of values */ String metadataTemplate = "" + Bundle.RejTreeKeyView_template_name() diff --git a/Core/src/org/sleuthkit/autopsy/rejview/RejTreeValueView.java b/Core/src/org/sleuthkit/autopsy/rejview/RejTreeValueView.java index 8b18d38bf5..d2b4e7cf5c 100644 --- a/Core/src/org/sleuthkit/autopsy/rejview/RejTreeValueView.java +++ b/Core/src/org/sleuthkit/autopsy/rejview/RejTreeValueView.java @@ -51,9 +51,9 @@ public final class RejTreeValueView extends RejTreeNodeView { "RejTreeValueView.valueBorder.title=Value",}) public RejTreeValueView(RejTreeValueNode node) { super(new BorderLayout()); - /** - * @param 1 Name - * @param 2 Type + /* + * param 1 Name + * param 2 Type */ String metadataTemplate = "" + Bundle.RejTreeValueView_template_name() @@ -63,8 +63,8 @@ public final class RejTreeValueView extends RejTreeNodeView { String valueName; String valueType; - /** - * @param 1 Value + /* + * param 1 Value */ String valueTemplate = "%1$s"; try { From f05844a62cc162351ceebecd3abaaa8b9b3b5038 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 31 May 2019 14:57:31 -0400 Subject: [PATCH 021/453] 5123 remove all images and other uri elements from html by default --- .../autopsy/contentviewers/HtmlPanel.java | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java index f915b8d5a6..e4768d075e 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java @@ -18,6 +18,11 @@ */ package org.sleuthkit.autopsy.contentviewers; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -25,8 +30,9 @@ import javafx.concurrent.Worker; import javafx.scene.web.WebView; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Node; +import net.htmlparser.jericho.Attribute; +import net.htmlparser.jericho.OutputDocument; +import net.htmlparser.jericho.Source; import org.openide.util.NbBundle.Messages; import org.w3c.dom.Document; import org.w3c.dom.NodeList; @@ -38,6 +44,7 @@ import org.w3c.dom.events.EventTarget; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class HtmlPanel extends javax.swing.JPanel { + private static final Logger logger = Logger.getLogger(HtmlPanel.class.getName()); private static final long serialVersionUID = 1L; private static final String TEXT_TYPE = "text/plain"; private final JFXPanel jfxPanel = new JFXPanel(); @@ -99,12 +106,26 @@ final class HtmlPanel extends javax.swing.JPanel { * @return The cleansed HTML String */ private String cleanseHTML(String htmlInString) { - org.jsoup.nodes.Document doc = Jsoup.parse(htmlInString); - // remove all 'img' tags. - doc.select("img").stream().forEach(Node::remove); - // remove all 'span' tags, these are often images which are ads - doc.select("span").stream().forEach(Node::remove); - return doc.html(); + String returnString = ""; + try { + Source source = new Source(new StringReader(htmlInString)); + OutputDocument document = new OutputDocument(source); + //remove background images + source.getAllTags().stream().filter((tag) -> (tag.toString().contains("background-image"))).forEachOrdered((tag) -> { + document.remove(tag); + }); + //remove images + source.getAllElements("img").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove other URI elements such as input boxes + List attributesToRemove = source.getURIAttributes(); + document.remove(attributesToRemove); + returnString = document.toString(); + } catch (IOException ex) { + logger.log(Level.WARNING, "Unable to read html for cleaning out URI elements with Jericho", ex); + } + return returnString; } /** From ab242a405e2ba8d6fc8c28dd57d369e4003b66b7 Mon Sep 17 00:00:00 2001 From: Brian Kjersten Date: Fri, 31 May 2019 17:40:53 -0400 Subject: [PATCH 022/453] 5106: Error message when we can't connect to Google. --- .../translators/GoogleTranslator.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index 38506ef936..318191e713 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -28,6 +28,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.logging.Level; import java.util.logging.Logger; import org.openide.util.NbBundle.Messages; @@ -56,9 +58,26 @@ public final class GoogleTranslator implements TextTranslator { settingsPanel = new GoogleTranslatorSettingsPanel(settings.getCredentialPath(), settings.getTargetLanguageCode()); loadTranslator(); } - + + private static boolean googleIsReachable() { + String host = "www.google.com"; + InetAddress address; + try { + address = InetAddress.getByName(host); + return address.isReachable(1500); + }catch (UnknownHostException ex) { + return false; + } catch (IOException ex) { + return false; + } + } + @Override public String translate(String string) throws TranslationException { + if (!googleIsReachable()) { + throw new TranslationException("Failure translating using GoogleTranslator: Cannot connect to Google"); + } + if (googleTranslate != null) { try { // Translates some text into English, without specifying the source language. @@ -66,10 +85,10 @@ public final class GoogleTranslator implements TextTranslator { // HTML files were producing lots of white space at the end String substring = string.trim(); - // WE can't currently set parameters, so we are using the default behavior of - // asuming the input is HTML. We need to replace newlines with
    for Google to preserve them + // We can't currently set parameters, so we are using the default behavior of + // assuming the input is HTML. We need to replace newlines with
    for Google to preserve them substring = substring.replaceAll("(\r\n|\n)", "
    "); - + // The API complains if the "Payload" is over 204800 bytes. I'm assuming that // deals with the full request. At some point, we get different errors about too // much text. Officially, Google says they will googleTranslate only 5k chars, @@ -81,7 +100,7 @@ public final class GoogleTranslator implements TextTranslator { Translation translation = googleTranslate.translate(substring); String translatedString = translation.getTranslatedText(); - + // put back the newlines translatedString = translatedString.replaceAll("
    ", "\n"); return translatedString; @@ -93,7 +112,7 @@ public final class GoogleTranslator implements TextTranslator { throw new TranslationException("Google Translator has not been configured, credentials need to be specified"); } } - + @Messages({"GoogleTranslator.name.text=Google Translate"}) @Override public String getName() { From 74ab8af6e049bda6d0a352488517f247c85bee09 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 11:01:11 -0400 Subject: [PATCH 023/453] 5092 populate ingest status column datasource summary --- .../Bundle.properties-MERGED | 1 + .../datasourcesummary/DataSourceBrowser.java | 11 +++- .../datasourcesummary/DataSourceSummary.java | 56 +++++++++++++++++++ .../DataSourceSummaryNode.java | 4 ++ 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED index 508e04b76a..fe998b0e57 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED @@ -66,6 +66,7 @@ DataSourceSummaryDialog.window.title=Data Sources Summary DataSourceSummaryNode.column.dataSourceName.header=Data Source Name DataSourceSummaryNode.column.files.header=Files DataSourceSummaryNode.column.results.header=Results +DataSourceSummaryNode.column.status.header=Ingest Status DataSourceSummaryNode.column.tags.header=Tags DataSourceSummaryNode.column.type.header=Type DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java index e3b9fa0e93..e946317da8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryNode. import static javax.swing.SwingConstants.RIGHT; import javax.swing.table.TableColumn; import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.IngestJobInfo; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -50,9 +51,10 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataSourceBrowser.class.getName()); - private static final int COUNT_COLUMN_WIDTH = 25; - private static final int USAGE_COLUMN_WIDTH = 120; - private static final int DATA_SOURCE_COLUMN_WIDTH = 325; + private static final int COUNT_COLUMN_WIDTH = 20; + private static final int INGEST_STATUS_WIDTH = 50; + private static final int USAGE_COLUMN_WIDTH = 110; + private static final int DATA_SOURCE_COLUMN_WIDTH = 280; private final Outline outline; private final org.openide.explorer.view.OutlineView outlineView; private final ExplorerManager explorerManager; @@ -69,6 +71,7 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana outlineView = new org.openide.explorer.view.OutlineView(); this.setVisible(true); outlineView.setPropertyColumns( + Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_results_header(), Bundle.DataSourceSummaryNode_column_results_header(), @@ -90,6 +93,8 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana column.setPreferredWidth(COUNT_COLUMN_WIDTH); } else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_type_header())) { column.setPreferredWidth(USAGE_COLUMN_WIDTH); + } else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_status_header())) { + column.setPreferredWidth(INGEST_STATUS_WIDTH); } else { column.setPreferredWidth(DATA_SOURCE_COLUMN_WIDTH); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java index 1aa55d95b5..a04604b3cd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java @@ -18,7 +18,17 @@ */ package org.sleuthkit.autopsy.casemodule.datasourcesummary; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.datamodel.CaseDbAccessManager; import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.IngestJobInfo; +import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType; +import org.sleuthkit.datamodel.TskCoreException; /** * Wrapper object for a DataSource and the information associated with it. @@ -27,6 +37,7 @@ import org.sleuthkit.datamodel.DataSource; class DataSourceSummary { private final DataSource dataSource; + private String status = ""; private final String type; private final long filesCount; private final long resultsCount; @@ -45,12 +56,23 @@ class DataSourceSummary { */ DataSourceSummary(DataSource dSource, String typeValue, Long numberOfFiles, Long numberOfResults, Long numberOfTags) { dataSource = dSource; + updateStatus(); type = typeValue == null ? "" : typeValue; filesCount = numberOfFiles == null ? 0 : numberOfFiles; resultsCount = numberOfResults == null ? 0 : numberOfResults; tagsCount = numberOfTags == null ? 0 : numberOfTags; } + void updateStatus() { + try { + IngestJobQueryCallback callback = new IngestJobQueryCallback(); + Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select("status_id FROM ingest_jobs WHERE obj_id=" + dataSource.getId(), callback); + status = callback.getStatus(); + } catch (NoCurrentCaseException | TskCoreException ex) { + + } + } + /** * Get the DataSource * @@ -87,6 +109,10 @@ class DataSourceSummary { return resultsCount; } + String getIngestStatus(){ + return status; + } + /** * Get the number of tagged content objects in this DataSource * @@ -95,4 +121,34 @@ class DataSourceSummary { long getTagsCount() { return tagsCount; } + + class IngestJobQueryCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback { + + IngestJobStatusType jobStatus = null; + + @Override + public void process(ResultSet rs) { + try { + while (rs.next()) { + IngestJobStatusType currentStatus = IngestJobStatusType.fromID(rs.getInt("status_id")); + if (currentStatus == IngestJobStatusType.COMPLETED) { + jobStatus = currentStatus; + } else if (currentStatus == IngestJobStatusType.STARTED) { + jobStatus = currentStatus; + return; + } + } + } catch (SQLException ex) { + System.out.println("EEEP"); + } + } + + String getStatus() { + if (jobStatus == null) { + return ""; + } else { + return jobStatus.getDisplayName(); + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java index 8323061a66..c9aef8c644 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java @@ -109,6 +109,7 @@ final class DataSourceSummaryNode extends AbstractNode { static final class DataSourceSummaryEntryNode extends AbstractNode { private final DataSource dataSource; + private final String status; private final String type; private final long filesCount; private final long resultsCount; @@ -124,6 +125,7 @@ final class DataSourceSummaryNode extends AbstractNode { DataSourceSummaryEntryNode(DataSourceSummary dataSourceSummary) { super(Children.LEAF); dataSource = dataSourceSummary.getDataSource(); + status = dataSourceSummary.getIngestStatus(); type = dataSourceSummary.getType(); filesCount = dataSourceSummary.getFilesCount(); resultsCount = dataSourceSummary.getResultsCount(); @@ -143,6 +145,7 @@ final class DataSourceSummaryNode extends AbstractNode { } @Messages({"DataSourceSummaryNode.column.dataSourceName.header=Data Source Name", + "DataSourceSummaryNode.column.status.header=Ingest Status", "DataSourceSummaryNode.column.type.header=Type", "DataSourceSummaryNode.column.files.header=Files", "DataSourceSummaryNode.column.results.header=Results", @@ -157,6 +160,7 @@ final class DataSourceSummaryNode extends AbstractNode { } sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(), dataSource)); + sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), status)); sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(), type)); sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(), From 1629706d4454d590258d07623e99715aae0fe55e Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 3 Jun 2019 11:22:10 -0400 Subject: [PATCH 024/453] Fix for exception when attempting to split empty keys. --- .../org/sleuthkit/autopsy/datamodel/BaseChildFactory.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java index 4a1147494a..9727d83e39 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java @@ -286,7 +286,12 @@ public abstract class BaseChildFactory extends ChildFactory.D * If pageSize is set split keys into pages, otherwise create a * single page containing all keys. */ - pages = Lists.partition(keys, pageSize > 0 ? pageSize : keys.size()); + if (keys.isEmpty()) { + pages.clear(); + } else { + pages = Lists.partition(keys, pageSize > 0 ? pageSize : keys.size()); + } + if (pages.size() != oldPageCount) { try { // Number of pages has changed so we need to send out a notification. From 92e0d6073a81fa066c5521289e3540daa8555cef Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 3 Jun 2019 12:51:02 -0400 Subject: [PATCH 025/453] Turn result paging on by default. --- Core/src/org/sleuthkit/autopsy/core/UserPreferences.java | 4 ++-- .../org/sleuthkit/autopsy/corecomponents/Bundle.properties | 2 +- .../sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index d6f68086c4..c18d8b0f96 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -504,10 +504,10 @@ public final class UserPreferences { /** * Get the maximum number of results to display in a result table. * - * @return Saved value or default (0) which indicates no max. + * @return Saved value or default (10,000). */ public static int getResultsTablePageSize() { - return preferences.getInt(RESULTS_TABLE_PAGE_SIZE, 0); + return preferences.getInt(RESULTS_TABLE_PAGE_SIZE, 10_000); } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index ac5bccebc8..7eb261880f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -216,4 +216,4 @@ DataResultViewerTable.pagesLabel.text=Pages: DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: -ViewPreferencesPanel.maxResultsLabel.toolTipText=\nAll results are shown in the results table if this value is 0 (default).\n
    You may want to consider setting this value if you have large numbers of results that are taking a long time to display.\n +ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
    Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index 2c0af06f50..cd75ae919e 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -269,4 +269,4 @@ DataResultViewerTable.pagesLabel.text=Pages: DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: -ViewPreferencesPanel.maxResultsLabel.toolTipText=\nAll results are shown in the results table if this value is 0 (default).\n
    You may want to consider setting this value if you have large numbers of results that are taking a long time to display.\n +ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
    Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n From 157e84356c52477acf0a71479b92e6c888ca02b0 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 3 Jun 2019 13:11:47 -0400 Subject: [PATCH 026/453] Update formatting of some IG classes --- .../sleuthkit/autopsy/imagegallery/ImageGalleryController.java | 2 +- .../org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 52295254c8..0e88900aee 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -698,7 +698,7 @@ public final class ImageGalleryController { //grab files with supported mime-types + MIMETYPE_CLAUSE //NON-NLS //grab files with image or video mime-types even if we don't officially support them - + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )" //NON-NLS + + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )" //NON-NLS + " ORDER BY parent_path "; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index 1c68d6c49d..a320590822 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); From b9ef321b1e96047407a177dc18ec6fb456d89aa4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 13:23:48 -0400 Subject: [PATCH 027/453] 5123 remove frames, iframes, audio, video, and other tags --- .../autopsy/contentviewers/HtmlPanel.java | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java index e4768d075e..b747e661e7 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java @@ -99,7 +99,7 @@ final class HtmlPanel extends javax.swing.JPanel { } /** - * Cleans out input HTML string + * Cleans out input HTML string so it will not access resources over the internet * * @param htmlInString The HTML string to cleanse * @@ -118,6 +118,42 @@ final class HtmlPanel extends javax.swing.JPanel { source.getAllElements("img").forEach((element) -> { document.remove(element.getAllTags()); }); + //remove frames + source.getAllElements("frame").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove iframes + source.getAllElements("iframe").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove pictures + source.getAllElements("picture").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove svg + source.getAllElements("svg").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove audio + source.getAllElements("audio").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove video + source.getAllElements("video").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove tracks + source.getAllElements("track").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove embeded external elements + source.getAllElements("embed").forEach((element) -> { + document.remove(element.getAllTags()); + }); + //remove linked elements + source.getAllElements("link").forEach((element) -> { + document.remove(element.getAllTags()); + }); //remove other URI elements such as input boxes List attributesToRemove = source.getURIAttributes(); document.remove(attributesToRemove); From 51e12b2eaf88e9aaf0c6dbf499de89ca2db0e82f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 13:32:26 -0400 Subject: [PATCH 028/453] 5123 change text of Show images to say Download instead of show --- Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties | 2 +- Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form | 2 +- Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 652f8781ba..fd3bbc833f 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -83,7 +83,7 @@ MediaViewImagePanel.zoomResetButton.text=Reset MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= -HtmlPanel.showImagesToggleButton.text=Show Images +HtmlPanel.showImagesToggleButton.text=Download Images MediaPlayerPanel.audioSlider.toolTipText= MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form index 97b3c67f72..407f0e6ec7 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form @@ -18,7 +18,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java index b747e661e7..250f2ba3cd 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java @@ -221,7 +221,7 @@ final class HtmlPanel extends javax.swing.JPanel { layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(showImagesToggleButton) - .addGap(0, 95, Short.MAX_VALUE)) + .addGap(0, 75, Short.MAX_VALUE)) .addComponent(htmlJPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( From 3c03ee5ce3659422acbc3bf91cffca0c03200949 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 13:36:02 -0400 Subject: [PATCH 029/453] 5123 update merged properties file --- .../sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 236fddfada..7600a880a2 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -145,7 +145,7 @@ MediaViewImagePanel.zoomResetButton.text=Reset MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= -HtmlPanel.showImagesToggleButton.text=Show Images +HtmlPanel.showImagesToggleButton.text=Download Images MediaPlayerPanel.audioSlider.toolTipText= MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 From 90898578ab9045ebdd527dff222d519d321b9058 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 3 Jun 2019 13:36:29 -0400 Subject: [PATCH 030/453] Renamed classes, cleaned up code, added comments --- .../casemodule/services/TagsManager.java | 24 ++ .../ApplicationTagsManager.java | 67 ---- .../ContentViewerTagManager.java | 232 ++++++++++++ .../autopsy/contentviewers/Bundle.properties | 2 +- .../contentviewers/Bundle.properties-MERGED | 2 +- .../contentviewers/MediaViewImagePanel.form | 42 ++- .../contentviewers/MediaViewImagePanel.java | 355 ++++++++++++++---- .../imagetagging/FocusChangeEvent.java | 48 --- .../imagetagging/FocusChangeListener.java | 18 - .../contentviewers/imagetagging/ImageTag.java | 343 +++++++++++++++++ ...ControlType.java => ImageTagControls.java} | 7 +- .../imagetagging/ImageTagCreator.java | 95 +++-- .../imagetagging/ImageTagRegion.java | 82 ++++ .../imagetagging/ImageTagsGroup.java | 124 ++++++ .../imagetagging/StoredTag.java | 269 ------------- .../imagetagging/StoredTagEvent.java | 40 -- .../imagetagging/StoredTagListener.java | 33 -- .../imagetagging/TopLevelTagsGroup.java | 103 ----- 18 files changed, 1169 insertions(+), 717 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java delete mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java delete mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java rename Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/{ControlType.java => ImageTagControls.java} (84%) create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagRegion.java create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java delete mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java delete mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java delete mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java delete mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 896298c2bd..e328b572de 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.casemodule.services; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -30,9 +31,11 @@ import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.applicationtags.ContentViewerTagManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.CaseDbAccessManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; @@ -48,6 +51,27 @@ public class TagsManager implements Closeable { private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName()); private final SleuthkitCase caseDb; + + static { + //Create the contentviewer tags table (beta) if the current case does not + //have the table present + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> { + if (evt.getNewValue() != null) { + Case currentCase = (Case) evt.getNewValue(); + try { + CaseDbAccessManager caseDb = currentCase.getSleuthkitCase().getCaseDbAccessManager(); + //Create our custom application tags table, if need be. + if (!caseDb.tableExists(ContentViewerTagManager.TABLE_NAME)) { + caseDb.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, + String.format("Unable to create the %s table for image tag storage.", + ContentViewerTagManager.TABLE_NAME), ex); + } + } + }); + } /** * Tests whether or not a given tag display name contains an illegal diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java deleted file mode 100755 index 65756d16c8..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ApplicationTagsManager.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule.services.applicationtags; - -import java.util.EnumSet; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.CaseDbAccessManager; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * A per case Autopsy service that manages the addition of application content - * tags to the case database. - */ -public final class ApplicationTagsManager { - - private static CaseDbAccessManager dbManager; - - private static final String TABLE_NAME = "beta_tag_app_data"; - private static final String TABLE_SCHEMA = "(app_data_id INTEGER PRIMARY KEY, " - + "content_tag_id INTEGER NOT NULL, app_data TEXT NOT NULL," - + "FOREIGN KEY(content_tag_id) REFERENCES content_tags(tag_id))"; - - private final String INSERT_OR_REPLACE_TAG_DATA = "(content_tag_id, app_data) VALUES (?, ?)"; - private final String SELECT_TAG_DATA = "* FROM " + TABLE_NAME + " WHERE content_tag_id = ?"; - private final String DELETE_TAG_DATA = "WHERE app_data_id = ?"; - - static { - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> { - if(evt.getNewValue() != null) { - Case currentCase = (Case) evt.getNewValue(); - try { - CaseDbAccessManager caseDb = currentCase.getSleuthkitCase().getCaseDbAccessManager(); - //Create our custom application tags table, if need be. - if (!caseDb.tableExists(TABLE_NAME)) { - caseDb.createTable(TABLE_NAME, TABLE_SCHEMA); - } - - dbManager = caseDb; - } catch (TskCoreException ex) { - //Log - } - } - }); - } - - //public createTag - //public updateTag - //public getTag - //public deleteTag - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java new file mode 100755 index 0000000000..33b50bab07 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java @@ -0,0 +1,232 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.services.applicationtags; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A per case Autopsy service that manages the addition of content viewer tags + * to the case database. This manager is also responsible for serializing and + * deserializing instances of your tag data objects for persistence and retrieval. + */ +public class ContentViewerTagManager { + + //Used to convert Java beans into the physical representation that will be stored + //in the database (for now, JSON was chosen). + private static final ObjectMapper SERIALIZER = new ObjectMapper(); + + public static final String TABLE_NAME = "beta_tag_app_data"; + public static final String TABLE_SCHEMA = "(app_data_id INTEGER PRIMARY KEY, " + + "content_tag_id INTEGER NOT NULL, app_data TEXT NOT NULL, " + + "FOREIGN KEY(content_tag_id) REFERENCES content_tags(tag_id))"; + + private static final String INSERT_TAG_DATA = "(content_tag_id, app_data) VALUES (%d, '%s')"; + private static final String UPDATE_TAG_DATA = "SET content_tag_id = %d, app_data = '%s' WHERE app_data_id = %d"; + private static final String SELECT_TAG_DATA = "* FROM " + TABLE_NAME + " WHERE content_tag_id = %d"; + private static final String DELETE_TAG_DATA = "WHERE app_data_id = %d"; + + /** + * Creates and saves a new ContentViewerTag in the case database. The + * generic tag data instance T will be automatically serialized into a + * storable format and persisted. + * + * @param Generic java bean class type that will be serialized into a + * storable format for persistence + * @param contentTag ContentTag that this ContentViewerTag is associated + * with (1:1) + * @param tagDataBean Data instance that contains the tag information to be + * persisted. + * @return An instance of a ContentViewerTag of type T, which contains all + * the stored information. + * + * @throws SerializationException Thrown if the tag data bean instance T + * could not be serialized into a storable format for persistence in the + * case database. + * @throws TskCoreException Thrown if this operation did not successfully + * persist in the case database. + * @throws NoCurrentCaseException Thrown if invocation of this method occurs + * when no case is open. + */ + public static ContentViewerTag saveTag(ContentTag contentTag, T tagDataBean) throws SerializationException, TskCoreException, NoCurrentCaseException { + try { + long contentTagId = contentTag.getId(); + String serialAppData = SERIALIZER.writeValueAsString(tagDataBean); + String insertTemplateInstance = String.format(INSERT_TAG_DATA, + contentTagId, serialAppData); + long insertId = Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().insert(TABLE_NAME, insertTemplateInstance); + return new ContentViewerTag<>(insertId, contentTag, tagDataBean); + } catch (JsonProcessingException ex) { + throw new SerializationException("Unable to convert object instance into a storable format", ex); + } + } + + /** + * Updates the ContentViewerTag instance with the new tag data T and + * persists the changes to the case database. + * + * @param Generic java bean class type that will be serialized into a + * storable format for persistence + * @param oldTag ContentViewerTag instance to be updated + * @param tagDataBean Data instance that contains the updated information to + * be persisted. + * + * @throws SerializationException Thrown if the tag data bean instance T + * could not be serialized into a storable format for persistence in the + * case database. + * @throws TskCoreException Thrown if this operation did not successfully + * persist in the case database. + * @throws NoCurrentCaseException Thrown if invocation of this method occurs + * when no case is open. + */ + public static ContentViewerTag updateTag(ContentViewerTag oldTag, T tagDataBean) + throws SerializationException, TskCoreException, NoCurrentCaseException { + try { + String serialAppData = SERIALIZER.writeValueAsString(tagDataBean); + String updateTemplateInstance = String.format(UPDATE_TAG_DATA, + oldTag.getContentTag().getId(), serialAppData, oldTag.getId()); + Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager() + .update(TABLE_NAME, updateTemplateInstance); + return new ContentViewerTag<>(oldTag.getId(), oldTag.getContentTag(), tagDataBean); + } catch (JsonProcessingException ex) { + throw new SerializationException("Unable to convert object instance into a storable format", ex); + } + } + + /** + * Retrieves a ContentViewerTag instance that is associated with the + * specified ContentTag. The Java bean class that represents the technical + * details of the tag should be passed so that automatic binding can take + * place. + * + * @param Java bean class type that will be instantiated and filled in + * with data. + * @param contentTag ContentTag that this ContentViewerTag is associated + * with (1:1) + * @param clazz Java bean class that will be instantiated and filled in with + * data. + * @return ContentViewerTag with an instance of T as a member variable or + * null if the content tag does not have an associated ContentViewerTag of + * type T. + * + * @throws TskCoreException Thrown if this operation did not successfully + * persist in the case database. + * @throws NoCurrentCaseException Thrown if invocation of this method occurs + * when no case is open. + */ + public static ContentViewerTag getTag(ContentTag contentTag, Class clazz) throws TskCoreException, NoCurrentCaseException { + try { + String selectTemplateInstance = String.format(SELECT_TAG_DATA, contentTag.getId()); + final ArrayList result = new ArrayList<>(); + Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager() + .select(selectTemplateInstance, (ResultSet rs) -> { + try { + if (rs.next()) { + long tagId = rs.getLong(1); + String appDetails = rs.getString(3); + try { + T instance = SERIALIZER.readValue(appDetails, clazz); + result.add(new ContentViewerTag<>(tagId, contentTag, instance)); + } catch (IOException ex) { + //Databind for type T failed. Not a system error + //but rather a logic error on the part of the caller. + result.add(null); + } + } + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + }); + return result.get(0); + } catch (RuntimeException ex) { + throw new TskCoreException("Unable to select tags from db", (Exception) ex.getCause()); + } + } + + /** + * Deletes the content viewer tag with the specified id. + * + * @param contentViewerTag ContentViewerTag to delete + * @throws TskCoreException Thrown if this operation did not successfully + * persist in the case database. + * @throws NoCurrentCaseException Thrown if invocation of this method occurs + * when no case is open. + */ + public static void deleteTag(ContentViewerTag contentViewerTag) throws TskCoreException, NoCurrentCaseException { + String deleteTemplateInstance = String.format(DELETE_TAG_DATA, contentViewerTag.getId()); + Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager() + .delete(TABLE_NAME, deleteTemplateInstance); + } + + /** + * This class represents a stored tag in the case database. It is a wrapper + * for the tag id, the attached Content tag object, and the Java bean + * instance that describes the technical details for reconstructing the tag. + * + * @param Java bean class type that will be instantiated and filled in + * with data. + */ + public static class ContentViewerTag { + + private final long id; + private final ContentTag contentTag; + private final T details; + + private ContentViewerTag(long id, ContentTag contentTag, T details) { + this.id = id; + this.contentTag = contentTag; + this.details = details; + } + + public long getId() { + return id; + } + + public ContentTag getContentTag() { + return contentTag; + } + + public T getDetails() { + return details; + } + } + + /** + * System exception thrown in the event that class instance T could not be + * properly serialized. + */ + public static class SerializationException extends Exception { + + public SerializationException(String message, Exception source) { + super(message, source); + } + } + + //Prevent this class from being instantiated. + private ContentViewerTagManager() { + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 631bb91273..a8b66e1c31 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -92,4 +92,4 @@ MediaPlayerPanel.infoLabel.text=No Errors MediaViewImagePanel.deleteTagButton.text=Delete Tag MediaViewImagePanel.createTagButton.text=Create Tag MediaViewImagePanel.jMenu1.text=jMenu1 -MediaViewImagePanel.editTagButton.text=Edit Tag Info +MediaViewImagePanel.showTagsButton.text=Hide Tags diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index b18b79580c..44cae34fd9 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -154,7 +154,7 @@ MediaPlayerPanel.infoLabel.text=No Errors MediaViewImagePanel.deleteTagButton.text=Delete Tag MediaViewImagePanel.createTagButton.text=Create Tag MediaViewImagePanel.jMenu1.text=jMenu1 -MediaViewImagePanel.editTagButton.text=Edit Tag Info +MediaViewImagePanel.showTagsButton.text=Hide Tags # {0} - tableName SQLiteViewer.readTable.errorText=Error getting rows for table: {0} # {0} - tableName diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form index ccfe83b2cc..447957d66c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form @@ -225,10 +225,18 @@ - - + + + + + + + + + + @@ -245,21 +253,6 @@ - - - - - - - - - - - - - - - @@ -274,6 +267,21 @@ + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index b6ec14fba1..5d6fa7e193 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -18,18 +18,16 @@ */ package org.sleuthkit.autopsy.contentviewers; -import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagCreator; -import org.sleuthkit.autopsy.contentviewers.imagetagging.StoredTag; -import org.sleuthkit.autopsy.contentviewers.imagetagging.StoredTagListener; -import org.sleuthkit.autopsy.contentviewers.imagetagging.TopLevelTagsGroup; -import org.sleuthkit.autopsy.contentviewers.imagetagging.StoredTagEvent; import java.awt.EventQueue; import java.awt.event.ActionEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import static java.util.Objects.nonNull; import java.util.SortedSet; import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.concurrent.Task; @@ -37,7 +35,7 @@ import javafx.embed.swing.JFXPanel; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.Cursor; -import javafx.scene.Node; +import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -54,14 +52,22 @@ import javax.imageio.ImageIO; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.controlsfx.control.MaskerPane; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.python.google.common.collect.Lists; import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog; import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog.TagNameAndComment; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.contentviewers.imagetagging.ControlType; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.applicationtags.ContentViewerTagManager; +import org.sleuthkit.autopsy.casemodule.services.applicationtags.ContentViewerTagManager.ContentViewerTag; +import org.sleuthkit.autopsy.casemodule.services.applicationtags.ContentViewerTagManager.SerializationException; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagControls; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagCreator; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTag; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsGroup; import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.datamodel.AbstractFile; @@ -79,14 +85,15 @@ import org.sleuthkit.datamodel.TskCoreException; class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel { private static final Image EXTERNAL = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm()); + private final Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName()); private final boolean fxInited; private JFXPanel fxPanel; private AbstractFile file; - private StoredTag lastFocused; - private TopLevelTagsGroup imageGroup; - private ImageTagCreator tagger; + private Group masterGroup; + private ImageTagsGroup tagsGroup; + private ImageTagCreator imageTagCreator; private ImageView fxImageView; private ScrollPane scrollPane; private final ProgressBar progressBar = new ProgressBar(); @@ -132,22 +139,29 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan // build jfx ui (we could do this in FXML?) fxImageView = new ImageView(); // will hold image - imageGroup = new TopLevelTagsGroup(fxImageView); + masterGroup = new Group(fxImageView); + tagsGroup = new ImageTagsGroup(fxImageView); + masterGroup.getChildren().add(tagsGroup); deleteTagButton.setEnabled(false); - editTagButton.setEnabled(false); - imageGroup.addFocusChangeListener((event) -> { - if (event.getType() == ControlType.NOT_FOCUSED || event.getNode().equals(fxImageView)) { + + //Update buttons when users select (or unselect) image tags. + tagsGroup.addFocusChangeListener((event) -> { + if (event.getPropertyName().equals(ImageTagControls.NOT_FOCUSED.getName())) { deleteTagButton.setEnabled(false); - createTagButton.setEnabled(true); - editTagButton.setEnabled(false); - } else if (event.getType() == ControlType.FOCUSED) { + if (DisplayOptions.HIDE_TAGS.getName().equals(showTagsButton.getText())) { + createTagButton.setEnabled(true); + } + } else if (event.getPropertyName().equals(ImageTagControls.FOCUSED.getName())) { deleteTagButton.setEnabled(true); - editTagButton.setEnabled(true); createTagButton.setEnabled(false); - lastFocused = event.getNode(); + if (masterGroup.getChildren().contains(imageTagCreator)) { + imageTagCreator.disconnect(); + masterGroup.getChildren().remove(imageTagCreator); + } } }); - scrollPane = new ScrollPane(imageGroup); // scrolls and sizes imageview + + scrollPane = new ScrollPane(masterGroup); // scrolls and sizes imageview scrollPane.getStyleClass().add("bg"); //NOI18N scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED); scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED); @@ -178,9 +192,13 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan Platform.runLater(() -> { fxImageView.setViewport(new Rectangle2D(0, 0, 0, 0)); fxImageView.setImage(null); - imageGroup.getChildren().clear(); + masterGroup.getChildren().clear(); + if (imageTagCreator != null) { + imageTagCreator.disconnect(); + } + showTagsButton.setText("Hide Tags"); scrollPane.setContent(null); - scrollPane.setContent(imageGroup); + scrollPane.setContent(masterGroup); }); } @@ -228,14 +246,28 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan try { Image fxImage = readImageTask.get(); - imageGroup.getChildren().clear(); - imageGroup.getChildren().add(fxImageView); + masterGroup.getChildren().clear(); + tagsGroup.getChildren().clear(); this.file = file; if (nonNull(fxImage)) { // We have a non-null image, so let's show it. fxImageView.setImage(fxImage); resetView(); - scrollPane.setContent(imageGroup); + masterGroup.getChildren().add(fxImageView); + masterGroup.getChildren().add(tagsGroup); + + try { + List tags = Case.getCurrentCase().getServices() + .getTagsManager().getContentTagsByContent(file); + + List> contentViewerTags = getContentViewerTags(tags); + //Add all image tags + tagsGroup = buildImageTagsGroup(contentViewerTags); + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.WARNING, "Could not retrieve image tags for file in case db", ex); //NON-NLS + //TODO - pop dialog + } + scrollPane.setContent(masterGroup); } else { showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file); } @@ -275,6 +307,65 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan }); } + /** + * Finds all ContentViewerTags that are of type 'ImageTagRegion' for the + * current file. + * + * @param contentTags + * @return + * @throws TskCoreException + * @throws NoCurrentCaseException + */ + private List> getContentViewerTags(List contentTags) + throws TskCoreException, NoCurrentCaseException { + List> contentViewerTags = new ArrayList<>(); + for (ContentTag contentTag : contentTags) { + ContentViewerTag contentViewerTag = ContentViewerTagManager + .getTag(contentTag, ImageTagRegion.class); + if (contentViewerTag == null) { + continue; + } + + contentViewerTags.add(contentViewerTag); + } + return contentViewerTags; + } + + /** + * Builds ImageTag instances from stored ContentViewerTags of the + * appropriate type. + * + * @param contentTags + * @return + * @throws TskCoreException + * @throws NoCurrentCaseException + */ + private ImageTagsGroup buildImageTagsGroup(List> contentViewerTags) { + + contentViewerTags.forEach(contentViewerTag -> { + /** + * Build the image tag, add an edit event call back to persist all + * edits made on this image tag instance. + */ + ImageTag imageTag = new ImageTag(contentViewerTag, fxImageView); + imageTag.subscribeToEditEvents((event) -> { + try { + scrollPane.setCursor(Cursor.WAIT); + ImageTagRegion newRegion = (ImageTagRegion) event.getNewValue(); + ContentViewerTagManager.updateTag(contentViewerTag, newRegion); + scrollPane.setCursor(Cursor.DEFAULT); + } catch (SerializationException | NoCurrentCaseException | TskCoreException ex) { + LOGGER.log(Level.WARNING, "Could not save edit for image tag in case db", ex); //NON-NLS + //TODO - pop dialog + } + }); + + tagsGroup.getChildren().add(imageTag); + }); + + return tagsGroup; + } + /** * @return supported mime types */ @@ -329,13 +420,13 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan zoomInButton = new javax.swing.JButton(); jSeparator2 = new javax.swing.JToolBar.Separator(); zoomResetButton = new javax.swing.JButton(); - jSeparator3 = new javax.swing.JToolBar.Separator(); filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0)); + filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); createTagButton = new javax.swing.JButton(); jSeparator4 = new javax.swing.JToolBar.Separator(); - editTagButton = new javax.swing.JButton(); - jSeparator5 = new javax.swing.JToolBar.Separator(); deleteTagButton = new javax.swing.JButton(); + jSeparator3 = new javax.swing.JToolBar.Separator(); + showTagsButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(jMenu1, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.jMenu1.text")); // NOI18N @@ -441,8 +532,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan } }); toolbar.add(zoomResetButton); - toolbar.add(jSeparator3); toolbar.add(filler1); + toolbar.add(filler2); org.openide.awt.Mnemonics.setLocalizedText(createTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.createTagButton.text")); // NOI18N createTagButton.setFocusable(false); @@ -457,18 +548,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan toolbar.add(createTagButton); toolbar.add(jSeparator4); - org.openide.awt.Mnemonics.setLocalizedText(editTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.editTagButton.text")); // NOI18N - editTagButton.setFocusable(false); - editTagButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - editTagButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - editTagButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - editTagButtonActionPerformed(evt); - } - }); - toolbar.add(editTagButton); - toolbar.add(jSeparator5); - org.openide.awt.Mnemonics.setLocalizedText(deleteTagButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.deleteTagButton.text")); // NOI18N deleteTagButton.setFocusable(false); deleteTagButton.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); @@ -480,6 +559,18 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan } }); toolbar.add(deleteTagButton); + toolbar.add(jSeparator3); + + org.openide.awt.Mnemonics.setLocalizedText(showTagsButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.showTagsButton.text")); // NOI18N + showTagsButton.setFocusable(false); + showTagsButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + showTagsButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + showTagsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + showTagsButtonActionPerformed(evt); + } + }); + toolbar.add(showTagsButton); add(toolbar); }// //GEN-END:initComponents @@ -526,52 +617,162 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private void deleteTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTagButtonActionPerformed Platform.runLater(() -> { - imageGroup.deleteNode(lastFocused); - }); - try { - Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(lastFocused.getContentTag()); - } catch (TskCoreException ex) { + ImageTag tagInFocus = tagsGroup.getFocus(); + //Null should not be expected, but just as a safetly precaution + if (tagInFocus == null) { + return; + } - } + try { + ContentViewerTag contentViewerTag = tagInFocus.getContentViewerTag(); + scrollPane.setCursor(Cursor.WAIT); + ContentViewerTagManager.deleteTag(contentViewerTag); + Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(contentViewerTag.getContentTag()); + tagsGroup.getChildren().remove(tagInFocus); + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.WARNING, "Could not delete image tag in case db", ex); //NON-NLS + //TODO pop dialog + } + + scrollPane.setCursor(Cursor.DEFAULT); + }); + + deleteTagButton.setEnabled(false); + createTagButton.setEnabled(true); }//GEN-LAST:event_deleteTagButtonActionPerformed private void createTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createTagButtonActionPerformed - Platform.runLater(() -> { - tagger = new ImageTagCreator(fxImageView); - StoredTagListener newTagListener = (StoredTagEvent event) -> { - StoredTag tag = event.getTag(); - imageGroup.getChildren().add(tag); - SwingUtilities.invokeLater(() -> { - TagNameAndComment result = GetTagNameAndCommentDialog.doDialog(); - if (result != null) { - try { - ContentTag t = Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, - result.getTagName(), result.getComment()); - tag.addContentTag(t); - } catch (TskCoreException ex) { - Platform.runLater(() -> imageGroup.deleteNode(tag)); - } - } else { - Platform.runLater(() -> imageGroup.deleteNode(tag)); + createTagButton.setEnabled(false); + showTagsButton.setEnabled(false); + imageTagCreator = new ImageTagCreator(fxImageView); + + PropertyChangeListener newTagListener = (event) -> { + + SwingUtilities.invokeLater(() -> { + ImageTagRegion tag = (ImageTagRegion) event.getNewValue(); + //Ask the user for tag name and comment + TagNameAndComment result = GetTagNameAndCommentDialog.doDialog(); + if (result == null) { + createTagButton.setEnabled(true); + showTagsButton.setEnabled(true); + return; + } + + //Persist and build image tag + Platform.runLater(() -> { + try { + ContentViewerTag contentViewerTag = storeImageTag(tag, result); + ImageTag imageTag = buildImageTag(contentViewerTag); + tagsGroup.getChildren().add(imageTag); + } catch (TskCoreException | SerializationException | NoCurrentCaseException ex) { + LOGGER.log(Level.WARNING, "Could not save new image tag in case db", ex); //NON-NLS + //TODO pop dialog } }); - imageGroup.deleteNode(tagger); + }); - }; - tagger.addNewTagListener(newTagListener); - imageGroup.getChildren().add(tagger); - }); + //Remove image tag creator from panel + Platform.runLater(() -> { + imageTagCreator.disconnect(); + masterGroup.getChildren().remove(imageTagCreator); + }); + }; + + imageTagCreator.addNewTagListener(newTagListener); + Platform.runLater(() -> masterGroup.getChildren().add(imageTagCreator)); }//GEN-LAST:event_createTagButtonActionPerformed - private void editTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editTagButtonActionPerformed - ContentTag t = lastFocused.getContentTag(); - }//GEN-LAST:event_editTagButtonActionPerformed + /** + * + * @param contentViewerTag + * @return + */ + private ImageTag buildImageTag(ContentViewerTag contentViewerTag) { + ImageTag imageTag = new ImageTag(contentViewerTag, fxImageView); + + //Automatically persist edits made by user + imageTag.subscribeToEditEvents((edit) -> { + try { + scrollPane.setCursor(Cursor.WAIT); + ImageTagRegion newRegion = (ImageTagRegion) edit.getNewValue(); + ContentViewerTagManager.updateTag(contentViewerTag, newRegion); + scrollPane.setCursor(Cursor.DEFAULT); + } catch (SerializationException | TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.WARNING, "Could not save edit for image tag in case db", ex); //NON-NLS + //TODO pop dialog + } + }); + return imageTag; + } + + /** + * Stores the image tag by creating a ContentTag instance and associating + * the ImageTagRegion data with it in the case database. + * + * @param data + * @param result + */ + private ContentViewerTag storeImageTag(ImageTagRegion data, TagNameAndComment result) + throws TskCoreException, SerializationException, NoCurrentCaseException { + try { + scrollPane.setCursor(Cursor.WAIT); + ContentTag contentTag = Case.getCurrentCaseThrows().getServices().getTagsManager() + .addContentTag(file, result.getTagName(), result.getComment()); + ContentViewerTag contentViewerTag = ContentViewerTagManager.saveTag(contentTag, data); + return contentViewerTag; + } finally { + scrollPane.setCursor(Cursor.DEFAULT); + createTagButton.setEnabled(true); + showTagsButton.setEnabled(true); + } + } + + private void showTagsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showTagsButtonActionPerformed + Platform.runLater(() -> { + if (DisplayOptions.HIDE_TAGS.getName().equals(showTagsButton.getText())) { + //Temporarily remove the tags group and update buttons + masterGroup.getChildren().remove(tagsGroup); + showTagsButton.setText(DisplayOptions.SHOW_TAGS.getName()); + createTagButton.setEnabled(false); + deleteTagButton.setEnabled(false); + } else { + //Add tags group back in and update buttons + masterGroup.getChildren().add(tagsGroup); + showTagsButton.setText(DisplayOptions.HIDE_TAGS.getName()); + if (tagsGroup.getFocus() != null) { + createTagButton.setEnabled(false); + deleteTagButton.setEnabled(true); + } else { + createTagButton.setEnabled(true); + deleteTagButton.setEnabled(false); + } + } + }); + }//GEN-LAST:event_showTagsButtonActionPerformed + + /** + * Display states for the show/hide tags button. + */ + enum DisplayOptions { + HIDE_TAGS("Hide Tags"), + SHOW_TAGS("Show Tags"); + + private final String name; + + DisplayOptions(String name) { + this.name = name; + } + + String getName() { + return name; + } + } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton createTagButton; private javax.swing.JButton deleteTagButton; - private javax.swing.JButton editTagButton; private javax.swing.Box.Filler filler1; + private javax.swing.Box.Filler filler2; private javax.swing.JMenu jMenu1; private javax.swing.JPopupMenu jPopupMenu1; private javax.swing.JPopupMenu jPopupMenu2; @@ -579,10 +780,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private javax.swing.JToolBar.Separator jSeparator2; private javax.swing.JToolBar.Separator jSeparator3; private javax.swing.JToolBar.Separator jSeparator4; - private javax.swing.JToolBar.Separator jSeparator5; private javax.swing.JButton rotateLeftButton; private javax.swing.JButton rotateRightButton; private javax.swing.JTextField rotationTextField; + private javax.swing.JButton showTagsButton; private javax.swing.JToolBar toolbar; private javax.swing.JButton zoomInButton; private javax.swing.JButton zoomOutButton; @@ -728,8 +929,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan // Add the transforms in reverse order of intended execution. // Note: They MUST be added in this order to ensure translate is // executed last. - imageGroup.getTransforms().clear(); - imageGroup.getTransforms().addAll(translate, rotate, scale); + masterGroup.getTransforms().clear(); + masterGroup.getTransforms().addAll(translate, rotate, scale); // Adjust scroll bar positions for view changes. if (viewportWidth > fxPanel.getWidth()) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java deleted file mode 100755 index f8cac1a128..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeEvent.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.contentviewers.imagetagging; - -import java.util.EventObject; -import javafx.event.Event; -import javafx.event.EventType; - -/** - * - * @author dsmyda - */ -public final class FocusChangeEvent extends EventObject{ - - private final EventType type; - private final StoredTag focused; - - public FocusChangeEvent(Object source, EventType type, StoredTag focused) { - super(source); - this.type = type; - this.focused = focused; - } - - public EventType getType() { - return type; - } - - public StoredTag getNode() { - return focused; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java deleted file mode 100755 index 6699e57002..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/FocusChangeListener.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.contentviewers.imagetagging; - -import java.util.EventListener; - -/** - * - * @author dsmyda - */ -@FunctionalInterface -public interface FocusChangeListener extends EventListener{ - - void focusChanged(FocusChangeEvent event); -} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java new file mode 100755 index 0000000000..e54d365331 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java @@ -0,0 +1,343 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import com.sun.javafx.event.EventDispatchChainImpl; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javafx.collections.ListChangeListener; +import javafx.scene.Cursor; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import org.sleuthkit.autopsy.casemodule.services.applicationtags.ContentViewerTagManager.ContentViewerTag; + +/** + * A tagged region displayed over an image. This class contains a "physical tag" + * and 8 edit "handles". The physical tag is a plain old rectangle that defines + * the tag boundaries. The edit handles serve two purposes. One is to represent + * selection. All 8 edit handles will become visible overtop the physical tag + * when the user clicks on the rectangle. The other purpose is to allow the user to edit + * and manipulate the physical tag boundaries (hence the name, edit handle). + * This class should be treated as a logical image tag. + */ +public final class ImageTag extends Group { + + // Used to tell the 8 edit handles to hide if this tag is no longer selected + private final EventDispatchChainImpl ALL_CHILDREN; + + //Notifies listeners that the user has editted the tag boundaries + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + + //The underlying presistent tag details that this image tag originates from + private final ContentViewerTag appTag; + + public ImageTag(ContentViewerTag appTag, ImageView image) { + ALL_CHILDREN = new EventDispatchChainImpl(); + this.appTag = appTag; + + this.getChildren().addListener((ListChangeListener) change -> { + change.next(); + change.getAddedSubList().forEach((node) -> ALL_CHILDREN.append(node.getEventDispatcher())); + }); + + ImageTagRegion details = appTag.getDetails(); + PhysicalTag physicalTag = new PhysicalTag(details); + + //Defines the max allowable boundary that a user may drag any given handle. + Boundary dragBoundary = (x, y) -> { + double boundingX = image.getX(); + double boundingY = image.getY(); + double width = image.getImage().getWidth(); + double height = image.getImage().getHeight(); + + return x > boundingX + details.getStrokeThickness() / 2 + && x < boundingX + width - details.getStrokeThickness() / 2 + && y > boundingY + details.getStrokeThickness() / 2 + && y < boundingY + height - details.getStrokeThickness() / 2; + }; + + EditHandle bottomLeft = new EditHandle(physicalTag) + .setPosition(Position.bottom(), Position.left()) + .setDrag(dragBoundary, Drag.bottom(), Drag.left()); + + EditHandle bottomRight = new EditHandle(physicalTag) + .setPosition(Position.bottom(), Position.right()) + .setDrag(dragBoundary, Drag.bottom(), Drag.right()); + + EditHandle topLeft = new EditHandle(physicalTag) + .setPosition(Position.top(), Position.left()) + .setDrag(dragBoundary, Drag.top(), Drag.left()); + + EditHandle topRight = new EditHandle(physicalTag) + .setPosition(Position.top(), Position.right()) + .setDrag(dragBoundary, Drag.top(), Drag.right()); + + EditHandle bottomMiddle = new EditHandle(physicalTag) + .setPosition(Position.bottom(), Position.xMiddle()) + .setDrag(dragBoundary, Drag.bottom()); + + EditHandle topMiddle = new EditHandle(physicalTag) + .setPosition(Position.top(), Position.xMiddle()) + .setDrag(dragBoundary, Drag.top()); + + EditHandle rightMiddle = new EditHandle(physicalTag) + .setPosition(Position.right(), Position.yMiddle()) + .setDrag(dragBoundary, Drag.right()); + + EditHandle leftMiddle = new EditHandle(physicalTag) + .setPosition(Position.left(), Position.yMiddle()) + .setDrag(dragBoundary, Drag.left()); + + //The "logical" tag is the Group + this.getChildren().addAll(physicalTag, bottomLeft, bottomRight, topLeft, + topRight, bottomMiddle, topMiddle, rightMiddle, leftMiddle); + + Tooltip.install(this, new Tooltip(appTag.getContentTag() + .getName().getDisplayName())); + + this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); + this.addEventHandler(ImageTagControls.FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); + } + + /** + * Add a new listener for edit events. These events are generated when a + * user drags on one of the edit "knobs" of the tag. + * + * @param listener + */ + public void subscribeToEditEvents(PropertyChangeListener listener) { + pcs.addPropertyChangeListener(listener); + } + + /** + * Get the app tag that this class represents. + * + * @return + */ + public ContentViewerTag getContentViewerTag() { + return appTag; + } + + /** + * Plain old rectangle that represents an unselected Image Tag + */ + class PhysicalTag extends Rectangle { + + public PhysicalTag(ImageTagRegion details) { + this.setStroke(Color.RED); + this.setFill(Color.RED.deriveColor(0, 0, 0, 0)); + this.setStrokeWidth(details.getStrokeThickness()); + + setX(details.getX()); + setY(details.getY()); + setWidth(details.getWidth()); + setHeight(details.getHeight()); + + this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> this.setOpacity(1)); + this.addEventHandler(ImageTagControls.FOCUSED, event -> this.setOpacity(0.5)); + } + + /** + * Builds a portable description of the tag region. + * + * @return + */ + public ImageTagRegion getState() { + return new ImageTagRegion() + .setX(this.getX()) + .setY(this.getY()) + .setWidth(this.getWidth()) + .setHeight(this.getHeight()) + .setStrokeThickness(this.getStrokeWidth()); + } + } + + /** + * Draggable "knob" used to manipulate the physical tag boundaries. + */ + class EditHandle extends Circle { + + private final PhysicalTag parent; + + public EditHandle(PhysicalTag parent) { + this.setVisible(false); + + //Hide when the tag is not selected. + this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> this.setVisible(false)); + this.addEventHandler(ImageTagControls.FOCUSED, event -> this.setVisible(true)); + + this.setRadius(parent.getStrokeWidth()); + this.setFill(parent.getStroke()); + + this.setOnDragDetected(event -> { + this.getParent().setCursor(Cursor.CLOSED_HAND); + }); + + this.setOnMouseReleased(event -> { + this.getParent().setCursor(Cursor.DEFAULT); + pcs.firePropertyChange(new PropertyChangeEvent(this, "Tag Edit", null, parent.getState())); + }); + + this.parent = parent; + } + + /** + * Sets the positioning of this edit handle on the physical tag. + * + * @param vals + * @return + */ + public EditHandle setPosition(Position... vals) { + for (Position pos : vals) { + parent.widthProperty().addListener((obs, oldVal, newVal) -> pos.set(parent, this)); + parent.heightProperty().addListener((obs, oldVal, newVal) -> pos.set(parent, this)); + pos.set(parent, this); + } + return this; + } + + /** + * Sets the drag capabilities for manipulating the physical tag. + * + * @param bounds + * @param vals + * @return + */ + public EditHandle setDrag(Boundary bounds, Drag... vals) { + this.setOnMouseDragged((event) -> { + for (Drag drag : vals) { + drag.perform(parent, event, bounds); + } + }); + return this; + } + } + + /** + * Position strategies for "sticking" to a location on the physical tag when + * it is resized. + */ + static interface Position { + + void set(PhysicalTag parent, Circle knob); + + static Position left() { + return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty()); + } + + static Position right() { + return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth())); + } + + static Position top() { + return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty()); + } + + static Position bottom() { + return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight())); + } + + static Position xMiddle() { + return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth() / 2)); + } + + static Position yMiddle() { + return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight() / 2)); + } + } + + /** + * Defines the bounding box for which dragging is allowable. + */ + @FunctionalInterface + static interface Boundary { + + boolean isPointInBounds(double x, double y); + } + + /** + * Drag strategies for manipulating the physical tag from a given side of + * the rectangle. + */ + static interface Drag { + + void perform(PhysicalTag parent, MouseEvent event, Boundary b); + + static Drag bottom() { + return (parent, event, bounds) -> { + if (!bounds.isPointInBounds(event.getX(), event.getY())) { + return; + } + + double deltaY = event.getY() - parent.getY(); + if (deltaY > 0) { + parent.setHeight(deltaY); + } + }; + } + + static Drag top() { + return (parent, event, bounds) -> { + if (!bounds.isPointInBounds(event.getX(), event.getY())) { + return; + } + + double deltaY = parent.getY() + parent.getHeight() - event.getY(); + if (deltaY < parent.getY() + parent.getHeight() && deltaY > 0) { + parent.setHeight(deltaY); + parent.setY(event.getY()); + } + }; + } + + static Drag left() { + return (parent, event, bounds) -> { + if (!bounds.isPointInBounds(event.getX(), event.getY())) { + return; + } + + double deltaX = parent.getX() + parent.getWidth() - event.getX(); + if (deltaX < parent.getX() + parent.getWidth() && deltaX > 0) { + parent.setWidth(deltaX); + parent.setX(event.getX()); + } + }; + } + + static Drag right() { + return (parent, event, bounds) -> { + if (!bounds.isPointInBounds(event.getX(), event.getY())) { + return; + } + + double deltaX = event.getX() - parent.getX(); + if (deltaX > 0) { + parent.setWidth(deltaX); + } + }; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ControlType.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagControls.java similarity index 84% rename from Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ControlType.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagControls.java index 6e0c6a2493..d4ee799262 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ControlType.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagControls.java @@ -22,11 +22,10 @@ import javafx.event.Event; import javafx.event.EventType; /** - * - * @author dsmyda + * Focus events for ImageTags to consume. These events trigger selection behavior + * on ImageTags and are originated from the ImageTagsGroup class. */ -public class ControlType { +public class ImageTagControls { public static final EventType NOT_FOCUSED = new EventType<>("NOT_FOCUSED"); public static final EventType FOCUSED = new EventType<>("FOCUSED"); - public static final EventType DELETE = new EventType<>("DELETE"); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java index b0c114ada5..4df4328b2d 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java @@ -16,11 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.contentviewers.imagetagging; -import java.util.ArrayList; -import java.util.Collection; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import javafx.event.EventHandler; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; @@ -28,7 +28,10 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; /** - * Creates image tags. This tool can be treated like any other JavaFX node. + * Creates image tags. This class attaches itself to a source image, waiting + * for mouse press, mouse drag, and mouse release events. Upon a mouse release + * event, any listeners are updated with the portable description of the new tag + * boundaries (ImageTagRegion). */ public final class ImageTagCreator extends Rectangle { @@ -39,12 +42,18 @@ public final class ImageTagCreator extends Rectangle { //a good balance between visual acuity and loss of selection at the borders //of the image. private double lineThicknessAsPercent = 1.5; - private final double minArea; - private final Collection listeners; + private final double minArea; + //Used to update listeners of the new tag boundaries + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private final EventHandler mousePressed; private final EventHandler mouseDragged; private final EventHandler mouseReleased; + + //Handles the unregistering this ImageTagCreator from mouse press, mouse drag, + //and mouse release events of the source image. + private final Runnable disconnect; /** * Adds tagging support to an image, where the 'tag' rectangle will be the @@ -53,8 +62,6 @@ public final class ImageTagCreator extends Rectangle { * @param image Image to tag */ public ImageTagCreator(ImageView image) { - listeners = new ArrayList<>(); - setStroke(Color.RED); setFill(Color.RED.deriveColor(0, 0, 0, 0)); @@ -65,7 +72,7 @@ public final class ImageTagCreator extends Rectangle { setStrokeWidth(lineThicknessPixels); minArea = lineThicknessPixels * lineThicknessPixels; setVisible(false); - + this.mousePressed = (MouseEvent event) -> { if (event.isSecondaryButtonDown()) { return; @@ -79,9 +86,9 @@ public final class ImageTagCreator extends Rectangle { setX(rectangleOriginX); setY(rectangleOriginY); }; - + image.addEventHandler(MouseEvent.MOUSE_PRESSED, this.mousePressed); - + this.mouseDragged = (MouseEvent event) -> { if (event.isSecondaryButtonDown()) { return; @@ -111,45 +118,55 @@ public final class ImageTagCreator extends Rectangle { } setHeight(Math.abs(offsetY)); }; - + image.addEventHandler(MouseEvent.MOUSE_DRAGGED, this.mouseDragged); - + this.mouseReleased = event -> { - if ((this.getWidth() - this.getStrokeWidth()) * - (this.getHeight() - this.getStrokeWidth()) <= minArea) { + //Reject any drags that are too small to count as a meaningful tag. + //Meaningful is described as having an area that is visible that is + //not consumed by the thickness of the stroke. + if ((this.getWidth() - this.getStrokeWidth()) + * (this.getHeight() - this.getStrokeWidth()) <= minArea) { defaultSettings(); return; - } - - //Notify listeners - StoredTagEvent newTagEvent = new StoredTagEvent(this, new StoredTag(image, this.getX(), this.getY(), - this.getX() + this.getWidth(), this.getY() + this.getHeight())); - - listeners.forEach((listener) -> { - listener.newTagEvent(newTagEvent); - }); - - defaultSettings(); + } + + this.pcs.firePropertyChange(new PropertyChangeEvent(this, "New Tag", + null, new ImageTagRegion() + .setX(this.getX()) + .setY(this.getY()) + .setWidth(this.getWidth()) + .setHeight(this.getHeight()) + .setStrokeThickness(lineThicknessPixels))); }; - + image.addEventHandler(MouseEvent.MOUSE_RELEASED, this.mouseReleased); - this.addEventHandler(ControlType.NOT_FOCUSED, (event) -> { + + //Used to remove itself from mouse events on the source image + disconnect = () -> { defaultSettings(); image.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleased); image.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragged); image.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressed); - }); - - this.addEventHandler(ControlType.DELETE, event -> { - defaultSettings(); - image.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleased); - image.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragged); - image.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressed); - }); + }; } - - public void addNewTagListener(StoredTagListener listener) { - listeners.add(listener); + + /** + * Registers a PCL for new tag events. Listeners are updated with a portable + * description (ImageTagRegion) of the new tag, which represent the + * rectangle boundaries. + * + * @param listener + */ + public void addNewTagListener(PropertyChangeListener listener) { + this.pcs.addPropertyChangeListener(listener); + } + + /** + * Removes itself from mouse events on the source image. + */ + public void disconnect() { + this.disconnect.run(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagRegion.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagRegion.java new file mode 100755 index 0000000000..b4ba2035b0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagRegion.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +/** + * Bean representation of an image tag. This class is used for storage and + * retrieval of ImageTags from the case database. + */ +public class ImageTagRegion { + + /** + * These fields will be serialized and stored in the case database by the + * ContentViewerTagManager. + */ + private double x; + private double y; + private double width; + private double height; + + private double strokeThickness; + + public ImageTagRegion setStrokeThickness(double thickness) { + this.strokeThickness = thickness; + return this; + } + + public ImageTagRegion setX(double x) { + this.x = x; + return this; + } + + public ImageTagRegion setWidth(double width) { + this.width = width; + return this; + } + + public ImageTagRegion setY(double y) { + this.y = y; + return this; + } + + public ImageTagRegion setHeight(double height) { + this.height = height; + return this; + } + + public double getX() { + return x; + } + + public double getWidth() { + return width; + } + + public double getY() { + return y; + } + + public double getHeight() { + return height; + } + + public double getStrokeThickness() { + return strokeThickness; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java new file mode 100755 index 0000000000..86257e55f8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java @@ -0,0 +1,124 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers.imagetagging; + +import com.sun.javafx.event.EventDispatchChainImpl; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javafx.event.Event; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; + +/** + * Manages the focus and z-ordering of ImageTags. Only one image tag may be + * selected at a time. Image tags show their 8 edit "handles" upon selection + * (see ImageTag class for more details) and get the highest z-ordering to make + * editing easier. This class is responsible for setting and dropping focus as + * the user navigates from tag to tag. The ImageTag is treated as a logical + * unit, however it's underlying representation consists of the physical + * rectangle and the 8 edit handles. JavaFX will report selection on the Node + * level (so either the Rectangle, or a singe edit handle), which makes keeping + * the entire image tag in focus a non-trivial problem. + */ +public final class ImageTagsGroup extends Group { + + private final EventDispatchChainImpl NO_OP_CHAIN = new EventDispatchChainImpl(); + private final PropertyChangeSupport pcl = new PropertyChangeSupport(this); + + private volatile ImageTag currentFocus; + + public ImageTagsGroup(Node backDrop) { + + //Reset focus of current selection if the back drop has focus. + backDrop.setOnMousePressed((mouseEvent) -> { + if (currentFocus != null) { + currentFocus.getEventDispatcher().dispatchEvent( + new Event(ImageTagControls.NOT_FOCUSED), NO_OP_CHAIN); + currentFocus = null; + } + + this.pcl.firePropertyChange(new PropertyChangeEvent(this, + ImageTagControls.NOT_FOCUSED.getName(), currentFocus, null)); + }); + + //Set the focus of selected tag + this.addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> { + if (!e.isPrimaryButtonDown()) { + return; + } + + //Pull out the logical image tag that this node is associated with + Node topLevelChild = e.getPickResult().getIntersectedNode(); + while (!this.getChildren().contains(topLevelChild)) { + topLevelChild = topLevelChild.getParent(); + } + + requestFocus((ImageTag) topLevelChild); + }); + } + + /** + * Subscribe to focus change events on Image tags. + * + * @param fcl PCL to be notified which Image tag has been selected. + */ + public void addFocusChangeListener(PropertyChangeListener fcl) { + this.pcl.addPropertyChangeListener(fcl); + } + + /** + * Get the image tag that current has focus. + * + * @return ImageTag instance or null if no tag is in focus. + */ + public ImageTag getFocus() { + return currentFocus; + } + + /** + * Notifies the logical image tag that it is no longer in focus. + * + * @param n + */ + private void resetFocus(ImageTag n) { + n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.NOT_FOCUSED), NO_OP_CHAIN); + this.pcl.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.NOT_FOCUSED.getName(), n, null)); + } + + /** + * Notifies the logical image that it is in focus. + * + * @param n + */ + private void requestFocus(ImageTag n) { + if (currentFocus == n) { + return; + } else if (currentFocus != null && currentFocus != n) { + resetFocus(currentFocus); + } + + n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.FOCUSED), NO_OP_CHAIN); + this.pcl.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.FOCUSED.getName(), currentFocus, n)); + + currentFocus = n; + n.toFront(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java deleted file mode 100755 index 1a97918dee..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTag.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.contentviewers.imagetagging; - -import com.sun.javafx.event.EventDispatchChainImpl; -import javafx.collections.ListChangeListener; -import javafx.scene.Cursor; -import javafx.scene.Group; -import javafx.scene.Node; -import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; -import javafx.scene.paint.Color; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Rectangle; -import org.sleuthkit.datamodel.ContentTag; - -/** - * - * @author dsmyda - */ -public final class StoredTag extends Group { - - private final EventDispatchChainImpl ALL_CHILDREN; - private final PhysicalTag physicalTag; - - public StoredTag(ImageView image, double x, double y, double x1, double y1) { - ALL_CHILDREN = new EventDispatchChainImpl(); - - this.getChildren().addListener((ListChangeListener) change -> { - change.next(); - change.getAddedSubList().forEach((node) -> ALL_CHILDREN.append(node.getEventDispatcher())); - }); - - double min = Math.min(image.getImage().getWidth(), image.getImage().getHeight()); - double lineThicknessPixels = min * 1.5 / 100.0; - physicalTag = new PhysicalTag(image); - physicalTag.setStrokeWidth(lineThicknessPixels); - - EditHandle bottomLeft = new EditHandle(); - bottomLeft.setPosition(Position.bottom(), Position.left()); - bottomLeft.setDrag(Drag.bottom(), Drag.left()); - - EditHandle bottomRight = new EditHandle(); - bottomRight.setPosition(Position.bottom(), Position.right()); - bottomRight.setDrag(Drag.bottom(), Drag.right()); - - EditHandle topLeft = new EditHandle(); - topLeft.setPosition(Position.top(), Position.left()); - topLeft.setDrag(Drag.top(), Drag.left()); - - EditHandle topRight = new EditHandle(); - topRight.setPosition(Position.top(), Position.right()); - topRight.setDrag(Drag.top(), Drag.right()); - - EditHandle bottomMiddle = new EditHandle(); - bottomMiddle.setPosition(Position.bottom(), Position.xMiddle()); - bottomMiddle.setDrag(Drag.bottom()); - - EditHandle topMiddle = new EditHandle(); - topMiddle.setPosition(Position.top(), Position.xMiddle()); - topMiddle.setDrag(Drag.top()); - - EditHandle rightMiddle = new EditHandle(); - rightMiddle.setPosition(Position.right(), Position.yMiddle()); - rightMiddle.setDrag(Drag.right()); - - EditHandle leftMiddle = new EditHandle(); - leftMiddle.setPosition(Position.left(), Position.yMiddle()); - leftMiddle.setDrag(Drag.left()); - - this.getChildren().addAll(physicalTag, bottomLeft, bottomRight, topLeft, - topRight, bottomMiddle, topMiddle, rightMiddle, leftMiddle); - - //Position the tag on the image. The edit knobs will be notified of - //the new coords and adjust themselves. - physicalTag.setX(x); - physicalTag.setY(y); - physicalTag.setWidth(x1 - x); - physicalTag.setHeight(y1 - y); - - this.focusedProperty().addListener((ov, oldV, newV) -> { - if(!newV) { - System.out.println("NOT FOCUSED"); - } else { - System.out.println("GAINED FOCUS"); - } - }); - - this.addEventHandler(ControlType.NOT_FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); - this.addEventHandler(ControlType.FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); - this.addEventHandler(ControlType.DELETE, event -> ALL_CHILDREN.dispatchEvent(event)); - } - - public void addContentTag(ContentTag t) { - physicalTag.linkContentTag(t); - } - - public ContentTag getContentTag() { - return physicalTag.getContentTag(); - } - - class PhysicalTag extends Rectangle { - - private final ImageView image; - private ContentTag tag; - - public PhysicalTag(ImageView image) { - this.setStroke(Color.RED); - this.setFill(Color.RED.deriveColor(0, 0, 0, 0)); - - this.addEventHandler(ControlType.NOT_FOCUSED, event -> this.setOpacity(1)); - this.addEventHandler(ControlType.FOCUSED, event -> this.setOpacity(0.5)); - - this.addEventHandler(ControlType.DELETE, event -> { - this.setVisible(false); - //TODO - delete tag from persistent storage here. - }); - - this.image = image; - } - - private void save() { - //TODO - persist tag - } - - public void linkContentTag(ContentTag t) { - tag = t; - } - - public ContentTag getContentTag() { - return tag; - } - - private ImageView getUnderlyingImage() { - return image; - } - - private class ImageRegion { - - } - } - - class EditHandle extends Circle { - - public EditHandle() { - super(physicalTag.getStrokeWidth(), physicalTag.getStroke()); - this.setVisible(false); - - //Manipulate the parent rectangle when this knob is dragged. - this.addEventHandler(ControlType.NOT_FOCUSED, event -> this.setVisible(false)); - this.addEventHandler(ControlType.FOCUSED, event -> this.setVisible(true)); - this.addEventHandler(ControlType.DELETE, event -> this.setVisible(false)); - this.setOnDragDetected(event -> this.getParent().setCursor(Cursor.CLOSED_HAND)); - this.setOnMouseReleased(event -> { - this.getParent().setCursor(Cursor.DEFAULT); - physicalTag.save(); - }); - } - - public void setPosition(Position... vals) { - for (Position pos : vals) { - physicalTag.widthProperty().addListener((obs, oldVal, newVal) -> pos.set(physicalTag, this)); - physicalTag.heightProperty().addListener((obs, oldVal, newVal) -> pos.set(physicalTag, this)); - } - } - - public void setDrag(Drag... vals) { - this.setOnMouseDragged((event) -> { - for (Drag drag : vals) { - drag.perform(physicalTag, event, physicalTag.getUnderlyingImage()); - } - }); - } - } - - static interface Position { - - void set(Rectangle parent, Circle knob); - - static Position left() { - return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty()); - } - - static Position right() { - return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth())); - } - - static Position top() { - return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty()); - } - - static Position bottom() { - return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight())); - } - - static Position xMiddle() { - return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth() / 2)); - } - - static Position yMiddle() { - return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight() / 2)); - } - } - - static interface Drag { - - void perform(Rectangle parent, MouseEvent event, ImageView image); - - static Drag bottom() { - return (parent, event, image) -> { - double deltaY = event.getY() - parent.getY(); - if (deltaY > 0 && event.getY() - < image.getY() + image.getImage().getHeight() - - parent.getStrokeWidth() / 2) { - parent.setHeight(deltaY); - } - }; - } - - static Drag top() { - return (parent, event, image) -> { - double deltaY = parent.getY() + parent.getHeight() - event.getY(); - if (deltaY < parent.getY() + parent.getHeight() && event.getY() - > image.getY() + parent.getStrokeWidth() / 2 && deltaY > 0) { - parent.setHeight(deltaY); - parent.setY(event.getY()); - } - }; - } - - static Drag left() { - return (parent, event, image) -> { - double deltaX = parent.getX() + parent.getWidth() - event.getX(); - if (deltaX < parent.getX() + parent.getWidth() - && event.getX() > image.getX() + parent.getStrokeWidth() / 2 - && deltaX > 0) { - parent.setWidth(deltaX); - parent.setX(event.getX()); - } - }; - } - - static Drag right() { - return (parent, event, image) -> { - double deltaX = event.getX() - parent.getX(); - if (deltaX > 0 && event.getX() < image.getX() - + image.getImage().getWidth() - parent.getStrokeWidth() / 2) { - parent.setWidth(deltaX); - } - }; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java deleted file mode 100755 index e1cba2d7f6..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sleuthkit.autopsy.contentviewers.imagetagging; - - -import java.util.EventObject; - -/** - * - * @author dsmyda - */ -public final class StoredTagEvent extends EventObject { - private final StoredTag tag; - - public StoredTagEvent(Object source, StoredTag tag) { - super(source); - this.tag = tag; - } - - public StoredTag getTag() { - return tag; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java deleted file mode 100755 index a727cc4f53..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/StoredTagListener.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sleuthkit.autopsy.contentviewers.imagetagging; - - -import java.util.EventListener; - -/** - * - * @author dsmyda - */ -@FunctionalInterface -public interface StoredTagListener extends EventListener { - - void newTagEvent(StoredTagEvent tagEvent); -} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java deleted file mode 100755 index 2a00df6f7a..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/TopLevelTagsGroup.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.contentviewers.imagetagging; - -import com.sun.javafx.event.EventDispatchChainImpl; -import java.util.ArrayList; -import java.util.Collection; -import javafx.event.Event; -import javafx.scene.Group; -import javafx.scene.Node; -import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; - -/** - * Top level group containing Image and all existing tags. - */ -public final class TopLevelTagsGroup extends Group { - - private final EventDispatchChainImpl NO_OP_CHAIN = new EventDispatchChainImpl(); - private final Collection listeners; - - private Node lastFocus; - private final ImageView baseImage; - - public TopLevelTagsGroup(ImageView image) { - super(image); - baseImage = image; - listeners = new ArrayList<>(); - - //Manage focus, such that only one child can be set at a time. - this.addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> { - if (!e.isPrimaryButtonDown()) { - return; - } - - Node topLevelChild = e.getPickResult().getIntersectedNode(); - while (!this.getChildren().contains(topLevelChild)) { - topLevelChild = topLevelChild.getParent(); - } - - requestFocus(topLevelChild); - }); - } - - public void addFocusChangeListener(FocusChangeListener fcl) { - listeners.add(fcl); - } - - private void resetFocus(Node n) { - n.getEventDispatcher().dispatchEvent(new Event(ControlType.NOT_FOCUSED), NO_OP_CHAIN); - if(n instanceof StoredTag) { - listeners.forEach((listener) -> { - listener.focusChanged(new FocusChangeEvent(this, ControlType.NOT_FOCUSED, (StoredTag) n)); - }); - } - } - - public void deleteNode(Node n) { - if (lastFocus == n) { - resetFocus(n); - lastFocus = null; - } - n.getEventDispatcher().dispatchEvent(new Event(ControlType.DELETE), NO_OP_CHAIN); - this.getChildren().remove(n); - } - - public void requestFocus(Node n) { - if (lastFocus == n) { - return; - } else if (lastFocus != null && lastFocus != n) { - resetFocus(lastFocus); - } - - n.getEventDispatcher().dispatchEvent(new Event(ControlType.FOCUSED), NO_OP_CHAIN); - if(n instanceof StoredTag) { - listeners.forEach((listener) -> { - listener.focusChanged(new FocusChangeEvent(this, ControlType.FOCUSED, (StoredTag) n)); - }); - } - - if (n != baseImage) { - n.toFront(); - } - - lastFocus = n; - } -} From fd894a71033fc3b2db2ad28a8a7421a0d3689051 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 13:39:37 -0400 Subject: [PATCH 031/453] 5123 change spot I missed show to download change --- Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java index 250f2ba3cd..b53b1dc258 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java @@ -168,7 +168,7 @@ final class HtmlPanel extends javax.swing.JPanel { * Refresh the panel to reflect the current show/hide images setting. */ @Messages({ - "HtmlPanel_showImagesToggleButton_show=Show Images", + "HtmlPanel_showImagesToggleButton_show=Download Images", "HtmlPanel_showImagesToggleButton_hide=Hide Images", "Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.",}) private void refresh() { From 476b407c3c71eed72baab2e29efd10abc8245c0c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 13:47:19 -0400 Subject: [PATCH 032/453] 5123 update to merged properties file --- .../sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 7600a880a2..7b8fa6ad6b 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -22,7 +22,7 @@ GstVideoPanel.cannotProcFile.err=The media player cannot process this file. GstVideoPanel.noOpenCase.errMsg=No open case available. Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML. HtmlPanel_showImagesToggleButton_hide=Hide Images -HtmlPanel_showImagesToggleButton_show=Show Images +HtmlPanel_showImagesToggleButton_show=Download Images HtmlViewer_file_error=This file is missing or unreadable. MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled. GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player. From 057fa9d48cd0e8acf0830f873d6cdae05c856bd5 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 3 Jun 2019 14:01:23 -0400 Subject: [PATCH 033/453] Bug fixes, comment fixes, more code clean up --- .../ContentViewerTagManager.java | 32 +++++++++---------- .../autopsy/contentviewers/Bundle.properties | 1 - .../contentviewers/Bundle.properties-MERGED | 1 - .../contentviewers/MediaViewImagePanel.form | 11 ------- .../contentviewers/MediaViewImagePanel.java | 32 +++++++------------ .../contentviewers/imagetagging/ImageTag.java | 8 ++--- .../imagetagging/ImageTagsGroup.java | 10 +++--- 7 files changed, 35 insertions(+), 60 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java index 33b50bab07..105a643331 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java @@ -37,7 +37,7 @@ import org.sleuthkit.datamodel.TskCoreException; public class ContentViewerTagManager { //Used to convert Java beans into the physical representation that will be stored - //in the database (for now, JSON was chosen). + //in the database. private static final ObjectMapper SERIALIZER = new ObjectMapper(); public static final String TABLE_NAME = "beta_tag_app_data"; @@ -53,20 +53,19 @@ public class ContentViewerTagManager { /** * Creates and saves a new ContentViewerTag in the case database. The * generic tag data instance T will be automatically serialized into a - * storable format and persisted. + * storable format. * - * @param Generic java bean class type that will be serialized into a - * storable format for persistence + * @param Generic class type that will be serialized into a + * storable format for persistence. * @param contentTag ContentTag that this ContentViewerTag is associated - * with (1:1) + * with (1:1). * @param tagDataBean Data instance that contains the tag information to be * persisted. * @return An instance of a ContentViewerTag of type T, which contains all * the stored information. * - * @throws SerializationException Thrown if the tag data bean instance T - * could not be serialized into a storable format for persistence in the - * case database. + * @throws SerializationException Thrown if the tag data instance T + * could not be serialized into a storable format. * @throws TskCoreException Thrown if this operation did not successfully * persist in the case database. * @throws NoCurrentCaseException Thrown if invocation of this method occurs @@ -89,15 +88,14 @@ public class ContentViewerTagManager { * Updates the ContentViewerTag instance with the new tag data T and * persists the changes to the case database. * - * @param Generic java bean class type that will be serialized into a - * storable format for persistence + * @param Generic class type that will be serialized into a + * storable format. * @param oldTag ContentViewerTag instance to be updated * @param tagDataBean Data instance that contains the updated information to * be persisted. * - * @throws SerializationException Thrown if the tag data bean instance T - * could not be serialized into a storable format for persistence in the - * case database. + * @throws SerializationException Thrown if the tag data instance T + * could not be serialized into a storable format. * @throws TskCoreException Thrown if this operation did not successfully * persist in the case database. * @throws NoCurrentCaseException Thrown if invocation of this method occurs @@ -119,15 +117,15 @@ public class ContentViewerTagManager { /** * Retrieves a ContentViewerTag instance that is associated with the - * specified ContentTag. The Java bean class that represents the technical + * specified ContentTag. The Java class T that represents the technical * details of the tag should be passed so that automatic binding can take * place. * - * @param Java bean class type that will be instantiated and filled in + * @param Generic class type that will be instantiated and filled in * with data. * @param contentTag ContentTag that this ContentViewerTag is associated * with (1:1) - * @param clazz Java bean class that will be instantiated and filled in with + * @param clazz Generic class that will be instantiated and filled in with * data. * @return ContentViewerTag with an instance of T as a member variable or * null if the content tag does not have an associated ContentViewerTag of @@ -187,7 +185,7 @@ public class ContentViewerTagManager { * for the tag id, the attached Content tag object, and the Java bean * instance that describes the technical details for reconstructing the tag. * - * @param Java bean class type that will be instantiated and filled in + * @param Generic class type that will be instantiated and filled in * with data. */ public static class ContentViewerTag { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index a8b66e1c31..0f5f1e841e 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -91,5 +91,4 @@ MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors MediaViewImagePanel.deleteTagButton.text=Delete Tag MediaViewImagePanel.createTagButton.text=Create Tag -MediaViewImagePanel.jMenu1.text=jMenu1 MediaViewImagePanel.showTagsButton.text=Hide Tags diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 44cae34fd9..f04db7f709 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -153,7 +153,6 @@ MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors MediaViewImagePanel.deleteTagButton.text=Delete Tag MediaViewImagePanel.createTagButton.text=Create Tag -MediaViewImagePanel.jMenu1.text=jMenu1 MediaViewImagePanel.showTagsButton.text=Hide Tags # {0} - tableName SQLiteViewer.readTable.errorText=Error getting rows for table: {0} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form index 447957d66c..befc6fe7a6 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form @@ -2,17 +2,6 @@
    - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index 5d6fa7e193..bb921a2974 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -196,7 +196,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan if (imageTagCreator != null) { imageTagCreator.disconnect(); } - showTagsButton.setText("Hide Tags"); + showTagsButton.setText(DisplayOptions.HIDE_TAGS.getName()); + showTagsButton.setEnabled(true); + createTagButton.setEnabled(true); + deleteTagButton.setEnabled(false); scrollPane.setContent(null); scrollPane.setContent(masterGroup); }); @@ -347,20 +350,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan * Build the image tag, add an edit event call back to persist all * edits made on this image tag instance. */ - ImageTag imageTag = new ImageTag(contentViewerTag, fxImageView); - imageTag.subscribeToEditEvents((event) -> { - try { - scrollPane.setCursor(Cursor.WAIT); - ImageTagRegion newRegion = (ImageTagRegion) event.getNewValue(); - ContentViewerTagManager.updateTag(contentViewerTag, newRegion); - scrollPane.setCursor(Cursor.DEFAULT); - } catch (SerializationException | NoCurrentCaseException | TskCoreException ex) { - LOGGER.log(Level.WARNING, "Could not save edit for image tag in case db", ex); //NON-NLS - //TODO - pop dialog - } - }); - - tagsGroup.getChildren().add(imageTag); + tagsGroup.getChildren().add(buildImageTag(contentViewerTag)); }); return tagsGroup; @@ -407,7 +397,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan // //GEN-BEGIN:initComponents private void initComponents() { - jMenu1 = new javax.swing.JMenu(); jPopupMenu1 = new javax.swing.JPopupMenu(); jPopupMenu2 = new javax.swing.JPopupMenu(); toolbar = new javax.swing.JToolBar(); @@ -428,8 +417,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan jSeparator3 = new javax.swing.JToolBar.Separator(); showTagsButton = new javax.swing.JButton(); - org.openide.awt.Mnemonics.setLocalizedText(jMenu1, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.jMenu1.text")); // NOI18N - setBackground(new java.awt.Color(0, 0, 0)); addComponentListener(new java.awt.event.ComponentAdapter() { public void componentResized(java.awt.event.ComponentEvent evt) { @@ -661,6 +648,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan //Persist and build image tag Platform.runLater(() -> { try { + scrollPane.setCursor(Cursor.WAIT); ContentViewerTag contentViewerTag = storeImageTag(tag, result); ImageTag imageTag = buildImageTag(contentViewerTag); tagsGroup.getChildren().add(imageTag); @@ -668,6 +656,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan LOGGER.log(Level.WARNING, "Could not save new image tag in case db", ex); //NON-NLS //TODO pop dialog } + + scrollPane.setCursor(Cursor.DEFAULT); }); }); @@ -683,7 +673,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan }//GEN-LAST:event_createTagButtonActionPerformed /** - * + * Creates an ImageTag instance from the ContentViewerTag. + * * @param contentViewerTag * @return */ @@ -696,11 +687,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan scrollPane.setCursor(Cursor.WAIT); ImageTagRegion newRegion = (ImageTagRegion) edit.getNewValue(); ContentViewerTagManager.updateTag(contentViewerTag, newRegion); - scrollPane.setCursor(Cursor.DEFAULT); } catch (SerializationException | TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Could not save edit for image tag in case db", ex); //NON-NLS //TODO pop dialog } + scrollPane.setCursor(Cursor.DEFAULT); }); return imageTag; } @@ -773,7 +764,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private javax.swing.JButton deleteTagButton; private javax.swing.Box.Filler filler1; private javax.swing.Box.Filler filler2; - private javax.swing.JMenu jMenu1; private javax.swing.JPopupMenu jPopupMenu1; private javax.swing.JPopupMenu jPopupMenu2; private javax.swing.JToolBar.Separator jSeparator1; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java index e54d365331..80b1cbe07f 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java @@ -54,16 +54,16 @@ public final class ImageTag extends Group { //The underlying presistent tag details that this image tag originates from private final ContentViewerTag appTag; - public ImageTag(ContentViewerTag appTag, ImageView image) { + public ImageTag(ContentViewerTag contentViewerTag, ImageView image) { ALL_CHILDREN = new EventDispatchChainImpl(); - this.appTag = appTag; + this.appTag = contentViewerTag; this.getChildren().addListener((ListChangeListener) change -> { change.next(); change.getAddedSubList().forEach((node) -> ALL_CHILDREN.append(node.getEventDispatcher())); }); - ImageTagRegion details = appTag.getDetails(); + ImageTagRegion details = contentViewerTag.getDetails(); PhysicalTag physicalTag = new PhysicalTag(details); //Defines the max allowable boundary that a user may drag any given handle. @@ -115,7 +115,7 @@ public final class ImageTag extends Group { this.getChildren().addAll(physicalTag, bottomLeft, bottomRight, topLeft, topRight, bottomMiddle, topMiddle, rightMiddle, leftMiddle); - Tooltip.install(this, new Tooltip(appTag.getContentTag() + Tooltip.install(this, new Tooltip(contentViewerTag.getContentTag() .getName().getDisplayName())); this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event)); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java index 86257e55f8..828b083d80 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java @@ -41,7 +41,7 @@ import javafx.scene.input.MouseEvent; public final class ImageTagsGroup extends Group { private final EventDispatchChainImpl NO_OP_CHAIN = new EventDispatchChainImpl(); - private final PropertyChangeSupport pcl = new PropertyChangeSupport(this); + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private volatile ImageTag currentFocus; @@ -55,7 +55,7 @@ public final class ImageTagsGroup extends Group { currentFocus = null; } - this.pcl.firePropertyChange(new PropertyChangeEvent(this, + this.pcs.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.NOT_FOCUSED.getName(), currentFocus, null)); }); @@ -81,7 +81,7 @@ public final class ImageTagsGroup extends Group { * @param fcl PCL to be notified which Image tag has been selected. */ public void addFocusChangeListener(PropertyChangeListener fcl) { - this.pcl.addPropertyChangeListener(fcl); + this.pcs.addPropertyChangeListener(fcl); } /** @@ -100,7 +100,7 @@ public final class ImageTagsGroup extends Group { */ private void resetFocus(ImageTag n) { n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.NOT_FOCUSED), NO_OP_CHAIN); - this.pcl.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.NOT_FOCUSED.getName(), n, null)); + this.pcs.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.NOT_FOCUSED.getName(), n, null)); } /** @@ -116,7 +116,7 @@ public final class ImageTagsGroup extends Group { } n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.FOCUSED), NO_OP_CHAIN); - this.pcl.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.FOCUSED.getName(), currentFocus, n)); + this.pcs.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.FOCUSED.getName(), currentFocus, n)); currentFocus = n; n.toFront(); From 64e7e03ec5b7f9c41e35a1eb16fb31096cc75b31 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 3 Jun 2019 14:04:38 -0400 Subject: [PATCH 034/453] Fixed generic type T warning --- .../services/applicationtags/ContentViewerTagManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java index 105a643331..33d7987d8a 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/applicationtags/ContentViewerTagManager.java @@ -139,7 +139,7 @@ public class ContentViewerTagManager { public static ContentViewerTag getTag(ContentTag contentTag, Class clazz) throws TskCoreException, NoCurrentCaseException { try { String selectTemplateInstance = String.format(SELECT_TAG_DATA, contentTag.getId()); - final ArrayList result = new ArrayList<>(); + final ArrayList> result = new ArrayList<>(); Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager() .select(selectTemplateInstance, (ResultSet rs) -> { try { From 6a6a282e66acece02fa28477177550258be79da4 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 3 Jun 2019 14:21:56 -0400 Subject: [PATCH 035/453] Added more space for text in buttons --- .../contentviewers/MediaViewImagePanel.form | 21 +++++++++++++++++++ .../contentviewers/MediaViewImagePanel.java | 13 ++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form index befc6fe7a6..fe7843bf7a 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form @@ -250,6 +250,16 @@ + + + + + + + + + + @@ -265,6 +275,17 @@ + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index bb921a2974..1e039d614a 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -268,7 +268,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan tagsGroup = buildImageTagsGroup(contentViewerTags); } catch (TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Could not retrieve image tags for file in case db", ex); //NON-NLS - //TODO - pop dialog } scrollPane.setContent(masterGroup); } else { @@ -539,6 +538,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan deleteTagButton.setFocusable(false); deleteTagButton.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); deleteTagButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + deleteTagButton.setMaximumSize(new java.awt.Dimension(61, 21)); + deleteTagButton.setMinimumSize(new java.awt.Dimension(61, 21)); + deleteTagButton.setPreferredSize(new java.awt.Dimension(61, 21)); + deleteTagButton.setRequestFocusEnabled(false); deleteTagButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); deleteTagButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -551,6 +554,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan org.openide.awt.Mnemonics.setLocalizedText(showTagsButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.showTagsButton.text")); // NOI18N showTagsButton.setFocusable(false); showTagsButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + showTagsButton.setMaximumSize(new java.awt.Dimension(61, 21)); + showTagsButton.setMinimumSize(new java.awt.Dimension(61, 21)); + showTagsButton.setOpaque(false); + showTagsButton.setPreferredSize(new java.awt.Dimension(61, 21)); + showTagsButton.setRolloverEnabled(false); showTagsButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); showTagsButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -618,7 +626,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan tagsGroup.getChildren().remove(tagInFocus); } catch (TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Could not delete image tag in case db", ex); //NON-NLS - //TODO pop dialog } scrollPane.setCursor(Cursor.DEFAULT); @@ -654,7 +661,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan tagsGroup.getChildren().add(imageTag); } catch (TskCoreException | SerializationException | NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Could not save new image tag in case db", ex); //NON-NLS - //TODO pop dialog } scrollPane.setCursor(Cursor.DEFAULT); @@ -689,7 +695,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan ContentViewerTagManager.updateTag(contentViewerTag, newRegion); } catch (SerializationException | TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Could not save edit for image tag in case db", ex); //NON-NLS - //TODO pop dialog } scrollPane.setCursor(Cursor.DEFAULT); }); From 37deadd747fd98876312f2e0d8bed949c8195154 Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 3 Jun 2019 15:01:40 -0400 Subject: [PATCH 036/453] Use UTF8 encoding for report content. --- Core/src/org/sleuthkit/autopsy/report/FileReportText.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/FileReportText.java b/Core/src/org/sleuthkit/autopsy/report/FileReportText.java index 9d3de2c945..ea0f3452c2 100644 --- a/Core/src/org/sleuthkit/autopsy/report/FileReportText.java +++ b/Core/src/org/sleuthkit/autopsy/report/FileReportText.java @@ -24,6 +24,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -63,9 +64,12 @@ class FileReportText implements FileReportModule { public void startReport(String baseReportDir) { this.reportPath = baseReportDir + FILE_NAME; try { - out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.reportPath))); + out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.reportPath), StandardCharsets.UTF_8)); + out.write('\ufeff'); } catch (FileNotFoundException ex) { logger.log(Level.WARNING, "Failed to create report text file", ex); //NON-NLS + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed to write BOM to report text file", ex); //NON-NLS } } From 2cdf0dd66d6328934e61eb87b6e1647ddd9f37e6 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 3 Jun 2019 15:45:00 -0400 Subject: [PATCH 037/453] 4963: DataResultTable is slow to load. - load S C & O columns data in background for AbstractContentNode and all it's derived classes. --- .../relationships/MessageNode.java | 11 -- .../datamodel/AbstractAbstractFileNode.java | 94 +++--------- .../datamodel/AbstractContentNode.java | 145 ++++++++++++++++++ .../datamodel/BlackboardArtifactNode.java | 84 ++++++---- .../datamodel/Bundle.properties-MERGED | 1 + .../autopsy/datamodel/GetSCOTask.java | 24 +-- 6 files changed, 234 insertions(+), 125 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index e6ac3f507f..2f4ce1157a 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -81,17 +81,6 @@ final class MessageNode extends BlackboardArtifactNode { sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS - addScoreProperty(sheetSet, tags); - - CorrelationAttributeInstance correlationAttribute = null; - if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { - correlationAttribute = getCorrelationAttributeInstance(); - } - addCommentProperty(sheetSet, tags, correlationAttribute); - - if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { - addCountProperty(sheetSet, correlationAttribute); - } final BlackboardArtifact artifact = getArtifact(); BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index d6b589f977..e8d71caae7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.datamodel; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; @@ -27,8 +26,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.stream.Collectors; import org.apache.commons.io.FilenameUtils; @@ -65,6 +62,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -82,10 +80,6 @@ public abstract class AbstractAbstractFileNode extends A private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED); - // pool to run long running translation and getSCO tasks in backgound - private static final ExecutorService translationSCOPool; - private static final Integer MAX_POOL_SIZE = 10; - /** * @param abstractFile file to wrap */ @@ -102,7 +96,7 @@ public abstract class AbstractAbstractFileNode extends A } if (UserPreferences.displayTranslatedFileNames()) { - AbstractAbstractFileNode.translationSCOPool.submit(new TranslationTask( + backgroundTasksPool.submit(new TranslationTask( new WeakReference<>(this), weakPcl)); } @@ -110,14 +104,7 @@ public abstract class AbstractAbstractFileNode extends A // or when tags are added. Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); } - - static { - //Initialize this pool only once! This will be used by every instance of AAFN - //to do their heavy duty SCO column and translation updates. - translationSCOPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, - new ThreadFactoryBuilder().setNameFormat("translation-and-sco-task-thread-%d").build()); - } - + /** * The finalizer removes event listeners as the BlackboardArtifactNode is * being garbage collected. Yes, we know that finalizers are considered to @@ -138,17 +125,7 @@ public abstract class AbstractAbstractFileNode extends A Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); } - /** - * Event signals to indicate the background tasks have completed processing. - * Currently, we have one property task in the background: - * - * 1) Retreiving the translation of the file name - */ - enum NodeSpecificEvents { - TRANSLATION_AVAILABLE, - SCO_AVAILABLE - } - + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); @@ -192,7 +169,7 @@ public abstract class AbstractAbstractFileNode extends A } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) { ContentTagAddedEvent event = (ContentTagAddedEvent) evt; if (event.getAddedTag().getContent().equals(content)) { - List tags = getContentTagsFromDatabase(); + List tags = this.getAllTagsFromDatabase(); Pair scorePropAndDescr = getScorePropertyAndDescription(tags); Score value = scorePropAndDescr.getLeft(); String descr = scorePropAndDescr.getRight(); @@ -204,7 +181,7 @@ public abstract class AbstractAbstractFileNode extends A } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) { ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt; if (event.getDeletedTagInfo().getContentID() == content.getId()) { - List tags = getContentTagsFromDatabase(); + List tags = getAllTagsFromDatabase(); Pair scorePropAndDescr = getScorePropertyAndDescription(tags); Score value = scorePropAndDescr.getLeft(); String descr = scorePropAndDescr.getRight(); @@ -216,7 +193,7 @@ public abstract class AbstractAbstractFileNode extends A } else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) { CommentChangedEvent event = (CommentChangedEvent) evt; if (event.getContentID() == content.getId()) { - List tags = getContentTagsFromDatabase(); + List tags = getAllTagsFromDatabase(); CorrelationAttributeInstance attribute = getCorrelationAttributeInstance(); updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(tags, attribute))); } @@ -249,38 +226,6 @@ public abstract class AbstractAbstractFileNode extends A */ private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); - /** - * Updates the values of the properties in the current property sheet with - * the new properties being passed in. Only if that property exists in the - * current sheet will it be applied. That way, we allow for subclasses to - * add their own (or omit some!) properties and we will not accidentally - * disrupt their UI. - * - * Race condition if not synchronized. Only one update should be applied at - * a time. - * - * @param newProps New file property instances to be updated in the current - * sheet. - */ - private synchronized void updateSheet(NodeProperty... newProps) { - //Refresh ONLY those properties in the sheet currently. Subclasses may have - //only added a subset of our properties or their own props.s - Sheet visibleSheet = this.getSheet(); - Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES); - Property[] visibleProps = visibleSheetSet.getProperties(); - for (NodeProperty newProp : newProps) { - for (int i = 0; i < visibleProps.length; i++) { - if (visibleProps[i].getName().equals(newProp.getName())) { - visibleProps[i] = newProp; - } - } - } - visibleSheetSet.put(visibleProps); - visibleSheet.put(visibleSheetSet); - //setSheet() will notify Netbeans to update this node in the UI. - this.setSheet(visibleSheet); - } - /* * This is called when the node is first initialized. Any new updates or * changes happen by directly manipulating the sheet. That means we can fire @@ -389,7 +334,7 @@ public abstract class AbstractAbstractFileNode extends A // Get the SCO columns data in a background task - AbstractAbstractFileNode.translationSCOPool.submit(new GetSCOTask( + backgroundTasksPool.submit(new GetSCOTask( new WeakReference<>(this), weakPcl)); properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content))); @@ -453,7 +398,8 @@ public abstract class AbstractAbstractFileNode extends A "AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated", "# {0} - occuranceCount", "AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"}) - Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description(); try { @@ -480,7 +426,8 @@ public abstract class AbstractAbstractFileNode extends A "AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.", "AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.", "AbstractAbstractFileNode.createSheet.noScore.description=No score"}) - Pair getScorePropertyAndDescription(List tags) { + @Override + protected Pair getScorePropertyAndDescription(List tags) { DataResultViewerTable.Score score = DataResultViewerTable.Score.NO_SCORE; String description = Bundle.AbstractAbstractFileNode_createSheet_noScore_description(); if (content.getKnown() == TskData.FileKnown.BAD) { @@ -498,7 +445,7 @@ public abstract class AbstractAbstractFileNode extends A if (!tags.isEmpty() && (score == DataResultViewerTable.Score.NO_SCORE || score == DataResultViewerTable.Score.INTERESTING_SCORE)) { score = DataResultViewerTable.Score.INTERESTING_SCORE; description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description(); - for (ContentTag tag : tags) { + for (Tag tag : tags) { if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) { score = DataResultViewerTable.Score.NOTABLE_SCORE; description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description(); @@ -511,11 +458,12 @@ public abstract class AbstractAbstractFileNode extends A @NbBundle.Messages({ "AbstractAbstractFileNode.createSheet.comment.displayName=C"}) - HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + @Override + protected HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { DataResultViewerTable.HasCommentStatus status = !tags.isEmpty() ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT; - for (ContentTag tag : tags) { + for (Tag tag : tags) { if (!StringUtils.isBlank(tag.getComment())) { //if the tag is null or empty or contains just white space it will indicate there is not a comment status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT; @@ -583,7 +531,13 @@ public abstract class AbstractAbstractFileNode extends A return tags; } - CorrelationAttributeInstance getCorrelationAttributeInstance() { + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(getContentTagsFromDatabase()); + } + + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { CorrelationAttributeInstance attribute = null; if (EamDb.isEnabled() && !UserPreferences.hideCentralRepoCommentsAndOccurrences()) { attribute = EamArtifactUtil.getInstanceFromContent(content); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 4f6f2e5f47..7a2577e407 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -18,20 +18,30 @@ */ package org.sleuthkit.autopsy.datamodel; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.logging.Level; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Children; +import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskException; @@ -50,6 +60,34 @@ public abstract class AbstractContentNode extends ContentNode T content; private static final Logger logger = Logger.getLogger(AbstractContentNode.class.getName()); + /** + * A pool of background tasks to run any long computation needed to + * populate this node. + */ + static final ExecutorService backgroundTasksPool; + static final Integer MAX_POOL_SIZE = 10; + + @NbBundle.Messages("AbstractContentNode.nodescription=no description") + private static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); + + + /** + * Event signals to indicate the background tasks have completed processing. + * Currently, we have one property task in the background: + * + * 1) Retrieving the translation of the file name + */ + enum NodeSpecificEvents { + TRANSLATION_AVAILABLE, + SCO_AVAILABLE + } + + static { + //Initialize this pool only once! This will be used by every instance of AAFN + //to do their heavy duty SCO column and translation updates. + backgroundTasksPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, + new ThreadFactoryBuilder().setNameFormat("content-node-background-task-%d").build()); + } /** * Handles aspects that depend on the Content object * @@ -240,4 +278,111 @@ public abstract class AbstractContentNode extends ContentNode public int read(byte[] buf, long offset, long len) throws TskException { return content.read(buf, offset, len); } + + + /** + * Updates the values of the properties in the current property sheet with + * the new properties being passed in. Only if that property exists in the + * current sheet will it be applied. That way, we allow for subclasses to + * add their own (or omit some!) properties and we will not accidentally + * disrupt their UI. + * + * Race condition if not synchronized. Only one update should be applied at + * a time. + * + * @param newProps New file property instances to be updated in the current + * sheet. + */ + protected synchronized void updateSheet(NodeProperty... newProps) { + //Refresh ONLY those properties in the sheet currently. Subclasses may have + //only added a subset of our properties or their own props.s + Sheet visibleSheet = this.getSheet(); + Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES); + Property[] visibleProps = visibleSheetSet.getProperties(); + for (NodeProperty newProp : newProps) { + for (int i = 0; i < visibleProps.length; i++) { + if (visibleProps[i].getName().equals(newProp.getName())) { + visibleProps[i] = newProp; + } + } + } + visibleSheetSet.put(visibleProps); + visibleSheet.put(visibleSheetSet); + //setSheet() will notify Netbeans to update this node in the UI. + this.setSheet(visibleSheet); + } + + /** + * Reads and returns a list of all tags associated with this content node. + * + * This default implementation returns an empty list. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @return list of tags associated with the node. + */ + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + /** + * Returns correlation attribute instance for the underlying content of the node. + * + * This default implementation returns null. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @return correlation attribute instance for the underlying content of the node. + * + */ + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + /** + * Returns Score property for the node. + * + * This default implementation returns NO_SCORE. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @param tags list of tags. + * + * @return Score property for the underlying content of the node. + * + */ + protected Pair getScorePropertyAndDescription(List tags) { + + return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); + } + /** + * Returns comment property for the node. + * + * This default implementation returns NO_COMMENT. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @param tags list of tags + * @param attribute correlation attribute instance + * + * @return Comment property for the underlying content of the node. + * + */ + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + /** + * Returns occurrences/count property for the node. + * + * This default implementation returns -1. + * The derived classes should override with an implementation specific + * to the type of node. + * + * @param attribute correlation attribute instance + * + * @return count property for the underlying content of the node. + * + */ + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + return Pair.of(-1L, NO_DESCR); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 55e81acd17..1f871363f9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -37,6 +38,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; @@ -55,11 +57,13 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; @@ -96,7 +100,7 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties; - private final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); + protected final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); /* @@ -151,6 +155,19 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft())); + } + if (scoData.getComment() != null) { + updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, scoData.getComment())); + } + if (scoData.getCountAndDescription() != null && + !UserPreferences.hideCentralRepoCommentsAndOccurrences()) { + updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft())); + } + } } }; @@ -335,8 +352,6 @@ public class BlackboardArtifactNode extends AbstractContentNode tags = getAllTagsFromDatabase(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); if (sheetSet == null) { sheetSet = Sheet.createPropertiesSet(); @@ -351,17 +366,15 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), NO_DESCR, "")); + + // Get the SCO columns data in a background task + backgroundTasksPool.submit(new GetSCOTask( + new WeakReference<>(this), weakPcl)); + if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { try { BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); @@ -520,6 +533,7 @@ public class BlackboardArtifactNode extends AbstractContentNode getAllTagsFromDatabase() { List tags = new ArrayList<>(); try { @@ -569,6 +583,7 @@ public class BlackboardArtifactNode extends AbstractContentNode t.getName().getDisplayName()).collect(Collectors.joining(", ")))); } + @Override protected final CorrelationAttributeInstance getCorrelationAttributeInstance() { CorrelationAttributeInstance correlationAttribute = null; if (EamDb.isEnabled()) { @@ -581,16 +596,17 @@ public class BlackboardArtifactNode extends AbstractContentNode tags, CorrelationAttributeInstance attribute) { + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT; for (Tag tag : tags) { if (!StringUtils.isBlank(tag.getComment())) { @@ -609,17 +625,16 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, - status)); + return status; } - + /** * Used by (subclasses of) BlackboardArtifactNode to add the Score property * to their sheets. * - * @param sheetSet the modifiable Sheet.Set returned by - * Sheet.get(Sheet.PROPERTIES) * @param tags the list of tags associated with the file + * + * @return score property */ @NbBundle.Messages({"BlackboardArtifactNode.createSheet.score.name=S", "BlackboardArtifactNode.createSheet.score.displayName=S", @@ -628,8 +643,10 @@ public class BlackboardArtifactNode extends AbstractContentNode tags) { - Score score = Score.NO_SCORE; + + @Override + protected Pair getScorePropertyAndDescription(List tags) { + Score score = Score.NO_SCORE; String description = Bundle.BlackboardArtifactNode_createSheet_noScore_description(); if (associated instanceof AbstractFile) { if (((AbstractFile) associated).getKnown() == TskData.FileKnown.BAD) { @@ -673,9 +690,10 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), description, score)); + + return Pair.of(score, description); } - + @NbBundle.Messages({"BlackboardArtifactNode.createSheet.count.name=O", "BlackboardArtifactNode.createSheet.count.displayName=O", "BlackboardArtifactNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated", @@ -683,7 +701,8 @@ public class BlackboardArtifactNode extends AbstractContentNode getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting String description = Bundle.BlackboardArtifactNode_createSheet_count_noCentralRepo_description(); try { @@ -699,14 +718,13 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), description, count)); - } - + return Pair.of(count, description); + } + private void updateSheet() { this.setSheet(createSheet()); } - + private String getRootParentName() { String parentName = associated.getName(); Content parent = associated; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 1d8fb7eb14..8d2bd5952d 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -37,6 +37,7 @@ AbstractAbstractFileNode.tagsProperty.displayName=Tags AbstractAbstractFileNode.typeDirColLbl=Type(Dir) AbstractAbstractFileNode.typeMetaColLbl=Type(Meta) AbstractAbstractFileNode.useridColLbl=UserID +AbstractContentNode.nodescription=no description AbstractFsContentNode.noDesc.text=no description ArtifactStringContent.attrsTableHeader.sources=Source(s) ArtifactStringContent.attrsTableHeader.type=Type diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java index e8a7b5b1f5..4278f99d66 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java @@ -25,42 +25,44 @@ import java.util.List; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.events.AutopsyEvent; -import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.Tag; /** - * Background task to get Score, Comment and Occurrences values for a Abstract file node. + * Background task to get Score, Comment and Occurrences values for an + * Abstract content node. * */ class GetSCOTask implements Runnable { - private final WeakReference> weakNodeRef; + private final WeakReference> weakNodeRef; private final PropertyChangeListener listener; - public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { + public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { this.weakNodeRef = weakContentRef; this.listener = listener; } @Override public void run() { - AbstractAbstractFileNode fileNode = weakNodeRef.get(); + AbstractContentNode contentNode = weakNodeRef.get(); //Check for stale reference - if (fileNode == null) { + if (contentNode == null) { return; } // get the SCO column values - List tags = fileNode.getContentTagsFromDatabase(); - CorrelationAttributeInstance attribute = fileNode.getCorrelationAttributeInstance(); + List tags = contentNode.getAllTagsFromDatabase(); + CorrelationAttributeInstance attribute = contentNode.getCorrelationAttributeInstance(); SCOData scoData = new SCOData(); - scoData.setScoreAndDescription(fileNode.getScorePropertyAndDescription(tags)); - scoData.setComment(fileNode.getCommentProperty(tags, attribute)); + scoData.setScoreAndDescription(contentNode.getScorePropertyAndDescription(tags)); + scoData.setComment(contentNode.getCommentProperty(tags, attribute)); if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) { - scoData.setCountAndDescription(fileNode.getCountPropertyAndDescription(attribute)); + scoData.setCountAndDescription(contentNode.getCountPropertyAndDescription(attribute)); } + // signal SCO data is available. if (listener != null) { listener.propertyChange(new PropertyChangeEvent( AutopsyEvent.SourceType.LOCAL.toString(), From 2a37021c809da9f976a5cab47d158de350361f24 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 18:35:28 -0400 Subject: [PATCH 038/453] 5092 working changes to update status in table --- .../datasourcesummary/DataSourceBrowser.java | 42 ++++++++++++++++++- .../DataSourceSummaryDialog.java | 11 +++++ .../DataSourceSummaryNode.java | 6 ++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java index e946317da8..9c73ddadda 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java @@ -37,7 +37,10 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryNode.DataSourceSummaryEntryNode; import static javax.swing.SwingConstants.RIGHT; +import javax.swing.SwingUtilities; import javax.swing.table.TableColumn; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.IngestJobInfo; import org.sleuthkit.datamodel.SleuthkitCase; @@ -58,7 +61,7 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana private final Outline outline; private final org.openide.explorer.view.OutlineView outlineView; private final ExplorerManager explorerManager; - private final List dataSourceSummaryList; + private List dataSourceSummaryList; private final RightAlignedTableCellRenderer rightAlignedRenderer = new RightAlignedTableCellRenderer(); /** @@ -187,6 +190,43 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana return null; } + void refresh(long jobId, IngestJobInfo.IngestJobStatusType newStatus) { + Node[] selectedNodes = explorerManager.getSelectedNodes(); + //attempt to update the status of any datasources that had status which was STARTED + for (DataSourceSummary summary : dataSourceSummaryList) { + //attempt to update the status of any datasources that had status which was STARTED + //the database may not have been updated when this event is received so we need to manually update the UI + if (summary.getIngestStatus() == IngestJobInfo.IngestJobStatusType.STARTED && summary.getJobId() == jobId) { + System.out.println("UPDATING STATUS"); + summary.setStatus(newStatus); + } + } + SwingUtilities.invokeLater(() -> { + explorerManager.setRootContext(new DataSourceSummaryNode(dataSourceSummaryList)); + List nodesToSelect = new ArrayList<>(); + for (Node node : explorerManager.getRootContext().getChildren().getNodes()) { + if (node instanceof DataSourceSummaryEntryNode) { + //there should only be one selected node as multi-select is disabled + for (Node selectedNode : selectedNodes) { + if (((DataSourceSummaryEntryNode) node).getDataSource().equals(((DataSourceSummaryEntryNode) selectedNode).getDataSource())) { + System.out.println("NODE TO SELECT ADDED"); + nodesToSelect.add(node); + } + } + } + } + //reselect the previously selected Nodes + try { + explorerManager.setSelectedNodes(nodesToSelect.toArray(new Node[nodesToSelect.size()])); + } catch (PropertyVetoException ex) { + logger.log(Level.WARNING, "Error selecting previously selected nodes", ex); + } + revalidate(); + repaint(); + + }); + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java index 3fd86c8de8..91ad7da792 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.casemodule.datasourcesummary; import java.awt.Frame; +import java.beans.PropertyChangeEvent; import java.util.Map; import java.util.Observable; import java.util.Observer; @@ -26,7 +27,9 @@ import java.util.logging.Logger; import javax.swing.event.ListSelectionEvent; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel; +import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.IngestJobInfo; /** * Dialog for displaying the Data Sources Summary information @@ -73,6 +76,14 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser this.repaint(); } }); + IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.CANCELLED.toString())){ + dataSourcesPanel.refresh((long)evt.getOldValue(), null); + } + else if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.COMPLETED.toString())) { + dataSourcesPanel.refresh((long)evt.getOldValue(), IngestJobInfo.IngestJobStatusType.COMPLETED); + } + }); this.pack(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java index c9aef8c644..6c5a212d9d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.casemodule.datasourcesummary; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Observable; import java.util.Observer; import javax.swing.Action; @@ -125,7 +126,7 @@ final class DataSourceSummaryNode extends AbstractNode { DataSourceSummaryEntryNode(DataSourceSummary dataSourceSummary) { super(Children.LEAF); dataSource = dataSourceSummary.getDataSource(); - status = dataSourceSummary.getIngestStatus(); + status = dataSourceSummary.getIngestStatus() == null ? "" : dataSourceSummary.getIngestStatus().toString(); type = dataSourceSummary.getType(); filesCount = dataSourceSummary.getFilesCount(); resultsCount = dataSourceSummary.getResultsCount(); @@ -183,7 +184,8 @@ final class DataSourceSummaryNode extends AbstractNode { } @Override - public Action[] getActions(boolean context) { + public Action[] getActions(boolean context + ) { List actions = new ArrayList<>(); actions.add(new ViewDataSourceInContextAction()); return actions.toArray(new Action[actions.size()]); From fb17d40ca49cd1e1c0f8300f1a2d901684d4f632 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 3 Jun 2019 18:40:46 -0400 Subject: [PATCH 039/453] 5092 query for status --- .../datasourcesummary/DataSourceSummary.java | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java index a04604b3cd..a3de258fe8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java @@ -20,13 +20,10 @@ package org.sleuthkit.autopsy.casemodule.datasourcesummary; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.List; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.datamodel.CaseDbAccessManager; import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.IngestJobInfo; import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType; import org.sleuthkit.datamodel.TskCoreException; @@ -37,7 +34,8 @@ import org.sleuthkit.datamodel.TskCoreException; class DataSourceSummary { private final DataSource dataSource; - private String status = ""; + private IngestJobStatusType status = null; + private Long jobId = null; private final String type; private final long filesCount; private final long resultsCount; @@ -56,23 +54,29 @@ class DataSourceSummary { */ DataSourceSummary(DataSource dSource, String typeValue, Long numberOfFiles, Long numberOfResults, Long numberOfTags) { dataSource = dSource; - updateStatus(); + getStatusFromDatabase(); type = typeValue == null ? "" : typeValue; filesCount = numberOfFiles == null ? 0 : numberOfFiles; resultsCount = numberOfResults == null ? 0 : numberOfResults; tagsCount = numberOfTags == null ? 0 : numberOfTags; } - void updateStatus() { + private void getStatusFromDatabase() { try { IngestJobQueryCallback callback = new IngestJobQueryCallback(); - Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select("status_id FROM ingest_jobs WHERE obj_id=" + dataSource.getId(), callback); + Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select("ingest_job_id, status_id FROM ingest_jobs WHERE obj_id=" + dataSource.getId(), callback); status = callback.getStatus(); + jobId = callback.getJobId(); + System.out.println("NEW STATUS: " + status); } catch (NoCurrentCaseException | TskCoreException ex) { } } + void setStatus(IngestJobStatusType newStatus){ + status = newStatus; + } + /** * Get the DataSource * @@ -82,6 +86,10 @@ class DataSourceSummary { return dataSource; } + Long getJobId() { + return jobId; + } + /** * Get the type of this DataSource * @@ -109,10 +117,10 @@ class DataSourceSummary { return resultsCount; } - String getIngestStatus(){ + IngestJobStatusType getIngestStatus() { return status; } - + /** * Get the number of tagged content objects in this DataSource * @@ -124,16 +132,18 @@ class DataSourceSummary { class IngestJobQueryCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback { - IngestJobStatusType jobStatus = null; + private IngestJobStatusType jobStatus = null; + private Long ingestJobId = null; @Override public void process(ResultSet rs) { try { while (rs.next()) { IngestJobStatusType currentStatus = IngestJobStatusType.fromID(rs.getInt("status_id")); - if (currentStatus == IngestJobStatusType.COMPLETED) { + if (currentStatus == IngestJobStatusType.COMPLETED) { jobStatus = currentStatus; - } else if (currentStatus == IngestJobStatusType.STARTED) { + } else if (currentStatus == IngestJobStatusType.STARTED) { + ingestJobId = rs.getLong("ingest_job_id"); jobStatus = currentStatus; return; } @@ -143,12 +153,12 @@ class DataSourceSummary { } } - String getStatus() { - if (jobStatus == null) { - return ""; - } else { - return jobStatus.getDisplayName(); - } + IngestJobStatusType getStatus() { + return jobStatus; + } + + Long getJobId() { + return ingestJobId; } } } From ea6687c2c596939dc4e0bc40d022da947f91d543 Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 4 Jun 2019 08:20:40 -0400 Subject: [PATCH 040/453] Address Codacy comments. --- .../datamodel/AbstractContentNode.java | 53 ++++----------- .../autopsy/datamodel/ImageNode.java | 67 ++++++++++++++++++ .../autopsy/datamodel/VolumeNode.java | 68 +++++++++++++++++++ 3 files changed, 147 insertions(+), 41 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 7a2577e407..f90a15ac42 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -67,8 +67,11 @@ public abstract class AbstractContentNode extends ContentNode static final ExecutorService backgroundTasksPool; static final Integer MAX_POOL_SIZE = 10; + /** + * Default no description string + */ @NbBundle.Messages("AbstractContentNode.nodescription=no description") - private static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); + protected static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); /** @@ -315,74 +318,42 @@ public abstract class AbstractContentNode extends ContentNode /** * Reads and returns a list of all tags associated with this content node. * - * This default implementation returns an empty list. - * The derived classes should override with an implementation specific - * to the type of node. - * * @return list of tags associated with the node. */ - protected List getAllTagsFromDatabase() { - return new ArrayList<>(); - } + abstract protected List getAllTagsFromDatabase(); + /** * Returns correlation attribute instance for the underlying content of the node. * - * This default implementation returns null. - * The derived classes should override with an implementation specific - * to the type of node. - * * @return correlation attribute instance for the underlying content of the node. - * */ - protected CorrelationAttributeInstance getCorrelationAttributeInstance() { - return null; - } + abstract protected CorrelationAttributeInstance getCorrelationAttributeInstance(); /** * Returns Score property for the node. * - * This default implementation returns NO_SCORE. - * The derived classes should override with an implementation specific - * to the type of node. - * * @param tags list of tags. * * @return Score property for the underlying content of the node. - * */ - protected Pair getScorePropertyAndDescription(List tags) { - - return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); - } + abstract protected Pair getScorePropertyAndDescription(List tags); + /** * Returns comment property for the node. * - * This default implementation returns NO_COMMENT. - * The derived classes should override with an implementation specific - * to the type of node. - * * @param tags list of tags * @param attribute correlation attribute instance * * @return Comment property for the underlying content of the node. - * */ - protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { - return DataResultViewerTable.HasCommentStatus.NO_COMMENT; - } + abstract protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute); + /** * Returns occurrences/count property for the node. * - * This default implementation returns -1. - * The derived classes should override with an implementation specific - * to the type of node. - * * @param attribute correlation attribute instance * * @return count property for the underlying content of the node. - * */ - protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { - return Pair.of(-1L, NO_DESCR); - } + abstract protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index 584a405fcc..b2c80f1322 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -28,12 +28,15 @@ import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import javax.swing.Action; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; import org.sleuthkit.autopsy.directorytree.FileSearchAction; @@ -47,6 +50,7 @@ import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; +import org.sleuthkit.datamodel.Tag; /** * This class is used to represent the "Node" for the image. The children of @@ -250,4 +254,67 @@ public class ImageNode extends AbstractContentNode { } }; + /** + * Reads and returns a list of all tags associated with this content node. + * + * Null implementation of an abstract method. + * + * @return list of tags associated with the node. + */ + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + /** + * Returns correlation attribute instance for the underlying content of the node. + * + * Null implementation of an abstract method. + * + * @return correlation attribute instance for the underlying content of the node. + */ + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + /** + * Returns Score property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags. + * + * @return Score property for the underlying content of the node. + */ + @Override + protected Pair getScorePropertyAndDescription(List tags) { + return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); + } + /** + * Returns comment property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags + * @param attribute correlation attribute instance + * + * @return Comment property for the underlying content of the node. + */ + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + /** + * Returns occurrences/count property for the node. + * + * Null implementation of an abstract method. + * + * @param attribute correlation attribute instance + * + * @return count property for the underlying content of the node. + */ + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + return Pair.of(-1L, NO_DESCR); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index d25b50d59f..233d098071 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -25,10 +25,14 @@ import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import javax.swing.Action; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; @@ -39,6 +43,7 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.Volume; import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction; +import org.sleuthkit.datamodel.Tag; /** * This class is used to represent the "Node" for the volume. Its child is the @@ -212,4 +217,67 @@ public class VolumeNode extends AbstractContentNode { public String getItemType() { return DisplayableItemNode.FILE_PARENT_NODE_KEY; } + /** + * Reads and returns a list of all tags associated with this content node. + * + * Null implementation of an abstract method. + * + * @return list of tags associated with the node. + */ + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + /** + * Returns correlation attribute instance for the underlying content of the node. + * + * Null implementation of an abstract method. + * + * @return correlation attribute instance for the underlying content of the node. + */ + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + /** + * Returns Score property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags. + * + * @return Score property for the underlying content of the node. + */ + @Override + protected Pair getScorePropertyAndDescription(List tags) { + return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); + } + /** + * Returns comment property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags + * @param attribute correlation attribute instance + * + * @return Comment property for the underlying content of the node. + */ + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + /** + * Returns occurrences/count property for the node. + * + * Null implementation of an abstract method. + * + * @param attribute correlation attribute instance + * + * @return count property for the underlying content of the node. + */ + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { + return Pair.of(-1L, NO_DESCR); + } } From ed4da71c1aabd6d2c12e66de88671bab92b6d5a7 Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 4 Jun 2019 08:29:37 -0400 Subject: [PATCH 041/453] Remove unused import. --- .../src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index f90a15ac42..154ca68d50 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datamodel; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; From 1f1eb95699dc23193a1a39a9ae20694f69261366 Mon Sep 17 00:00:00 2001 From: Brian Kjersten Date: Tue, 4 Jun 2019 09:47:51 -0500 Subject: [PATCH 042/453] 5111 Clean up HTML output --- .../texttranslation/translators/GoogleTranslator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index 318191e713..3c761af6e1 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -34,6 +34,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.texttranslation.TextTranslator; import org.sleuthkit.autopsy.texttranslation.TranslationException; @@ -103,6 +104,10 @@ public final class GoogleTranslator implements TextTranslator { // put back the newlines translatedString = translatedString.replaceAll("
    ", "\n"); + + // With our current settings, Google Translate outputs HTML + // so we need to undo the escape characters. + translatedString = EscapeUtil.unEscapeHtml(translatedString); return translatedString; } catch (Throwable ex) { //Catching throwables because some of this Google Translate code throws throwables From ff9a26a447e0c6ef9b7a49f0f163b6c1760d0523 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 4 Jun 2019 12:38:39 -0400 Subject: [PATCH 043/453] Adding export CSV action --- .../datamodel/DataModelActionsFactory.java | 11 + .../autopsy/datamodel/DirectoryNode.java | 2 + .../sleuthkit/autopsy/datamodel/FileNode.java | 2 + .../autopsy/datamodel/LayoutFileNode.java | 2 + .../autopsy/datamodel/LocalFileNode.java | 2 + .../autopsy/datamodel/SlackFileNode.java | 2 + .../datamodel/SpecialDirectoryNode.java | 2 + .../directorytree/Bundle.properties-MERGED | 9 + .../directorytree/DataResultFilterNode.java | 1 + .../ExplorerNodeActionVisitor.java | 5 + .../directorytree/ExportCSVAction.java | 298 ++++++++++++++++++ .../keywordsearch/AdHocSearchFilterNode.java | 2 + 12 files changed, 338 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index 13a0753ca1..bdb4a394a8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -92,6 +93,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -119,6 +121,7 @@ public class DataModelActionsFactory { actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, slackFileNode)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -155,6 +158,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance());// + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -189,6 +193,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -223,6 +228,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -257,6 +263,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -291,6 +298,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -325,6 +333,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -379,6 +388,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -415,6 +425,7 @@ public class DataModelActionsFactory { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index 97b0fd8216..5a3476f896 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -28,6 +28,7 @@ import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -89,6 +90,7 @@ public class DirectoryNode extends AbstractFsContentNode { actionsList.add(ViewFileInTimelineAction.createViewFileAction(content)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(new RunIngestModulesAction(content)); actionsList.add(null); // creates a menu separator diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 1695b253b0..7b3a55c20b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -173,6 +174,7 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(null); // Creates an item separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // Creates an item separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index bfd8f7bd91..41ecad0340 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -29,6 +29,7 @@ import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -105,6 +106,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode { } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 6d64aeb621..4a62cd9e4f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -82,6 +83,7 @@ public class LocalFileNode extends AbstractAbstractFileNode { actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java index 49d1b9da54..0e7bce56cc 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java @@ -28,6 +28,7 @@ import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -85,6 +86,7 @@ public class SlackFileNode extends AbstractFsContentNode { NbBundle.getMessage(this.getClass(), "SlackFileNode.getActions.viewInNewWin.text"), this)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java index 47f616de12..ce5948ce68 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java @@ -25,6 +25,7 @@ import javax.swing.Action; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.FileSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; @@ -61,6 +62,7 @@ public abstract class SpecialDirectoryNode extends AbstractAbstractFileNode visit(final DerivedFile d) { List actionsList = new ArrayList<>(); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(AddContentTagAction.getInstance()); final Collection selectedFilesList = @@ -166,6 +169,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final LocalFile d) { List actionsList = new ArrayList<>(); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(AddContentTagAction.getInstance()); final Collection selectedFilesList = @@ -182,6 +186,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final org.sleuthkit.datamodel.File d) { List actionsList = new ArrayList<>(); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(AddContentTagAction.getInstance()); final Collection selectedFilesList = diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java new file mode 100644 index 0000000000..169701a55c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -0,0 +1,298 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this content except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.directorytree; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.netbeans.api.progress.ProgressHandle; +import org.openide.util.Cancellable; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Node; +import org.openide.nodes.Node.PropertySet; +import org.openide.nodes.Node.Property; + +/** + * Exports CSV version of result nodes to a location selected by the user. + */ +public final class ExportCSVAction extends AbstractAction { + + private Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); + + private String userDefinedExportPath; + + // This class is a singleton to support multi-selection of nodes, since + // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every + // node in the array returns a reference to the same action object from Node.getActions(boolean). + private static ExportCSVAction instance; + + public static synchronized ExportCSVAction getInstance() { + if (null == instance) { + instance = new ExportCSVAction(); + } + return instance; + } + + /** + * Private constructor for the action. + */ + @NbBundle.Messages({"ExportCSV.title.text=Export to CSV"}) + private ExportCSVAction() { + super(Bundle.ExportCSV_title_text()); + } + + /** + * Asks user to choose destination, then extracts content to destination + * (recursing on directories). + * + * @param e The action event. + */ + @NbBundle.Messages({ + "# {0} - Output file", + "ExportCSV.actionPerformed.fileExists=File {0} already exists", + "ExportCSV.actionPerformed.noCurrentCase=No open case available"}) + @Override + public void actionPerformed(ActionEvent e) { + + Collection selectedNodes = Utilities.actionsGlobalContext().lookupAll(Node.class); + if (selectedNodes.isEmpty()) { + return; + } + + Node parent = selectedNodes.iterator().next().getParentNode(); + if (parent != null) { + System.out.println("HTML name: " + parent.getHtmlDisplayName()); + System.out.println("Display name: " + parent.getDisplayName()); + System.out.println("Class: " + parent.getClass().getCanonicalName()); + for (PropertySet set : parent.getPropertySets()) { + for (Property prop : set.getProperties()) { + try { + System.out.println(" " + prop.getDisplayName() + " : " + prop.getValue().toString()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + } + + try { + String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_listing.csv", Calendar.getInstance()); + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows()))); + fileChooser.setSelectedFile(new File(fileName)); + fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); + + int returnVal = fileChooser.showSaveDialog((Component) e.getSource()); + if (returnVal == JFileChooser.APPROVE_OPTION) { + + File selectedFile = fileChooser.getSelectedFile(); + if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS + selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS + } + updateExportDirectory(selectedFile.getParent(), Case.getCurrentCaseThrows()); + + if (selectedFile.exists()) { + logger.log(Level.SEVERE, "File {0} already exists", selectedFile.getAbsolutePath()); //NON-NLS + MessageNotifyUtil.Message.info(Bundle.ExportCSV_actionPerformed_fileExists(selectedFile)); + return; + } + + CSVWriter writer = new CSVWriter(selectedNodes, selectedFile); + writer.execute(); + } + } catch (NoCurrentCaseException ex) { + JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExportCSV_actionPerformed_noCurrentCase()); + logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS + } + } + + /** + * Get the export directory path. + * + * @param openCase The current case. + * + * @return The export directory path. + */ + private String getExportDirectory(Case openCase) { + String caseExportPath = openCase.getExportDirectory(); + + if (userDefinedExportPath == null) { + return caseExportPath; + } + + File file = new File(userDefinedExportPath); + if (file.exists() == false || file.isDirectory() == false) { + return caseExportPath; + } + + return userDefinedExportPath; + } + + /** + * Update the default export directory. If the directory path matches the + * case export directory, then the directory used will always match the + * export directory of any given case. Otherwise, the path last used will be + * saved. + * + * @param exportPath The export path. + * @param openCase The current case. + */ + private void updateExportDirectory(String exportPath, Case openCase) { + if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) { + userDefinedExportPath = null; + } else { + userDefinedExportPath = exportPath; + } + } + + + /** + * Thread that does the actual extraction work + */ + private class CSVWriter extends SwingWorker { + + private final Logger logger = Logger.getLogger(CSVWriter.class.getName()); + private ProgressHandle progress; + + private final List nodesToExport; + private final File outputFile; + + /** + * Create an instance of the CSVWriter. + * + * @param extractionTasks List of file extraction tasks. + */ + CSVWriter(Collection selectedNodes, File outputFile) { + this.nodesToExport = new ArrayList<>(selectedNodes); + this.outputFile = outputFile; + } + + @NbBundle.Messages({"CSVWriter.progress.extracting=Exporting to CSV file", + "CSVWriter.progress.cancelling=Cancelling"}) + @Override + protected Object doInBackground() throws Exception { + if (nodesToExport.isEmpty()) { + return null; + } + + // Set up progress bar. + final String displayName = Bundle.CSVWriter_progress_extracting(); + progress = ProgressHandle.createHandle(displayName, new Cancellable() { + @Override + public boolean cancel() { + if (progress != null) { + progress.setDisplayName(Bundle.CSVWriter_progress_cancelling()); + } + return ExportCSVAction.CSVWriter.this.cancel(true); + } + }); + progress.start(); + progress.switchToIndeterminate(); + + try (BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))) { + // Write BOM + br.write('\ufeff'); + + // Write the header + List headers = new ArrayList<>(); + PropertySet[] sets = nodesToExport.get(0).getPropertySets(); + for(PropertySet set : sets) { + for (Property prop : set.getProperties()) { + headers.add(prop.getDisplayName()); + } + } + br.write(listToCSV(headers)); + + // Write each line + for (Node node : nodesToExport) { + if (this.isCancelled()) { + break; + } + + List values = new ArrayList<>(); + sets = node.getPropertySets(); + for(PropertySet set : sets) { + for (Property prop : set.getProperties()) { + values.add(prop.getValue().toString()); + } + } + br.write(listToCSV(values)); + } + } + + return null; + } + + private String listToCSV(List values) { + return "\"" + String.join("\",\"", values) + "\"\n"; + } + + @NbBundle.Messages({"CSVWriter.done.notifyMsg.error=Error exporting to CSV file", + "# {0} - Output file", + "CSVWriter.done.notifyMsg.success=Wrote to {0}"}) + @Override + protected void done() { + boolean msgDisplayed = false; + try { + super.get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS + MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_error()); + msgDisplayed = true; + } catch (java.util.concurrent.CancellationException ex) { + // catch and ignore if we were cancelled + } finally { + progress.finish(); + if (!this.isCancelled() && !msgDisplayed) { + MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_success(outputFile)); + } + } + } + } +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java index 57b84b11d6..cff13cc16e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java @@ -32,6 +32,7 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.lookup.ProxyLookup; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; @@ -163,6 +164,7 @@ class AdHocSearchFilterNode extends FilterNode { actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); actionsList.add(null); actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); From 20334eb7d5b81f3e41d54d89ee0d767a7cb0511b Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 4 Jun 2019 13:47:41 -0400 Subject: [PATCH 044/453] Use asynchronous node creation for directory tree to support view file in directory functionality. --- .../corecomponents/DataResultViewerTable.java | 30 ++++++++++--------- .../datamodel/AbstractContentNode.java | 2 +- .../autopsy/datamodel/DataSourcesNode.java | 2 +- .../datamodel/RootContentChildren.java | 4 ++- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index a5bbc461b8..47662e3c8f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -293,20 +293,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - /* - * If the given node is not null and has children, set it as the - * root context of the child OutlineView, otherwise make an - * "empty"node the root context. - * - * IMPORTANT NOTE: This is the first of many times where a - * getChildren call on the current root node causes all of the - * children of the root node to be created and defeats lazy child - * node creation, if it is enabled. It also likely leads to many - * case database round trips. - */ - if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { - this.rootNode = rootNode; - + if (rootNode != null) { /** * Check to see if we have previously created a paging support * class for this node. @@ -355,6 +342,21 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // No-op } }); + } + + /* + * If the given node is not null and has children, set it as the + * root context of the child OutlineView, otherwise make an + * "empty"node the root context. + * + * IMPORTANT NOTE: This is the first of many times where a + * getChildren call on the current root node causes all of the + * children of the root node to be created and defeats lazy child + * node creation, if it is enabled. It also likely leads to many + * case database round trips. + */ + if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { + this.rootNode = rootNode; this.getExplorerManager().setRootContext(this.rootNode); setupTable(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 4f6f2e5f47..29522bce4b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -66,7 +66,7 @@ public abstract class AbstractContentNode extends ContentNode * @param lookup The Lookup object for the node. */ AbstractContentNode(T content, Lookup lookup) { - super(Children.create(new ContentChildren(content), true), lookup); + super(Children.create(new ContentChildren(content), false), lookup); this.content = content; //super.setName(ContentUtils.getSystemName(content)); super.setName("content_" + Long.toString(content.getId())); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 1265db5658..5ab29a5376 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -57,7 +57,7 @@ public class DataSourcesNode extends DisplayableItemNode { } public DataSourcesNode(long dsObjId) { - super(Children.create(new DataSourcesNodeChildren(dsObjId), true), Lookups.singleton(NAME)); + super(Children.create(new DataSourcesNodeChildren(dsObjId), false), Lookups.singleton(NAME)); displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME; init(); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java index 66291955a8..c5d77692c8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java @@ -25,6 +25,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; /** * Children implementation for the root node of a ContentNode tree. Accepts a @@ -34,6 +35,7 @@ public class RootContentChildren extends Children.Keys { private final Collection contentKeys; private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor(); + private final CreateSleuthkitNodeVisitor createSleuthkitNodeVisitor = new CreateSleuthkitNodeVisitor(); /** * @param contentKeys root Content objects for the Node tree @@ -68,7 +70,7 @@ public class RootContentChildren extends Children.Keys { if (key instanceof AutopsyVisitableItem) { return new Node[] {((AutopsyVisitableItem)key).accept(createAutopsyNodeVisitor)}; } else { - return null; + return new Node[] {((SleuthkitVisitableItem)key).accept(createSleuthkitNodeVisitor)}; } } From 4a5fbe3aeb7b3ad27b8f3c063f8f0dbc5a91f2ec Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 4 Jun 2019 14:35:17 -0400 Subject: [PATCH 045/453] 5092 working updates for ingest status --- .../datasourcesummary/DataSourceBrowser.java | 13 +++------- .../datasourcesummary/DataSourceSummary.java | 26 +++++-------------- .../DataSourceSummaryDialog.java | 17 ++++++++---- .../DataSourceSummaryNode.java | 2 +- 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java index 9c73ddadda..3fcb50b02e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java @@ -190,15 +190,12 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana return null; } - void refresh(long jobId, IngestJobInfo.IngestJobStatusType newStatus) { + void refresh(content datasource, IngestJobInfo.IngestJobStatusType newStatus) { Node[] selectedNodes = explorerManager.getSelectedNodes(); //attempt to update the status of any datasources that had status which was STARTED for (DataSourceSummary summary : dataSourceSummaryList) { - //attempt to update the status of any datasources that had status which was STARTED - //the database may not have been updated when this event is received so we need to manually update the UI - if (summary.getIngestStatus() == IngestJobInfo.IngestJobStatusType.STARTED && summary.getJobId() == jobId) { - System.out.println("UPDATING STATUS"); - summary.setStatus(newStatus); + if (summary.getDataSource().equals(datasource)) { + summary.updateStatusFromDatabase(); } } SwingUtilities.invokeLater(() -> { @@ -209,7 +206,6 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana //there should only be one selected node as multi-select is disabled for (Node selectedNode : selectedNodes) { if (((DataSourceSummaryEntryNode) node).getDataSource().equals(((DataSourceSummaryEntryNode) selectedNode).getDataSource())) { - System.out.println("NODE TO SELECT ADDED"); nodesToSelect.add(node); } } @@ -221,10 +217,9 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana } catch (PropertyVetoException ex) { logger.log(Level.WARNING, "Error selecting previously selected nodes", ex); } - revalidate(); - repaint(); }); + } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java index a3de258fe8..071be6e3cb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java @@ -35,7 +35,6 @@ class DataSourceSummary { private final DataSource dataSource; private IngestJobStatusType status = null; - private Long jobId = null; private final String type; private final long filesCount; private final long resultsCount; @@ -54,29 +53,24 @@ class DataSourceSummary { */ DataSourceSummary(DataSource dSource, String typeValue, Long numberOfFiles, Long numberOfResults, Long numberOfTags) { dataSource = dSource; - getStatusFromDatabase(); + updateStatusFromDatabase(); type = typeValue == null ? "" : typeValue; filesCount = numberOfFiles == null ? 0 : numberOfFiles; resultsCount = numberOfResults == null ? 0 : numberOfResults; tagsCount = numberOfTags == null ? 0 : numberOfTags; } - private void getStatusFromDatabase() { + final void updateStatusFromDatabase() { try { IngestJobQueryCallback callback = new IngestJobQueryCallback(); - Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select("ingest_job_id, status_id FROM ingest_jobs WHERE obj_id=" + dataSource.getId(), callback); + Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select("status_id FROM ingest_jobs WHERE obj_id=" + dataSource.getId(), callback); status = callback.getStatus(); - jobId = callback.getJobId(); - System.out.println("NEW STATUS: " + status); + System.out.println("STATUS IN DB: " + status.getDisplayName()); } catch (NoCurrentCaseException | TskCoreException ex) { } } - - void setStatus(IngestJobStatusType newStatus){ - status = newStatus; - } - + /** * Get the DataSource * @@ -85,11 +79,7 @@ class DataSourceSummary { DataSource getDataSource() { return dataSource; } - - Long getJobId() { - return jobId; - } - + /** * Get the type of this DataSource * @@ -143,7 +133,6 @@ class DataSourceSummary { if (currentStatus == IngestJobStatusType.COMPLETED) { jobStatus = currentStatus; } else if (currentStatus == IngestJobStatusType.STARTED) { - ingestJobId = rs.getLong("ingest_job_id"); jobStatus = currentStatus; return; } @@ -157,8 +146,5 @@ class DataSourceSummary { return jobStatus; } - Long getJobId() { - return ingestJobId; - } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java index 91ad7da792..1ac367c990 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java @@ -28,6 +28,8 @@ import javax.swing.event.ListSelectionEvent; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent; +import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.IngestJobInfo; @@ -77,11 +79,16 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser } }); IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> { - if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.CANCELLED.toString())){ - dataSourcesPanel.refresh((long)evt.getOldValue(), null); - } - else if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.COMPLETED.toString())) { - dataSourcesPanel.refresh((long)evt.getOldValue(), IngestJobInfo.IngestJobStatusType.COMPLETED); + if (evt instanceof DataSourceAnalysisCompletedEvent) { + DataSourceAnalysisCompletedEvent dsEvent = (DataSourceAnalysisCompletedEvent) evt; + if (dsEvent.getResult() == Reason.ANALYSIS_COMPLETED) { + System.out.println("DS JOB ID: " + dsEvent.getDataSourceIngestJobId()); + System.out.println("JOB ID: " + dsEvent.getIngestJobId()); + dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), IngestJobInfo.IngestJobStatusType.COMPLETED); + + } else if (dsEvent.getResult() == Reason.ANALYSIS_CANCELLED) { + dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), null); + } } }); this.pack(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java index 6c5a212d9d..b833e39743 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java @@ -126,7 +126,7 @@ final class DataSourceSummaryNode extends AbstractNode { DataSourceSummaryEntryNode(DataSourceSummary dataSourceSummary) { super(Children.LEAF); dataSource = dataSourceSummary.getDataSource(); - status = dataSourceSummary.getIngestStatus() == null ? "" : dataSourceSummary.getIngestStatus().toString(); + status = dataSourceSummary.getIngestStatus() == null ? "" : dataSourceSummary.getIngestStatus().getDisplayName(); type = dataSourceSummary.getType(); filesCount = dataSourceSummary.getFilesCount(); resultsCount = dataSourceSummary.getResultsCount(); From d2154cd3809de5981c86e6af32ce29a188327fea Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 4 Jun 2019 14:37:17 -0400 Subject: [PATCH 046/453] 5092 fix accidently reverted check of datasource id --- .../casemodule/datasourcesummary/DataSourceBrowser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java index 3fcb50b02e..9f74f71e2e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java @@ -190,11 +190,11 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana return null; } - void refresh(content datasource, IngestJobInfo.IngestJobStatusType newStatus) { + void refresh(long dataSourceId, IngestJobInfo.IngestJobStatusType newStatus) { Node[] selectedNodes = explorerManager.getSelectedNodes(); //attempt to update the status of any datasources that had status which was STARTED for (DataSourceSummary summary : dataSourceSummaryList) { - if (summary.getDataSource().equals(datasource)) { + if (summary.getDataSource().getId() == dataSourceId) { summary.updateStatusFromDatabase(); } } From c1029fc154a5228395a5f9bbd4ec9191b0cbcf56 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 4 Jun 2019 14:41:20 -0400 Subject: [PATCH 047/453] 5092 reduce number of database queries necessary to display status --- .../casemodule/datasourcesummary/DataSourceBrowser.java | 2 +- .../casemodule/datasourcesummary/DataSourceSummary.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java index 9f74f71e2e..0df95720cf 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java @@ -195,7 +195,7 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana //attempt to update the status of any datasources that had status which was STARTED for (DataSourceSummary summary : dataSourceSummaryList) { if (summary.getDataSource().getId() == dataSourceId) { - summary.updateStatusFromDatabase(); + summary.setIngestStatus(newStatus); } } SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java index 071be6e3cb..010c7b17ee 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java @@ -53,14 +53,14 @@ class DataSourceSummary { */ DataSourceSummary(DataSource dSource, String typeValue, Long numberOfFiles, Long numberOfResults, Long numberOfTags) { dataSource = dSource; - updateStatusFromDatabase(); + getStatusFromDatabase(); type = typeValue == null ? "" : typeValue; filesCount = numberOfFiles == null ? 0 : numberOfFiles; resultsCount = numberOfResults == null ? 0 : numberOfResults; tagsCount = numberOfTags == null ? 0 : numberOfTags; } - final void updateStatusFromDatabase() { + private void getStatusFromDatabase() { try { IngestJobQueryCallback callback = new IngestJobQueryCallback(); Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select("status_id FROM ingest_jobs WHERE obj_id=" + dataSource.getId(), callback); @@ -80,6 +80,10 @@ class DataSourceSummary { return dataSource; } + void setIngestStatus(IngestJobStatusType ingestStatus){ + status = ingestStatus; + } + /** * Get the type of this DataSource * From c9c451945db17955c1e4aaa80714fccb833a74c5 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 4 Jun 2019 15:02:28 -0400 Subject: [PATCH 048/453] 5092 add comments to better document how ingest job status is updated --- .../datasourcesummary/DataSourceBrowser.java | 11 +++-- .../datasourcesummary/DataSourceSummary.java | 47 +++++++++++++++---- .../DataSourceSummaryDialog.java | 5 -- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java index 0df95720cf..f423b8a75a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java @@ -39,8 +39,6 @@ import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryNode. import static javax.swing.SwingConstants.RIGHT; import javax.swing.SwingUtilities; import javax.swing.table.TableColumn; -import org.openide.util.Exceptions; -import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.IngestJobInfo; import org.sleuthkit.datamodel.SleuthkitCase; @@ -61,7 +59,7 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana private final Outline outline; private final org.openide.explorer.view.OutlineView outlineView; private final ExplorerManager explorerManager; - private List dataSourceSummaryList; + private final List dataSourceSummaryList; private final RightAlignedTableCellRenderer rightAlignedRenderer = new RightAlignedTableCellRenderer(); /** @@ -190,6 +188,13 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana return null; } + /** + * Update the DataSourceBrowser to display up to date status information for + * the data sources. + * + * @param dataSourceId the ID of the data source which should be updated + * @param newStatus the new status which the data source should have + */ void refresh(long dataSourceId, IngestJobInfo.IngestJobStatusType newStatus) { Node[] selectedNodes = explorerManager.getSelectedNodes(); //attempt to update the status of any datasources that had status which was STARTED diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java index 010c7b17ee..fedb11884a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.casemodule.datasourcesummary; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.datamodel.CaseDbAccessManager; @@ -33,6 +35,8 @@ import org.sleuthkit.datamodel.TskCoreException; */ class DataSourceSummary { + private static final Logger logger = Logger.getLogger(DataSourceSummary.class.getName()); + private static final String INGEST_JOB_STATUS_QUERY = "status_id FROM ingest_jobs WHERE obj_id="; private final DataSource dataSource; private IngestJobStatusType status = null; private final String type; @@ -60,17 +64,19 @@ class DataSourceSummary { tagsCount = numberOfTags == null ? 0 : numberOfTags; } + /** + * Get the status of the ingest job from the case database + */ private void getStatusFromDatabase() { try { IngestJobQueryCallback callback = new IngestJobQueryCallback(); - Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select("status_id FROM ingest_jobs WHERE obj_id=" + dataSource.getId(), callback); + Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(INGEST_JOB_STATUS_QUERY + dataSource.getId(), callback); status = callback.getStatus(); - System.out.println("STATUS IN DB: " + status.getDisplayName()); } catch (NoCurrentCaseException | TskCoreException ex) { } } - + /** * Get the DataSource * @@ -79,11 +85,17 @@ class DataSourceSummary { DataSource getDataSource() { return dataSource; } - - void setIngestStatus(IngestJobStatusType ingestStatus){ + + /** + * Manually set the ingest job status + * + * @param ingestStatus the status which the ingest job should have + * currently, null to display empty string + */ + void setIngestStatus(IngestJobStatusType ingestStatus) { status = ingestStatus; } - + /** * Get the type of this DataSource * @@ -111,6 +123,12 @@ class DataSourceSummary { return resultsCount; } + /** + * Get the IngestJobStatusType associated with this data source. + * + * @return the IngestJobStatusType associated with this data source. Can be + * null if the IngestJobStatusType is not STARTED or COMPLETED. + */ IngestJobStatusType getIngestStatus() { return status; } @@ -124,28 +142,37 @@ class DataSourceSummary { return tagsCount; } + /** + * Callback to parse result set, getting the status to be associated with + * this data source + */ class IngestJobQueryCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback { private IngestJobStatusType jobStatus = null; - private Long ingestJobId = null; @Override public void process(ResultSet rs) { try { while (rs.next()) { IngestJobStatusType currentStatus = IngestJobStatusType.fromID(rs.getInt("status_id")); - if (currentStatus == IngestJobStatusType.COMPLETED) { + if (currentStatus == IngestJobStatusType.COMPLETED) { jobStatus = currentStatus; - } else if (currentStatus == IngestJobStatusType.STARTED) { + } else if (currentStatus == IngestJobStatusType.STARTED) { jobStatus = currentStatus; return; } } } catch (SQLException ex) { - System.out.println("EEEP"); + logger.log(Level.WARNING, "Error getting status for ingest job", ex); } } + /** + * Get the status which was determined for this callback + * + * @return the status of the data source which was + * queried for + */ IngestJobStatusType getStatus() { return jobStatus; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java index 1ac367c990..f322731f42 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java @@ -23,7 +23,6 @@ import java.beans.PropertyChangeEvent; import java.util.Map; import java.util.Observable; import java.util.Observer; -import java.util.logging.Logger; import javax.swing.event.ListSelectionEvent; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel; @@ -43,7 +42,6 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser private final DataSourceSummaryDetailsPanel detailsPanel; private final DataSourceBrowser dataSourcesPanel; private final IngestJobInfoPanel ingestHistoryPanel; - private static final Logger logger = Logger.getLogger(DataSourceSummaryDialog.class.getName()); /** * Creates new form DataSourceSummaryDialog for displaying a summary of the @@ -82,10 +80,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser if (evt instanceof DataSourceAnalysisCompletedEvent) { DataSourceAnalysisCompletedEvent dsEvent = (DataSourceAnalysisCompletedEvent) evt; if (dsEvent.getResult() == Reason.ANALYSIS_COMPLETED) { - System.out.println("DS JOB ID: " + dsEvent.getDataSourceIngestJobId()); - System.out.println("JOB ID: " + dsEvent.getIngestJobId()); dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), IngestJobInfo.IngestJobStatusType.COMPLETED); - } else if (dsEvent.getResult() == Reason.ANALYSIS_CANCELLED) { dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), null); } From 8637f1dfe0305ffb1e61609787d1d2cf140a55d4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 4 Jun 2019 15:20:07 -0400 Subject: [PATCH 049/453] 5092 minor cleanup before pr --- .../casemodule/datasourcesummary/DataSourceBrowser.java | 4 +++- .../casemodule/datasourcesummary/DataSourceSummary.java | 3 +-- .../casemodule/datasourcesummary/DataSourceSummaryDialog.java | 1 + .../casemodule/datasourcesummary/DataSourceSummaryNode.java | 3 +-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java index f423b8a75a..ac09010de1 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java @@ -196,13 +196,15 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana * @param newStatus the new status which the data source should have */ void refresh(long dataSourceId, IngestJobInfo.IngestJobStatusType newStatus) { - Node[] selectedNodes = explorerManager.getSelectedNodes(); + //attempt to update the status of any datasources that had status which was STARTED for (DataSourceSummary summary : dataSourceSummaryList) { if (summary.getDataSource().getId() == dataSourceId) { summary.setIngestStatus(newStatus); } } + //figure out which nodes were previously selected + Node[] selectedNodes = explorerManager.getSelectedNodes(); SwingUtilities.invokeLater(() -> { explorerManager.setRootContext(new DataSourceSummaryNode(dataSourceSummaryList)); List nodesToSelect = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java index fedb11884a..c9c5b551ce 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java @@ -170,8 +170,7 @@ class DataSourceSummary { /** * Get the status which was determined for this callback * - * @return the status of the data source which was - * queried for + * @return the status of the data source which was queried for */ IngestJobStatusType getStatus() { return jobStatus; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java index f322731f42..1535cbefe0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java @@ -76,6 +76,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser this.repaint(); } }); + //add listener to refresh jobs with Started status when they complete IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> { if (evt instanceof DataSourceAnalysisCompletedEvent) { DataSourceAnalysisCompletedEvent dsEvent = (DataSourceAnalysisCompletedEvent) evt; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java index b833e39743..703e1f1373 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java @@ -184,8 +184,7 @@ final class DataSourceSummaryNode extends AbstractNode { } @Override - public Action[] getActions(boolean context - ) { + public Action[] getActions(boolean context) { List actions = new ArrayList<>(); actions.add(new ViewDataSourceInContextAction()); return actions.toArray(new Action[actions.size()]); From d34610fb58c118a75196a440d92cb5f43e74be7a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 4 Jun 2019 15:32:21 -0400 Subject: [PATCH 050/453] 5092 address codacy complaints --- .../datasourcesummary/DataSourceSummary.java | 10 +++++----- .../datasourcesummary/DataSourceSummaryNode.java | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java index c9c5b551ce..390dce1afe 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java @@ -34,7 +34,7 @@ import org.sleuthkit.datamodel.TskCoreException; * */ class DataSourceSummary { - + private static final Logger logger = Logger.getLogger(DataSourceSummary.class.getName()); private static final String INGEST_JOB_STATUS_QUERY = "status_id FROM ingest_jobs WHERE obj_id="; private final DataSource dataSource; @@ -73,7 +73,7 @@ class DataSourceSummary { Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(INGEST_JOB_STATUS_QUERY + dataSource.getId(), callback); status = callback.getStatus(); } catch (NoCurrentCaseException | TskCoreException ex) { - + logger.log(Level.WARNING, "Error getting status for data source from case database", ex); } } @@ -147,9 +147,9 @@ class DataSourceSummary { * this data source */ class IngestJobQueryCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback { - + private IngestJobStatusType jobStatus = null; - + @Override public void process(ResultSet rs) { try { @@ -175,6 +175,6 @@ class DataSourceSummary { IngestJobStatusType getStatus() { return jobStatus; } - + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java index 703e1f1373..0411d94bb2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.casemodule.datasourcesummary; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Observable; import java.util.Observer; import javax.swing.Action; From 21615fca2f339b57b744f772d49b8588fe46ad35 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 5 Jun 2019 10:59:45 -0400 Subject: [PATCH 051/453] 5061 remove translation size setting --- .../BingTranslatorSettingsPanel.form | 56 +++---------------- .../BingTranslatorSettingsPanel.java | 42 +++----------- .../translators/Bundle.properties | 2 - .../GoogleTranslatorSettingsPanel.form | 4 ++ .../GoogleTranslatorSettingsPanel.java | 22 ++++++-- 5 files changed, 38 insertions(+), 88 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form index 64227561b3..54dfaa4827 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -39,28 +39,15 @@ - - - - + - - - - - - - - - - - - - - - - - + + + + + + + @@ -83,12 +70,6 @@ - - - - - - @@ -151,27 +132,6 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index e477fbf662..5a73bca0fa 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -122,9 +122,6 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { testButton = new javax.swing.JButton(); targetLanguageLabel = new javax.swing.JLabel(); targetLanguageComboBox = new javax.swing.JComboBox<>(); - translationSizeLabel = new javax.swing.JLabel(); - translationSizeSpinner = new javax.swing.JSpinner(); - unitsLabel = new javax.swing.JLabel(); testUntranslatedTextField = new javax.swing.JTextField(); untranslatedLabel = new javax.swing.JLabel(); resultLabel = new javax.swing.JLabel(); @@ -151,12 +148,6 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(translationSizeLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.translationSizeLabel.text")); // NOI18N - - translationSizeSpinner.setModel(new javax.swing.SpinnerNumberModel(5000, 5000, 500000, 5000)); - - org.openide.awt.Mnemonics.setLocalizedText(unitsLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.unitsLabel.text")); // NOI18N - testUntranslatedTextField.setText(DEFUALT_TEST_STRING); org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N @@ -190,24 +181,15 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, 486, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 20, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(translationSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(testButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(testButton, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(25, 25, 25) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(translationSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 77, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(unitsLabel) - .addGap(0, 0, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addComponent(untranslatedLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(resultLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))) + .addComponent(untranslatedLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(resultLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addContainerGap()))) ); layout.setVerticalGroup( @@ -222,11 +204,6 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { .addComponent(targetLanguageLabel) .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(translationSizeLabel) - .addComponent(translationSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(unitsLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(testButton) .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -264,9 +241,6 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { private javax.swing.JButton testButton; private javax.swing.JLabel testResultValueLabel; private javax.swing.JTextField testUntranslatedTextField; - private javax.swing.JLabel translationSizeLabel; - private javax.swing.JSpinner translationSizeSpinner; - private javax.swing.JLabel unitsLabel; private javax.swing.JLabel untranslatedLabel; private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties index 6ebd3002ef..0021118e14 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties @@ -6,9 +6,7 @@ BingTranslatorSettingsPanel.testButton.text=Test BingTranslatorSettingsPanel.testResultValueLabel.text= BingTranslatorSettingsPanel.resultLabel.text=Result: BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: -BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: -BingTranslatorSettingsPanel.unitsLabel.text=characters BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the GoogleTranslatorSettingsPanel.testButton.text=Test GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form index 7d728fd011..b55a40b30f 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form @@ -145,6 +145,7 @@ + @@ -152,6 +153,7 @@ + @@ -159,6 +161,7 @@ + @@ -166,6 +169,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java index 8bccc01485..0041860182 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java @@ -130,21 +130,31 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { }); selectLanguageByCode(targetLanguageCode); targetLanguageComboBox.addItemListener(listener); - targetLanguageComboBox.setEnabled(true); + enableControls(true); + } else { - targetLanguageComboBox.setEnabled(false); + enableControls(false); } } else { warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_noFileSelected()); - targetLanguageComboBox.setEnabled(false); + enableControls(false); } } catch (Throwable throwable) { warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unknownFailurePopulating()); logger.log(Level.WARNING, "Throwable caught while populating list of supported languages", throwable); - targetLanguageComboBox.setEnabled(false); + enableControls(false); } } + private void enableControls(boolean enabled) { + targetLanguageComboBox.setEnabled(enabled); + testButton.setEnabled(enabled); + testResultValueLabel.setEnabled(enabled); + testUntranslatedTextField.setEnabled(enabled); + untranslatedLabel.setEnabled(enabled); + resultLabel.setEnabled(enabled); + } + /** * Given a language code select the corresponding language in the combo box * if it is present @@ -202,12 +212,16 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(resultLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.resultLabel.text")); // NOI18N + resultLabel.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N + untranslatedLabel.setEnabled(false); testUntranslatedTextField.setText(DEFUALT_TEST_STRING); + testUntranslatedTextField.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(testButton, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testButton.text")); // NOI18N + testButton.setEnabled(false); testButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { testButtonActionPerformed(evt); From 2ecd3a56b4f5740dc03992b21a05765cd5dbcde7 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 5 Jun 2019 11:13:32 -0400 Subject: [PATCH 052/453] Addressed review comments. --- .../relationships/MessageNode.java | 5 -- .../datamodel/AbstractAbstractFileNode.java | 10 +-- .../datamodel/AbstractContentNode.java | 7 +- .../datamodel/BlackboardArtifactNode.java | 82 +++++++++++++++---- .../datamodel/Bundle.properties-MERGED | 2 +- .../autopsy/datamodel/GetSCOTask.java | 2 +- 6 files changed, 79 insertions(+), 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index 2f4ce1157a..d1cdcbabf2 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -18,13 +18,10 @@ */ package org.sleuthkit.autopsy.communications.relationships; -import java.util.List; import java.util.TimeZone; import java.util.logging.Level; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Sheet; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.core.UserPreferences; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; @@ -40,7 +37,6 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHO import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT; import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; -import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.communications.Utils; @@ -72,7 +68,6 @@ final class MessageNode extends BlackboardArtifactNode { @Override protected Sheet createSheet() { Sheet sheet = super.createSheet(); - List tags = getAllTagsFromDatabase(); Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); if (sheetSet == null) { sheetSet = Sheet.createPropertiesSet(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index e8d71caae7..158f2ef4d9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -74,8 +74,6 @@ import org.sleuthkit.datamodel.TskData; public abstract class AbstractAbstractFileNode extends AbstractContentNode { private static final Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName()); - @NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description") - private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc(); private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED); @@ -328,9 +326,11 @@ public abstract class AbstractAbstractFileNode extends A } // Create place holders for S C O - properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), NO_DESCR, "")); - properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, "")); - properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), NO_DESCR, "")); + properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), VALUE_LOADING, "")); + properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), VALUE_LOADING, "")); + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), VALUE_LOADING, "")); + } // Get the SCO columns data in a background task diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 154ca68d50..b9e012e6af 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -64,14 +64,15 @@ public abstract class AbstractContentNode extends ContentNode * populate this node. */ static final ExecutorService backgroundTasksPool; - static final Integer MAX_POOL_SIZE = 10; + private static final Integer MAX_POOL_SIZE = 10; /** * Default no description string */ - @NbBundle.Messages("AbstractContentNode.nodescription=no description") + @NbBundle.Messages({"AbstractContentNode.nodescription=no description", + "AbstractContentNode.valueLoading=value loading"}) protected static final String NO_DESCR = Bundle.AbstractContentNode_nodescription(); - + protected static final String VALUE_LOADING = Bundle.AbstractContentNode_valueLoading(); /** * Event signals to indicate the background tasks have completed processing. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 1f871363f9..23ab78fa01 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -99,10 +99,7 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties; - - protected final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); - - + /* * Artifact types which should have the full unique path of the associated * content as a property. @@ -367,9 +364,11 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), NO_DESCR, "")); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, "")); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), VALUE_LOADING, "")); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), VALUE_LOADING, "")); + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), VALUE_LOADING, "")); + } // Get the SCO columns data in a background task backgroundTasksPool.submit(new GetSCOTask( @@ -596,14 +595,33 @@ public class BlackboardArtifactNode extends AbstractContentNode tags, CorrelationAttributeInstance attribute) { + HasCommentStatus status = getCommentProperty(tags, attribute ); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, + status)); + } + + /** + * Gets the comment property for the node + * * @param tags the list of tags associated with the file * @param attribute the correlation attribute associated with this * artifact's associated file, null if central repo is not * enabled * @return comment property */ - @NbBundle.Messages({"BlackboardArtifactNode.createSheet.comment.name=C", - "BlackboardArtifactNode.createSheet.comment.displayName=C"}) @Override protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { @@ -627,14 +645,15 @@ public class BlackboardArtifactNode extends AbstractContentNode tags) { + Pair scoreAndDescription = getScorePropertyAndDescription(tags); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoreAndDescription.getRight(), scoreAndDescription.getLeft())); + } + /** + * Get the score property for the node. + * + * @param tags the list of tags associated with the file + * + * @return score property and description + */ @Override protected Pair getScorePropertyAndDescription(List tags) { Score score = Score.NO_SCORE; @@ -693,17 +724,40 @@ public class BlackboardArtifactNode extends AbstractContentNode countAndDescription = getCountPropertyAndDescription(attribute); + sheetSet.put( + new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), countAndDescription.getRight(), countAndDescription.getLeft())); + } + + /** + * Gets the Occurrences property for the node. + * + * @param attribute correlation attribute instance + * + * @return count and description + * + */ @Override protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) { - Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting + Long count = -1L; String description = Bundle.BlackboardArtifactNode_createSheet_count_noCentralRepo_description(); try { //don't perform the query if there is no correlation value diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 8d2bd5952d..0386b0fef2 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -1,5 +1,4 @@ AbstractAbstractFileNode.accessTimeColLbl=Access Time -AbstractAbstractFileNode.addFileProperty.desc=no description AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr. AbstractAbstractFileNode.changeTimeColLbl=Change Time AbstractAbstractFileNode.createdTimeColLbl=Created Time @@ -38,6 +37,7 @@ AbstractAbstractFileNode.typeDirColLbl=Type(Dir) AbstractAbstractFileNode.typeMetaColLbl=Type(Meta) AbstractAbstractFileNode.useridColLbl=UserID AbstractContentNode.nodescription=no description +AbstractContentNode.valueLoading=value loading AbstractFsContentNode.noDesc.text=no description ArtifactStringContent.attrsTableHeader.sources=Source(s) ArtifactStringContent.attrsTableHeader.type=Type diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java index 4278f99d66..b7f4e38a5e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java @@ -37,7 +37,7 @@ class GetSCOTask implements Runnable { private final WeakReference> weakNodeRef; private final PropertyChangeListener listener; - public GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { + GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) { this.weakNodeRef = weakContentRef; this.listener = listener; } From 852ad67e05f59a3e2860210136e464de054006e7 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 5 Jun 2019 12:16:16 -0400 Subject: [PATCH 053/453] 5061 fix initial selection of english --- .../BingTranslatorSettingsPanel.form | 1 + .../BingTranslatorSettingsPanel.java | 15 ++++++--- .../translators/Bundle.properties-MERGED | 5 +-- .../translators/GoogleTranslator.java | 31 +++++++++++-------- .../GoogleTranslatorSettingsPanel.java | 24 +++++++------- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form index 54dfaa4827..cd855ac419 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form @@ -124,6 +124,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index 5a73bca0fa..c6d4c9b932 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -33,6 +33,7 @@ import java.util.logging.Logger; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.commons.lang3.StringUtils; +import org.openide.util.NbBundle.Messages; /** * Settings panel for the GoogleTranslator @@ -68,11 +69,12 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { } }); - targetLanguageCode = code; populateComboBox(); - selectLanguageByCode(targetLanguageCode); + selectLanguageByCode(code); + targetLanguageCode = code; } + @Messages({"BingTranslatorSettingsPanel.warning.targetLanguageFailure=Unable to get list of target languages or parse the result that was received"}) private void populateComboBox() { Request get_request = new Request.Builder() .url(GET_TARGET_LANGUAGES_URL).build(); @@ -87,8 +89,11 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { responses.entrySet().forEach((entry) -> { targetLanguageComboBox.addItem(new LanguageWrapper(entry.getKey(), entry.getValue().getAsJsonObject().get("name").getAsString())); }); + targetLanguageComboBox.setEnabled(true); } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException ex) { - logger.log(Level.WARNING, "Unable to get list of target languages or parse the result that was received", ex); + logger.log(Level.SEVERE, Bundle.BingTranslatorSettingsPanel_warning_targetLanguageFailure(), ex); + warningLabel.setText(Bundle.BingTranslatorSettingsPanel_warning_targetLanguageFailure()); + targetLanguageComboBox.setEnabled(false); } } @@ -142,6 +147,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(targetLanguageLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.targetLanguageLabel.text")); // NOI18N + targetLanguageComboBox.setEnabled(false); targetLanguageComboBox.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { targetLanguageComboBoxSelected(evt); @@ -216,11 +222,12 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { ); }// //GEN-END:initComponents + @Messages({"BingTranslatorSettingsPanel.warning.invalidKey=Invalid translation authentication key"}) private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed if (testTranslationSetup()) { warningLabel.setText(""); } else { - warningLabel.setText("Invalid translation authentication key"); + warningLabel.setText(Bundle.BingTranslatorSettingsPanel_warning_invalidKey()); } }//GEN-LAST:event_testButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED index 35e06bda99..51cbb9a26f 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED @@ -1,9 +1,12 @@ BingTranslator.name.text=Bing Translator +BingTranslatorSettingsPanel.warning.invalidKey=Invalid translation authentication key +BingTranslatorSettingsPanel.warning.targetLanguageFailure=Unable to get list of target languages or parse the result that was received GoogleTranslator.name.text=Google Translate GoogleTranslatorSettingsPanel.browseButton.text=Browse GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials Path: GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file. GoogleTranslatorSettingsPanel.errorMessage.noFileSelected=A JSON file must be selected to provide your credentials for Google Translate. +GoogleTranslatorSettingsPanel.errorMessage.translationFailure=Translation failure with specified credentials GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file. GoogleTranslatorSettingsPanel.errorMessage.unableToReadCredentials=Unable to read credentials from credentials file, please set the location to be a valid JSON credentials file. GoogleTranslatorSettingsPanel.errorMessage.unknownFailureGetting=Failure getting list of supported languages with current credentials file. @@ -16,9 +19,7 @@ BingTranslatorSettingsPanel.testButton.text=Test BingTranslatorSettingsPanel.testResultValueLabel.text= BingTranslatorSettingsPanel.resultLabel.text=Result: BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: -BingTranslatorSettingsPanel.translationSizeLabel.text=Translation Size: BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language: -BingTranslatorSettingsPanel.unitsLabel.text=characters BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the GoogleTranslatorSettingsPanel.testButton.text=Test GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated: diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index d55316b86c..3bb6a6c9ed 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -32,6 +32,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.logging.Level; import java.util.logging.Logger; +import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.coreutils.EscapeUtil; @@ -59,26 +60,26 @@ public final class GoogleTranslator implements TextTranslator { settingsPanel = new GoogleTranslatorSettingsPanel(settings.getCredentialPath(), settings.getTargetLanguageCode()); loadTranslator(); } - + private static boolean googleIsReachable() { String host = "www.google.com"; InetAddress address; try { address = InetAddress.getByName(host); return address.isReachable(1500); - }catch (UnknownHostException ex) { + } catch (UnknownHostException ex) { return false; } catch (IOException ex) { return false; } } - + @Override public String translate(String string) throws TranslationException { if (!googleIsReachable()) { throw new TranslationException("Failure translating using GoogleTranslator: Cannot connect to Google"); } - + if (googleTranslate != null) { try { // Translates some text into English, without specifying the source language. @@ -89,7 +90,7 @@ public final class GoogleTranslator implements TextTranslator { // We can't currently set parameters, so we are using the default behavior of // assuming the input is HTML. We need to replace newlines with
    for Google to preserve them substring = substring.replaceAll("(\r\n|\n)", "
    "); - + // The API complains if the "Payload" is over 204800 bytes. I'm assuming that // deals with the full request. At some point, we get different errors about too // much text. Officially, Google says they will googleTranslate only 5k chars, @@ -101,15 +102,15 @@ public final class GoogleTranslator implements TextTranslator { Translation translation = googleTranslate.translate(substring); String translatedString = translation.getTranslatedText(); - + // put back the newlines translatedString = translatedString.replaceAll("
    ", "\n"); - + // With our current settings, Google Translate outputs HTML // so we need to undo the escape characters. translatedString = EscapeUtil.unEscapeHtml(translatedString); return translatedString; - } catch (Throwable ex) { + } catch (Throwable ex) { //Catching throwables because some of this Google Translate code throws throwables throw new TranslationException("Failure translating using GoogleTranslator", ex); } @@ -117,7 +118,7 @@ public final class GoogleTranslator implements TextTranslator { throw new TranslationException("Google Translator has not been configured, credentials need to be specified"); } } - + @Messages({"GoogleTranslator.name.text=Google Translate"}) @Override public String getName() { @@ -136,10 +137,14 @@ public final class GoogleTranslator implements TextTranslator { private void loadTranslator() { InputStream credentialStream = null; Credentials creds = null; - try { - credentialStream = new FileInputStream(settings.getCredentialPath()); - } catch (FileNotFoundException ex) { - logger.log(Level.WARNING, "JSON file for GoogleTranslator credentials not found", ex); + if (StringUtils.isBlank(settings.getCredentialPath())) { + logger.log(Level.INFO, "No credentials file has been provided for Google Translator"); + } else { + try { + credentialStream = new FileInputStream(settings.getCredentialPath()); + } catch (FileNotFoundException ex) { + logger.log(Level.WARNING, "JSON file for GoogleTranslator credentials not found", ex); + } } if (credentialStream != null) { try { diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java index 0041860182..4032d69a05 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java @@ -305,18 +305,20 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { } }//GEN-LAST:event_browseButtonActionPerformed + @Messages({"GoogleTranslatorSettingsPanel.errorMessage.translationFailure=Translation failure with specified credentials"}) private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed - testResultValueLabel.setText(""); - Translate tempTranslate = getTemporaryTranslationService(); - if (tempTranslate != null) { - try { - Translation translation = tempTranslate.translate(testUntranslatedTextField.getText()); - testResultValueLabel.setText(translation.getTranslatedText()); - warningLabel.setText(""); - } catch (Exception ex) { - warningLabel.setText("Invalid translation credentials path"); - } - } + testResultValueLabel.setText(""); + Translate tempTranslate = getTemporaryTranslationService(); + if (tempTranslate != null) { + try { + Translation translation = tempTranslate.translate(testUntranslatedTextField.getText()); + testResultValueLabel.setText(translation.getTranslatedText()); + warningLabel.setText(""); + } catch (Exception ex) { + warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure()); + logger.log(Level.WARNING, Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure(), ex); + } + } }//GEN-LAST:event_testButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables From f8d02a3002e361f9d94be229b16bd75163c0d67b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 5 Jun 2019 12:19:38 -0400 Subject: [PATCH 054/453] Added button --- .../autopsy/corecomponents/Bundle.properties | 1 + .../corecomponents/Bundle.properties-MERGED | 1 + .../corecomponents/DataResultViewerTable.form | 24 +++- .../corecomponents/DataResultViewerTable.java | 29 +++-- .../directorytree/Bundle.properties-MERGED | 6 +- .../directorytree/ExportCSVAction.java | 117 ++++++++++++------ 6 files changed, 122 insertions(+), 56 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 7eb261880f..fae74a30ac 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -217,3 +217,4 @@ DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
    Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n +DataResultViewerTable.exportCSVButton.text=Save table as CSV diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index cd75ae919e..43ceba39ba 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -270,3 +270,4 @@ DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
    Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n +DataResultViewerTable.exportCSVButton.text=Save table as CSV diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index 6b17cfd6dd..87e771695b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -16,9 +16,10 @@ - - - + + + + @@ -39,7 +40,7 @@ - + @@ -48,9 +49,10 @@ + - - + + @@ -164,5 +166,15 @@
    + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index a5bbc461b8..eb5fad97f5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -1291,6 +1291,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); gotoPageLabel = new javax.swing.JLabel(); gotoPageTextField = new javax.swing.JTextField(); + exportCSVButton = new javax.swing.JButton(); pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N @@ -1338,13 +1339,21 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } }); + exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N + exportCSVButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + exportCSVButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap(608, Short.MAX_VALUE) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(exportCSVButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(pageLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -1366,7 +1375,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() + .addGap(3, 3, 3) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(pageLabel) .addComponent(pageNumLabel) @@ -1374,9 +1383,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(gotoPageLabel) - .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 324, Short.MAX_VALUE) + .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(exportCSVButton)) + .addGap(3, 3, 3) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE) .addContainerGap()) ); @@ -1397,7 +1407,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer { pagingSupport.gotoPage(); }//GEN-LAST:event_gotoPageTextFieldActionPerformed + private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed + org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(rootNode.getChildren().getNodes()), this); + }//GEN-LAST:event_exportCSVButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton exportCSVButton; private javax.swing.JLabel gotoPageLabel; private javax.swing.JTextField gotoPageTextField; private org.openide.explorer.view.OutlineView outlineView; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index d58338c450..6ec6a0e76b 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -11,9 +11,9 @@ DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data s DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module. DirectoryTreeTopComponent.resultsView.title=Listing # {0} - Output file -ExportCSV.actionPerformed.fileExists=File {0} already exists -ExportCSV.actionPerformed.noCurrentCase=No open case available -ExportCSV.title.text=Export to CSV +ExportCSV.saveNodesToCSV.fileExists=File {0} already exists +ExportCSV.saveNodesToCSV.noCurrentCase=No open case available +ExportCSV.title.text=Export selected rows to CSV ExternalViewerAction.actionPerformed.failure.exe.message=The file is an executable and will not be opened. ExternalViewerAction.actionPerformed.failure.IO.message=There is no associated editor for files of this type or the associated application failed to launch. ExternalViewerAction.actionPerformed.failure.missingFile.message=The file no longer exists. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index 169701a55c..dce6244084 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -25,8 +25,10 @@ import java.io.BufferedWriter; import java.io.FileWriter; import java.io.FileOutputStream; import java.io.OutputStreamWriter; +import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.HashSet; @@ -63,9 +65,11 @@ import org.openide.nodes.Node.Property; */ public final class ExportCSVAction extends AbstractAction { - private Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); + private static Logger logger = Logger.getLogger(ExportCSVAction.class.getName()); + private final static String DEFAULT_FILENAME = "Results"; + private final static List columnsToSkip = Arrays.asList("S", "C", "O"); - private String userDefinedExportPath; + private static volatile String userDefinedExportPath; // TODO make volatile or whatever // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every @@ -82,53 +86,44 @@ public final class ExportCSVAction extends AbstractAction { /** * Private constructor for the action. */ - @NbBundle.Messages({"ExportCSV.title.text=Export to CSV"}) + @NbBundle.Messages({"ExportCSV.title.text=Export selected rows to CSV"}) private ExportCSVAction() { super(Bundle.ExportCSV_title_text()); } - + /** * Asks user to choose destination, then extracts content to destination * (recursing on directories). * * @param e The action event. */ - @NbBundle.Messages({ - "# {0} - Output file", - "ExportCSV.actionPerformed.fileExists=File {0} already exists", - "ExportCSV.actionPerformed.noCurrentCase=No open case available"}) + @Override public void actionPerformed(ActionEvent e) { - Collection selectedNodes = Utilities.actionsGlobalContext().lookupAll(Node.class); - if (selectedNodes.isEmpty()) { + saveNodesToCSV(selectedNodes, (Component)e.getSource()); + } + + @NbBundle.Messages({ + "# {0} - Output file", + "ExportCSV.saveNodesToCSV.fileExists=File {0} already exists", + "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available"}) + public static void saveNodesToCSV(Collection nodesToExport, Component component) { + + if (nodesToExport.isEmpty()) { return; } - Node parent = selectedNodes.iterator().next().getParentNode(); - if (parent != null) { - System.out.println("HTML name: " + parent.getHtmlDisplayName()); - System.out.println("Display name: " + parent.getDisplayName()); - System.out.println("Class: " + parent.getClass().getCanonicalName()); - for (PropertySet set : parent.getPropertySets()) { - for (Property prop : set.getProperties()) { - try { - System.out.println(" " + prop.getDisplayName() + " : " + prop.getValue().toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - } - } - try { - String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_listing.csv", Calendar.getInstance()); + + String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode()); + JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows()))); fileChooser.setSelectedFile(new File(fileName)); fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); - int returnVal = fileChooser.showSaveDialog((Component) e.getSource()); + int returnVal = fileChooser.showSaveDialog(component); if (returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = fileChooser.getSelectedFile(); @@ -143,15 +138,51 @@ public final class ExportCSVAction extends AbstractAction { return; } - CSVWriter writer = new CSVWriter(selectedNodes, selectedFile); + CSVWriter writer = new CSVWriter(nodesToExport, selectedFile); writer.execute(); } } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExportCSV_actionPerformed_noCurrentCase()); + JOptionPane.showMessageDialog(component, Bundle.ExportCSV_actionPerformed_noCurrentCase()); logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS } } + /** + * Create a default name for the CSV output. + * + * @param parent The parent node for the selected nodes + * + * @return the default name + */ + private static String getDefaultOutputFileName(Node parent) { + String dateStr = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS", Calendar.getInstance()); + + if (parent != null) { + // The first value in the property set is generally a reasonable name + for (PropertySet set : parent.getPropertySets()) { + for (Property prop : set.getProperties()) { + try { + String parentName = prop.getValue().toString(); + + // Strip off the count (if present) + System.out.println("parentName (raw) : " + parentName); + parentName = parentName.replaceAll("\\([0-9]+\\)$", ""); + System.out.println("parentName (after paren regex) : " + parentName); + + // Strip out any invalid characters + parentName = parentName.replaceAll("[\\\\/:*?\"<>|]", "_"); + System.out.println("parentName (after char regex) : " + parentName); + + return parentName + " " + dateStr; + } catch (IllegalAccessException | InvocationTargetException ex) { + logger.log(Level.WARNING, "Failed to get property set value as string", ex); + } + } + } + } + return DEFAULT_FILENAME + " " + dateStr; + } + /** * Get the export directory path. * @@ -159,7 +190,7 @@ public final class ExportCSVAction extends AbstractAction { * * @return The export directory path. */ - private String getExportDirectory(Case openCase) { + private static String getExportDirectory(Case openCase) { // TODO sync String caseExportPath = openCase.getExportDirectory(); if (userDefinedExportPath == null) { @@ -183,7 +214,7 @@ public final class ExportCSVAction extends AbstractAction { * @param exportPath The export path. * @param openCase The current case. */ - private void updateExportDirectory(String exportPath, Case openCase) { + private static void updateExportDirectory(String exportPath, Case openCase) { // TODO sync if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) { userDefinedExportPath = null; } else { @@ -195,12 +226,12 @@ public final class ExportCSVAction extends AbstractAction { /** * Thread that does the actual extraction work */ - private class CSVWriter extends SwingWorker { + private static class CSVWriter extends SwingWorker { private final Logger logger = Logger.getLogger(CSVWriter.class.getName()); private ProgressHandle progress; - private final List nodesToExport; + private final Collection nodesToExport; private final File outputFile; /** @@ -208,8 +239,8 @@ public final class ExportCSVAction extends AbstractAction { * * @param extractionTasks List of file extraction tasks. */ - CSVWriter(Collection selectedNodes, File outputFile) { - this.nodesToExport = new ArrayList<>(selectedNodes); + CSVWriter(Collection nodesToExport, File outputFile) { + this.nodesToExport = nodesToExport; this.outputFile = outputFile; } @@ -241,25 +272,31 @@ public final class ExportCSVAction extends AbstractAction { // Write the header List headers = new ArrayList<>(); - PropertySet[] sets = nodesToExport.get(0).getPropertySets(); + PropertySet[] sets = nodesToExport.iterator().next().getPropertySets(); for(PropertySet set : sets) { for (Property prop : set.getProperties()) { - headers.add(prop.getDisplayName()); + if ( ! columnsToSkip.contains(prop.getDisplayName())) { + headers.add(prop.getDisplayName()); + } } } br.write(listToCSV(headers)); // Write each line - for (Node node : nodesToExport) { + Iterator nodeIterator = nodesToExport.iterator(); + while (nodeIterator.hasNext()) { if (this.isCancelled()) { break; } + Node node = (Node)nodeIterator.next(); List values = new ArrayList<>(); sets = node.getPropertySets(); for(PropertySet set : sets) { for (Property prop : set.getProperties()) { - values.add(prop.getValue().toString()); + if ( ! columnsToSkip.contains(prop.getDisplayName())) { + values.add(prop.getValue().toString()); + } } } br.write(listToCSV(values)); From 3d319be5b8af8f35cb48868e27399c7ab662af37 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 5 Jun 2019 12:27:53 -0400 Subject: [PATCH 055/453] 5061 adjust comments for clarity and completeness --- .../translators/BingTranslator.java | 28 ++++++++++++++--- .../BingTranslatorSettingsPanel.java | 3 ++ .../translators/GoogleTranslator.java | 7 ++++- .../GoogleTranslatorSettingsPanel.java | 30 +++++++++++-------- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index 78f1cf9afd..775cdf3588 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -40,22 +40,32 @@ import org.sleuthkit.autopsy.texttranslation.TranslationException; @ServiceProvider(service = TextTranslator.class) public class BingTranslator implements TextTranslator { - //In the String below, "en" is the target language. You can include multiple target + //The target language follows the to= in the string below. You can include multiple target //languages separated by commas. A full list of supported languages is here: //https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support private static final String BASE_URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to="; + private static final int MAX_STRING_LENGTH = 5000; private final BingTranslatorSettingsPanel settingsPanel; private final BingTranslatorSettings settings = new BingTranslatorSettings(); // This sends messages to Microsoft. private final OkHttpClient CLIENT = new OkHttpClient(); - //We might want to make this a configurable setting for anyone who has a - //paid account that's willing to pay for long documents. - private final int MAX_STRING_LENGTH = 5000; + /** + * Create a Bing Translator + */ public BingTranslator() { settingsPanel = new BingTranslatorSettingsPanel(settings.getAuthenticationKey(), settings.getTargetLanguageCode()); } + /** + * Get the tranlationurl for the specified language code + * + * + * + * @param languageCode language code for language to translate to + * + * @return a string representation of the url to request translation from + */ static String getTranlatorUrl(String languageCode) { return BASE_URL + languageCode; } @@ -134,6 +144,16 @@ public class BingTranslator implements TextTranslator { settings.saveSettings(); } + /** + * Parse the response to get the translated text + * + * @param json_text the json which was received as a response to a + * translation request + * + * @return the translated text + * + * @throws TranslationException + */ private String parseJSONResponse(String json_text) throws TranslationException { /* * Here is an example of the text we get from Bing when input is "gato", diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index c6d4c9b932..cb115354e2 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -74,6 +74,9 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { targetLanguageCode = code; } + /** + * Populate the target language combo box with available target languages + */ @Messages({"BingTranslatorSettingsPanel.warning.targetLanguageFailure=Unable to get list of target languages or parse the result that was received"}) private void populateComboBox() { Request get_request = new Request.Builder() diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index 3bb6a6c9ed..de61222072 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -61,6 +61,11 @@ public final class GoogleTranslator implements TextTranslator { loadTranslator(); } + /** + * Check if google is able to be reached + * + * @return true if it can be, false otherwise + */ private static boolean googleIsReachable() { String host = "www.google.com"; InetAddress address; @@ -138,7 +143,7 @@ public final class GoogleTranslator implements TextTranslator { InputStream credentialStream = null; Credentials creds = null; if (StringUtils.isBlank(settings.getCredentialPath())) { - logger.log(Level.INFO, "No credentials file has been provided for Google Translator"); + logger.log(Level.INFO, "No credentials file has been provided for Google Translator"); } else { try { credentialStream = new FileInputStream(settings.getCredentialPath()); diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java index 4032d69a05..84fad9e8e1 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java @@ -146,6 +146,12 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { } } + /** + * Helper method to enable/disable all controls which are dependent on valid + * credentials having been provided + * + * @param enabled true to enable the controls, false to disable them + */ private void enableControls(boolean enabled) { targetLanguageComboBox.setEnabled(enabled); testButton.setEnabled(enabled); @@ -307,18 +313,18 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { @Messages({"GoogleTranslatorSettingsPanel.errorMessage.translationFailure=Translation failure with specified credentials"}) private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed - testResultValueLabel.setText(""); - Translate tempTranslate = getTemporaryTranslationService(); - if (tempTranslate != null) { - try { - Translation translation = tempTranslate.translate(testUntranslatedTextField.getText()); - testResultValueLabel.setText(translation.getTranslatedText()); - warningLabel.setText(""); - } catch (Exception ex) { - warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure()); - logger.log(Level.WARNING, Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure(), ex); - } - } + testResultValueLabel.setText(""); + Translate tempTranslate = getTemporaryTranslationService(); + if (tempTranslate != null) { + try { + Translation translation = tempTranslate.translate(testUntranslatedTextField.getText()); + testResultValueLabel.setText(translation.getTranslatedText()); + warningLabel.setText(""); + } catch (Exception ex) { + warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure()); + logger.log(Level.WARNING, Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure(), ex); + } + } }//GEN-LAST:event_testButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables From 938d0826d7e08b68d32e8f8a7a00520e4997b6ce Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 5 Jun 2019 12:56:49 -0400 Subject: [PATCH 056/453] 5061 address easy to fix codacy issues --- .../translators/BingTranslator.java | 7 +++---- .../translators/BingTranslatorTest.java | 14 +++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java index 775cdf3588..4c2526ab9b 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java @@ -121,8 +121,8 @@ public class BingTranslator implements TextTranslator { try { String response = postTranslationRequest(toTranslate); return parseJSONResponse(response); - } catch (Throwable e) { - throw new TranslationException(e.getMessage()); + } catch (IOException | TranslationException ex) { + throw new TranslationException("Exception while attempting to translate using BingTranslator", ex); } } @@ -167,8 +167,7 @@ public class BingTranslator implements TextTranslator { JsonObject response0 = responses.get(0).getAsJsonObject(); JsonArray translations = response0.getAsJsonArray("translations"); JsonObject translation0 = translations.get(0).getAsJsonObject(); - String text = translation0.get("text").getAsString(); - return text; + return translation0.get("text").getAsString(); } catch (IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) { throw new TranslationException("JSON text does not match Bing Translator scheme: " + e); } diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java index 48c29543d3..25c78ba049 100644 --- a/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java @@ -18,13 +18,13 @@ */ package org.sleuthkit.autopsy.texttranslation.translators; -import java.io.IOException; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; +//import java.io.IOException; +//import org.junit.After; +//import org.junit.AfterClass; +//import org.junit.Before; +//import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.*; +//import static org.junit.Assert.*; /** * Tests for the BingTranslator translation service, these tests have been @@ -33,7 +33,7 @@ import static org.junit.Assert.*; public class BingTranslatorTest { @Test - public void testTranslate() throws Exception { + public void testTranslate() { // BingTranslator translator = new BingTranslator(); // String input = "gato"; // String expectedTranslation = "cat"; From 05bd31ed90feb7c951795ace5b71513061c67b18 Mon Sep 17 00:00:00 2001 From: Joe Ho Date: Wed, 5 Jun 2019 14:34:35 -0400 Subject: [PATCH 057/453] Add new files for PR --- .../configurelogicalimager/Bundle.properties | 126 +++ .../Bundle.properties-MERGED | 176 ++++ .../ConfigVisualPanel1.form | 119 +++ .../ConfigVisualPanel1.java | 288 ++++++ .../ConfigVisualPanel2.form | 616 ++++++++++++ .../ConfigVisualPanel2.java | 952 ++++++++++++++++++ .../ConfigWizardPanel1.java | 149 +++ .../ConfigWizardPanel2.java | 158 +++ .../ConfigureLogicalImager.java | 85 ++ .../EditFullPathsRulePanel.form | 138 +++ .../EditFullPathsRulePanel.java | 265 +++++ .../EditNonFullPathsRulePanel.form | 280 ++++++ .../EditNonFullPathsRulePanel.java | 482 +++++++++ .../configurelogicalimager/EditRulePanel.java | 127 +++ .../EncryptionProgramsRule.java | 54 + .../LogicalImagerConfig.java | 67 ++ .../LogicalImagerConfigDeserializer.java | 187 ++++ .../LogicalImagerRule.java | 239 +++++ .../NewRuleSetPanel.form | 89 ++ .../NewRuleSetPanel.java | 153 +++ .../tsk_logical_imager.exe | Bin 0 -> 1398272 bytes .../autopsy/corecomponents/TextPrompt.java | 16 +- 22 files changed, 4762 insertions(+), 4 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties-MERGED create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.form create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.form create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel1.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel2.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigureLogicalImager.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditRulePanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/EncryptionProgramsRule.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfig.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfigDeserializer.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerRule.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/configurelogicalimager/tsk_logical_imager.exe diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties new file mode 100644 index 0000000000..093d91ffab --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties @@ -0,0 +1,126 @@ +ConfigureLogicalImagerDialog.loadButton.text=Load +ConfigureLogicalImagerDialog.newButton.text=New +ConfigureLogicalImagerDialog.title=Configure Logical Imager +ConfigureLogicalImagerDialog.saveButton.text=Save +ConfigureLogicalImagerDialog.configFile.text= +ConfigureLogicalImagerDialog.configFile.toolTipText= +ConfigureLogicalImagerDialog.jLabel1.text=Rule Set: +ConfigureLogicalImagerDialog.finalizeImageWriter.text=Finalize Image Writer +ConfigureLogicalImagerDialog.newRuleButton.text=New Rule +ConfigureLogicalImagerDialog.editRuleButton.text=Edit Rule +ConfigureLogicalImagerDialog.deleteRuleButton.text=Delete Rule +ConfigureLogicalImagerDialog.jLabel2.text=Rule Details +ConfigureLogicalImagerDialog.shouldSaveCheckBox.text=Should Save +ConfigureLogicalImagerDialog.shouldAlertCheckBox.text=Should Alert +ConfigureLogicalImagerDialog.jLabel3.text=Extensions: +ConfigureLogicalImagerDialog.extensionsTextField.text= +ConfigureLogicalImagerDialog.filenamesLabel.text=File names: +ConfigureLogicalImagerDialog.folderNamesLabel.text=Folder names: +ConfigureLogicalImagerDialog.jLabel4.text=File size: +ConfigureLogicalImagerDialog.daysIncludedLabel.text=day(s) +ConfigureLogicalImagerDialog.daysIncludedTextField.text= +ConfigureLogicalImagerDialog.modifiedDateLabel.text=Modified Within: +ConfigureLogicalImagerDialog.fullPathsLabel.text=Full paths: +ConfigureLogicalImagerDialog.flagEncryptionProgramsCheckBox.text=Flag encryption programs +EditRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +EditRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditRulePanel.fullPathsLabel.text=Full paths: +EditRulePanel.daysIncludedLabel.text=day(s) +EditRulePanel.daysIncludedTextField.text= +EditRulePanel.modifiedDateLabel.text=Modified Within: +EditRulePanel.folderNamesLabel.text=Folder names: +EditRulePanel.filenamesLabel.text=File names: +EditRulePanel.extensionsTextField.text= +ConfigureLogicalImagerDialog.jLabel5.text=Rule Name: +ConfigureLogicalImagerDialog.jLabel6.text=Description: +ConfigureLogicalImagerDialog.ruleNameEditTextField.text= +ConfigureLogicalImagerDialog.descriptionEditTextField.text= +ConfigureLogicalImagerDialog.fullPathsTable.columnModel.title0= +ConfigVisualPanel1.jTextField1.text= +ConfigVisualPanel1.jLabel1.text=Configuration file +ConfigVisualPanel2.jCheckBox1.text=jCheckBox1 +ConfigVisualPanel2.jCheckBox2.text=jCheckBox2 +ConfigVisualPanel2.jTextField1.text=jTextField1 +ConfigVisualPanel1.jRadioButton1.text=Create new configuration +ConfigVisualPanel1.jRadioButton2.text=Open existing configuration +ConfigVisualPanel1.jLabel1.text_1=Configuration file: +ConfigVisualPanel1.newRadioButton.text_1=Create a new configuration +ConfigVisualPanel1.loadRadioButton.text_1=Open an existing configuration +ConfigVisualPanel1.configFileTextField.text_1= +ConfigVisualPanel2.modifiedDateLabel.text=Modified Within: +ConfigVisualPanel2.folderNamesLabel.text=Folder names: +ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed +ConfigVisualPanel2.filenamesLabel.text=File names: +ConfigVisualPanel2.extensionsTextField.text= +ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console if rule matches +ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file if it matches a rule +ConfigVisualPanel2.deleteRuleButton.text=Delete Rule Set +ConfigVisualPanel2.descriptionEditTextField.text= +ConfigVisualPanel2.editRuleButton.text=Edit Rule Set +ConfigVisualPanel2.newRuleButton.text=New Rule Set +ConfigVisualPanel2.ruleNameEditTextField.text= +ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption programs are found +ConfigVisualPanel2.fullPathsLabel.text=Full paths: +ConfigVisualPanel2.daysIncludedLabel.text=day(s) +ConfigVisualPanel2.daysIncludedTextField.text= +ConfigVisualPanel2.configFileTextField.toolTipText= +ConfigVisualPanel2.configFileTextField.text= +ConfigVisualPanel2.filenamesTable.columnModel.title0= +ConfigVisualPanel2.fileSizeLabel.text=File size in bytes: +ConfigVisualPanel2.extensionsLabel.text=Extensions: +ConfigVisualPanel2.descriptionLabel.text=Description: +ConfigVisualPanel2.ruleNameLabel.text=Rule Set: +ConfigVisualPanel2.ruleSetFileLabel.text=Configuration rule file: +EditRulePanel.ruleNameLabel.text=Rule Set: +EditRulePanel.descriptionTextField.text= +EditRulePanel.extensionsLabel.text=Extensions: +EditRulePanel.ruleNameTextField.text= +EditRulePanel.extensionsCheckBox.text= +EditRulePanel.filenamesCheckBox.text= +EditRulePanel.folderNamesCheckBox.text= +EditRulePanel.fullPathsCheckBox.text= +EditRulePanel.fileSizeCheckBox.text= +EditRulePanel.minDaysCheckBox.text= +EditRulePanel.fileSizeLabel.text=File size: +EditRulePanel.descriptionLabel.text=Description: +EditRulePanel.jTable1.columnModel.title3=Title 4 +EditRulePanel.jTable1.columnModel.title2=Title 3 +EditRulePanel.jTable1.columnModel.title1=Title 2 +EditRulePanel.shouldAlertCheckBox.actionCommand= +EditFullPathsRulePanel.ruleNameLabel.text=Rule Set: +EditFullPathsRulePanel.descriptionLabel.text=Description: +EditFullPathsRulePanel.descriptionTextField.text= +EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand= +EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditFullPathsRulePanel.ruleNameTextField.text= +EditFullPathsRulePanel.fullPathsLabel.text=Full paths: +EditFullPathsRulePanel.fullPathsLabel.toolTipText= +EditNonFullPathsRulePanel.ruleNameTextField.text= +EditNonFullPathsRulePanel.ruleNameLabel.text=Rule Set: +EditNonFullPathsRulePanel.descriptionLabel.text=Description: +EditNonFullPathsRulePanel.descriptionTextField.text= +EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditNonFullPathsRulePanel.daysIncludedLabel.text=day(s) +EditNonFullPathsRulePanel.modifiedDateLabel.text=Modified Within: +EditNonFullPathsRulePanel.fileSizeLabel.text=File size (bytes): +EditNonFullPathsRulePanel.folderNamesLabel.text=Folder names: +EditNonFullPathsRulePanel.filenamesLabel.text=File names: +EditNonFullPathsRulePanel.extensionsTextField.text= +EditNonFullPathsRulePanel.extensionsLabel.text=Extensions: +EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand= +EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +ConfigVisualPanel1.browseButton.text=Open +ConfigVisualPanel2.fullPathsTable.columnModel.title0= +ConfigVisualPanel2.folderNamesTable.columnModel.title0= +ConfigVisualPanel2.shouldSaveCheckBox.toolTipText= +NewRuleSetPanel.chooseLabel.text=Choose type of file rule +EditNonFullPathsRulePanel.minSizeLabel.text=Minimum: +EditNonFullPathsRulePanel.maxSizeLabel.text=Maximum: +EditNonFullPathsRulePanel.minSizeTextField.text= +EditNonFullPathsRulePanel.maxSizeTextField.text= +ConfigVisualPanel2.maxSizeTextField.text= +ConfigVisualPanel2.maxSizeLabel.text=Maximum: +ConfigVisualPanel2.minSizeTextField.text= +ConfigVisualPanel2.minSizeLabel.text=Minimum: +EditNonFullPathsRulePanel.minDaysTextField.text=jFormattedTextField1 diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties-MERGED new file mode 100644 index 0000000000..57a5c6954c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties-MERGED @@ -0,0 +1,176 @@ +ConfigureLogicalImager.title=Configure Logical Imager +ConfigureLogicalImagerDialog.loadButton.text=Load +ConfigureLogicalImagerDialog.newButton.text=New +ConfigureLogicalImagerDialog.title=Configure Logical Imager +ConfigureLogicalImagerDialog.saveButton.text=Save +ConfigureLogicalImagerDialog.configFile.text= +ConfigureLogicalImagerDialog.configFile.toolTipText= +ConfigureLogicalImagerDialog.jLabel1.text=Rule Set: +ConfigureLogicalImagerDialog.finalizeImageWriter.text=Finalize Image Writer +ConfigureLogicalImagerDialog.newRuleButton.text=New Rule +ConfigureLogicalImagerDialog.editRuleButton.text=Edit Rule +ConfigureLogicalImagerDialog.deleteRuleButton.text=Delete Rule +ConfigureLogicalImagerDialog.jLabel2.text=Rule Details +ConfigureLogicalImagerDialog.shouldSaveCheckBox.text=Should Save +ConfigureLogicalImagerDialog.shouldAlertCheckBox.text=Should Alert +ConfigureLogicalImagerDialog.jLabel3.text=Extensions: +ConfigureLogicalImagerDialog.extensionsTextField.text= +ConfigureLogicalImagerDialog.filenamesLabel.text=File names: +ConfigureLogicalImagerDialog.folderNamesLabel.text=Folder names: +ConfigureLogicalImagerDialog.jLabel4.text=File size: +ConfigureLogicalImagerDialog.daysIncludedLabel.text=day(s) +ConfigureLogicalImagerDialog.daysIncludedTextField.text= +ConfigureLogicalImagerDialog.modifiedDateLabel.text=Modified Within: +ConfigureLogicalImagerDialog.fullPathsLabel.text=Full paths: +ConfigureLogicalImagerDialog.flagEncryptionProgramsCheckBox.text=Flag encryption programs +ConfigVisualPanel1.chooseFileTitle=Select a Logical Imager configuration json file +# {0} - filename +ConfigVisualPanel1.configFileIsEmpty=Configuration file {0} is empty +ConfigVisualPanel1.configurationError=Configuration error +ConfigVisualPanel1.fileNameExtensionFilter=configuration json file +ConfigVisualPanel1.invalidConfigJson=Invalid config json: +ConfigVisualPanel1.selectConfigurationFile=Select configuration file +ConfigVisualPanel2.cancel=Cancel +ConfigVisualPanel2.deleteRuleSet=Delete rule set +ConfigVisualPanel2.deleteRuleSetConfirmation=Delete rule set confirmation +ConfigVisualPanel2.editConfiguration=Edit configuration +ConfigVisualPanel2.editRuleError=Edit rule error +ConfigVisualPanel2.editRuleSet=Edit rule set +ConfigVisualPanel2.ok=OK +ConfigVisualPanel2.rulesTable.columnModel.title0=Rule set +ConfigVisualPanel2.rulesTable.columnModel.title1=Description +# {0} - configFilename +ConfigWizardPanel2.failedToSaveConfigMsg=Failed to save configuration file: {0} +ConfigWizardPanel2.failedToSaveExeMsg=Failed to save tsk_logical_imager.exe file +# {0} - reason +ConfigWizardPanel2.reason=\nReason: +CTL_ConfigureLogicalImager=Configure Logical Imager +EditFullPathsRulePanel.example=Example: +EditFullPathsRulePanel.fullPaths=Full paths +EditNonFullPathsRulePanel.emptyExtensionException=Extensions cannot have an empty entry +EditNonFullPathsRulePanel.example=Example: +EditNonFullPathsRulePanel.fileNames=File names +EditNonFullPathsRulePanel.folderNames=Folder names +# {0} - message +EditNonFullPathsRulePanel.maxFileSizeMustBeNumberException=Maximum file size must be a number: {0} +EditNonFullPathsRulePanel.maxFileSizeNotPositiveException=Maximum file size must be a positive +# {0} - maxFileSize +# {1} - minFileSize +EditNonFullPathsRulePanel.maxFileSizeSmallerThanMinException=Maximum file size: {0} must be bigger than minimum file size: {1} +# {0} - message +EditNonFullPathsRulePanel.minFileSizeMustBeNumberException=Minimum file size must be a number: {0} +EditNonFullPathsRulePanel.minFileSizeNotPositiveException=Minimum file size must be a positive +# {0} - message +EditNonFullPathsRulePanel.modifiedDaysMustBeNumberException=Modified days must be a number: {0} +EditNonFullPathsRulePanel.modifiedDaysNotPositiveException=Modified days must be a positive +# ({0} - fieldName +EditRulePanel.blankLineException={0} cannot have a blank line +EditRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +EditRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditRulePanel.fullPathsLabel.text=Full paths: +EditRulePanel.daysIncludedLabel.text=day(s) +EditRulePanel.daysIncludedTextField.text= +EditRulePanel.modifiedDateLabel.text=Modified Within: +EditRulePanel.folderNamesLabel.text=Folder names: +EditRulePanel.filenamesLabel.text=File names: +EditRulePanel.extensionsTextField.text= +ConfigureLogicalImagerDialog.jLabel5.text=Rule Name: +ConfigureLogicalImagerDialog.jLabel6.text=Description: +ConfigureLogicalImagerDialog.ruleNameEditTextField.text= +ConfigureLogicalImagerDialog.descriptionEditTextField.text= +ConfigureLogicalImagerDialog.fullPathsTable.columnModel.title0= +ConfigVisualPanel1.jTextField1.text= +ConfigVisualPanel1.jLabel1.text=Configuration file +ConfigVisualPanel2.jCheckBox1.text=jCheckBox1 +ConfigVisualPanel2.jCheckBox2.text=jCheckBox2 +ConfigVisualPanel2.jTextField1.text=jTextField1 +ConfigVisualPanel1.jRadioButton1.text=Create new configuration +ConfigVisualPanel1.jRadioButton2.text=Open existing configuration +ConfigVisualPanel1.jLabel1.text_1=Configuration file: +ConfigVisualPanel1.newRadioButton.text_1=Create a new configuration +ConfigVisualPanel1.loadRadioButton.text_1=Open an existing configuration +ConfigVisualPanel1.configFileTextField.text_1= +ConfigVisualPanel2.modifiedDateLabel.text=Modified Within: +ConfigVisualPanel2.folderNamesLabel.text=Folder names: +ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed +ConfigVisualPanel2.filenamesLabel.text=File names: +ConfigVisualPanel2.extensionsTextField.text= +ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console if rule matches +ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file if it matches a rule +ConfigVisualPanel2.deleteRuleButton.text=Delete Rule Set +ConfigVisualPanel2.descriptionEditTextField.text= +ConfigVisualPanel2.editRuleButton.text=Edit Rule Set +ConfigVisualPanel2.newRuleButton.text=New Rule Set +ConfigVisualPanel2.ruleNameEditTextField.text= +ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption programs are found +ConfigVisualPanel2.fullPathsLabel.text=Full paths: +ConfigVisualPanel2.daysIncludedLabel.text=day(s) +ConfigVisualPanel2.daysIncludedTextField.text= +ConfigVisualPanel2.configFileTextField.toolTipText= +ConfigVisualPanel2.configFileTextField.text= +ConfigVisualPanel2.filenamesTable.columnModel.title0= +ConfigVisualPanel2.fileSizeLabel.text=File size in bytes: +ConfigVisualPanel2.extensionsLabel.text=Extensions: +ConfigVisualPanel2.descriptionLabel.text=Description: +ConfigVisualPanel2.ruleNameLabel.text=Rule Set: +ConfigVisualPanel2.ruleSetFileLabel.text=Configuration rule file: +EditRulePanel.ruleNameLabel.text=Rule Set: +EditRulePanel.descriptionTextField.text= +EditRulePanel.extensionsLabel.text=Extensions: +EditRulePanel.ruleNameTextField.text= +EditRulePanel.extensionsCheckBox.text= +EditRulePanel.filenamesCheckBox.text= +EditRulePanel.folderNamesCheckBox.text= +EditRulePanel.fullPathsCheckBox.text= +EditRulePanel.fileSizeCheckBox.text= +EditRulePanel.minDaysCheckBox.text= +EditRulePanel.fileSizeLabel.text=File size: +EditRulePanel.descriptionLabel.text=Description: +EditRulePanel.jTable1.columnModel.title3=Title 4 +EditRulePanel.jTable1.columnModel.title2=Title 3 +EditRulePanel.jTable1.columnModel.title1=Title 2 +EditRulePanel.shouldAlertCheckBox.actionCommand= +EditFullPathsRulePanel.ruleNameLabel.text=Rule Set: +EditFullPathsRulePanel.descriptionLabel.text=Description: +EditFullPathsRulePanel.descriptionTextField.text= +EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand= +EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditFullPathsRulePanel.ruleNameTextField.text= +EditFullPathsRulePanel.fullPathsLabel.text=Full paths: +EditFullPathsRulePanel.fullPathsLabel.toolTipText= +EditNonFullPathsRulePanel.ruleNameTextField.text= +EditNonFullPathsRulePanel.ruleNameLabel.text=Rule Set: +EditNonFullPathsRulePanel.descriptionLabel.text=Description: +EditNonFullPathsRulePanel.descriptionTextField.text= +EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditNonFullPathsRulePanel.daysIncludedLabel.text=day(s) +EditNonFullPathsRulePanel.modifiedDateLabel.text=Modified Within: +EditNonFullPathsRulePanel.fileSizeLabel.text=File size (bytes): +EditNonFullPathsRulePanel.folderNamesLabel.text=Folder names: +EditNonFullPathsRulePanel.filenamesLabel.text=File names: +EditNonFullPathsRulePanel.extensionsTextField.text= +EditNonFullPathsRulePanel.extensionsLabel.text=Extensions: +EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand= +EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +ConfigVisualPanel1.browseButton.text=Open +ConfigVisualPanel2.fullPathsTable.columnModel.title0= +ConfigVisualPanel2.folderNamesTable.columnModel.title0= +ConfigVisualPanel2.shouldSaveCheckBox.toolTipText= +EditRulePanel.validateRuleNameExceptionMsg=Rule name cannot be empty +EncryptionProgramsRule.encryptionProgramsRuleDescription=Find encryption programs +EncryptionProgramsRule.encryptionProgramsRuleName=Encryption Programs +LogicalImagerConfigDeserializer.fullPathsException=A rule with full-paths cannot have other rule definitions +LogicalImagerConfigDeserializer.missingRuleSetException=Missing rule-set +# {0} - key +LogicalImagerConfigDeserializer.unsupportedKeyException=Unsupported key: {0} +NewRuleSetPanel.chooseLabel.text=Choose type of file rule +EditNonFullPathsRulePanel.minSizeLabel.text=Minimum: +EditNonFullPathsRulePanel.maxSizeLabel.text=Maximum: +EditNonFullPathsRulePanel.minSizeTextField.text= +EditNonFullPathsRulePanel.maxSizeTextField.text= +ConfigVisualPanel2.maxSizeTextField.text= +ConfigVisualPanel2.maxSizeLabel.text=Maximum: +ConfigVisualPanel2.minSizeTextField.text= +ConfigVisualPanel2.minSizeLabel.text=Minimum: +EditNonFullPathsRulePanel.minDaysTextField.text=jFormattedTextField1 diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.form b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.form new file mode 100644 index 0000000000..4df1ad428f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.form @@ -0,0 +1,119 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.java new file mode 100644 index 0000000000..795559ca92 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel1.java @@ -0,0 +1,288 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.openide.util.NbBundle; + +/** + * Configuration Visual Panel 1 + */ +public final class ConfigVisualPanel1 extends JPanel implements DocumentListener { + + private LogicalImagerConfig config; + private String configFilename; + private boolean newFile = true; + + /** + * Creates new form ConfigVisualPanel1 + */ + public ConfigVisualPanel1() { + initComponents(); + configFileTextField.getDocument().addDocumentListener(new MyDocumentListener(this)); + } + + @NbBundle.Messages({ + "ConfigVisualPanel1.selectConfigurationFile=Select configuration file" + }) + @Override + public String getName() { + return Bundle.ConfigVisualPanel1_selectConfigurationFile(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + newRadioButton = new javax.swing.JRadioButton(); + loadRadioButton = new javax.swing.JRadioButton(); + jLabel1 = new javax.swing.JLabel(); + configFileTextField = new javax.swing.JTextField(); + browseButton = new javax.swing.JButton(); + + buttonGroup1.add(newRadioButton); + newRadioButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(newRadioButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.newRadioButton.text_1")); // NOI18N + newRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newRadioButtonActionPerformed(evt); + } + }); + + buttonGroup1.add(loadRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(loadRadioButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.loadRadioButton.text_1")); // NOI18N + loadRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + loadRadioButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.jLabel1.text_1")); // NOI18N + + configFileTextField.setEditable(false); + configFileTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.configFileTextField.text_1")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.browseButton.text")); // NOI18N + browseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(30, 30, 30) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(loadRadioButton) + .addComponent(newRadioButton))) + .addGroup(layout.createSequentialGroup() + .addGap(38, 38, 38) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1) + .addComponent(configFileTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 287, Short.MAX_VALUE)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseButton) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(30, 30, 30) + .addComponent(newRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(loadRadioButton) + .addGap(37, 37, 37) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(configFileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton)) + .addContainerGap(141, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void newRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newRadioButtonActionPerformed + configFileTextField.setText(""); + }//GEN-LAST:event_newRadioButtonActionPerformed + + private void loadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_loadRadioButtonActionPerformed + configFileTextField.setText(""); + }//GEN-LAST:event_loadRadioButtonActionPerformed + + @NbBundle.Messages({ + "ConfigVisualPanel1.chooseFileTitle=Select a Logical Imager configuration json file" + }) + private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + chooseFile(Bundle.ConfigVisualPanel1_chooseFileTitle()); + }//GEN-LAST:event_browseButtonActionPerformed + + @NbBundle.Messages({ + "ConfigVisualPanel1.fileNameExtensionFilter=configuration json file", + "ConfigVisualPanel1.invalidConfigJson=Invalid config json: ", + "ConfigVisualPanel1.configurationError=Configuration error", + }) + private void chooseFile(String title) { + final String jsonExt = ".json"; // NON-NLS + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle(title); + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + FileFilter filter = new FileNameExtensionFilter(Bundle.ConfigVisualPanel1_fileNameExtensionFilter(), new String[] {"json"}); // NON-NLS + fileChooser.setFileFilter(filter); + fileChooser.setMultiSelectionEnabled(false); + if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + String path = fileChooser.getSelectedFile().getPath(); + if (new File(path).exists()) { + try { + loadConfigFile(path); + configFilename = path; + configFileTextField.setText(path); + newFile = false; + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, + Bundle.ConfigVisualPanel1_invalidConfigJson() + ex.getMessage() , + Bundle.ConfigVisualPanel1_configurationError(), + JOptionPane.ERROR_MESSAGE); + } + } else { + if (!path.endsWith(jsonExt)) { + path += jsonExt; + } + configFilename = path; + configFileTextField.setText(path); + config = new LogicalImagerConfig(); + newFile = true; + } + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseButton; + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.JTextField configFileTextField; + private javax.swing.JLabel jLabel1; + private javax.swing.JRadioButton loadRadioButton; + private javax.swing.JRadioButton newRadioButton; + // End of variables declaration//GEN-END:variables + + @NbBundle.Messages({ + "# {0} - filename", + "ConfigVisualPanel1.configFileIsEmpty=Configuration file {0} is empty", + }) + private void loadConfigFile(String path) throws FileNotFoundException, JsonIOException, JsonSyntaxException, IOException { + try (FileInputStream is = new FileInputStream(path)) { + InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); + GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting(); + gsonBuilder.registerTypeAdapter(LogicalImagerConfig.class, new LogicalImagerConfigDeserializer()); + Gson gson = gsonBuilder.create(); + config = gson.fromJson(reader, LogicalImagerConfig.class); + if (config == null) { + // This happens if the file is empty. Gson doesn't call the deserializer and doesn't throw any exception. + throw new IOException(Bundle.ConfigVisualPanel1_configFileIsEmpty(path)); + } + } + } + + public LogicalImagerConfig getConfig() { + return config; + } + + public String getConfigFilename() { + return configFilename; + } + + public boolean isNewFile() { + return newFile; + } + + void setConfigFilename(String filename) { + configFileTextField.setText(filename); + } + + @Override + public void insertUpdate(DocumentEvent e) { + } + + @Override + public void removeUpdate(DocumentEvent e) { + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + + public boolean isPanelValid() { + return (newFile || !configFileTextField.getText().isEmpty()); + } + + private static class MyDocumentListener implements DocumentListener { + + private final ConfigVisualPanel1 panel; + + public MyDocumentListener(ConfigVisualPanel1 aThis) { + this.panel = aThis; + } + + @Override + public void insertUpdate(DocumentEvent e) { + fireChange(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + fireChange(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + fireChange(); + } + + private void fireChange() { + panel.firePropertyChange("UPDATE_UI", false, true); // NON-NLS + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.form b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.form new file mode 100644 index 0000000000..26255667c4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.form @@ -0,0 +1,616 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + <ResourceString bundle="org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties" key="ConfigVisualPanel2.fullPathsTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + + + + + + + + + + + +
    +
    + + + + + <ResourceString bundle="org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties" key="ConfigVisualPanel2.filenamesTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + + + + + + + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + <ResourceString bundle="org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties" key="ConfigVisualPanel2.folderNamesTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + + + + + + + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + <ResourceString bundle="org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties" key="ConfigVisualPanel2.rulesTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + <ResourceString bundle="org/sleuthkit/autopsy/configurelogicalimager/Bundle.properties" key="ConfigVisualPanel2.rulesTable.columnModel.title1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + + + + + + + + +
    + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.java new file mode 100644 index 0000000000..5f7ed7bf54 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigVisualPanel2.java @@ -0,0 +1,952 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.swing.JButton; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.openide.util.NbBundle; + +/** + * Configuration Visual Panel 2 + */ +@NbBundle.Messages({ + "ConfigVisualPanel2.ok=OK", + "ConfigVisualPanel2.cancel=Cancel" +}) +public final class ConfigVisualPanel2 extends JPanel { + + private static final List EMPTY_LIST = new ArrayList<>(); + private String configFilename; + private LogicalImagerConfig config = null; + private final JButton okButton = new JButton(Bundle.ConfigVisualPanel2_ok()); + private final JButton cancelButton = new JButton(Bundle.ConfigVisualPanel2_cancel()); + private boolean flagEncryptionPrograms = false; + + /** + * Creates new form ConfigVisualPanel2 + */ + public ConfigVisualPanel2() { + initComponents(); + if (config != null) { + updatePanel(configFilename, config); + } + } + + @NbBundle.Messages({ + "ConfigVisualPanel2.editConfiguration=Edit configuration" + }) + @Override + public String getName() { + return Bundle.ConfigVisualPanel2_editConfiguration(); + } + + public boolean isFlagEncryptionPrograms() { + return flagEncryptionPrograms; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + modifiedDateLabel = new javax.swing.JLabel(); + daysIncludedTextField = new javax.swing.JTextField(); + daysIncludedLabel = new javax.swing.JLabel(); + fullPathsLabel = new javax.swing.JLabel(); + flagEncryptionProgramsCheckBox = new javax.swing.JCheckBox(); + ruleNameLabel = new javax.swing.JLabel(); + ruleNameEditTextField = new javax.swing.JTextField(); + newRuleButton = new javax.swing.JButton(); + descriptionLabel = new javax.swing.JLabel(); + editRuleButton = new javax.swing.JButton(); + descriptionEditTextField = new javax.swing.JTextField(); + deleteRuleButton = new javax.swing.JButton(); + jScrollPane5 = new javax.swing.JScrollPane(); + fullPathsTable = new javax.swing.JTable(); + jScrollPane6 = new javax.swing.JScrollPane(); + filenamesTable = new javax.swing.JTable(); + shouldSaveCheckBox = new javax.swing.JCheckBox(); + shouldAlertCheckBox = new javax.swing.JCheckBox(); + jScrollPane7 = new javax.swing.JScrollPane(); + folderNamesTable = new javax.swing.JTable(); + extensionsLabel = new javax.swing.JLabel(); + extensionsTextField = new javax.swing.JTextField(); + filenamesLabel = new javax.swing.JLabel(); + configFileTextField = new javax.swing.JTextField(); + ruleSetFileLabel = new javax.swing.JLabel(); + finalizeImageWriter = new javax.swing.JCheckBox(); + jScrollPane1 = new javax.swing.JScrollPane(); + rulesTable = new javax.swing.JTable(); + folderNamesLabel = new javax.swing.JLabel(); + fileSizeLabel = new javax.swing.JLabel(); + jSeparator1 = new javax.swing.JSeparator(); + minSizeLabel = new javax.swing.JLabel(); + minSizeTextField = new javax.swing.JFormattedTextField(); + maxSizeLabel = new javax.swing.JLabel(); + maxSizeTextField = new javax.swing.JFormattedTextField(); + + org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.modifiedDateLabel.text")); // NOI18N + + daysIncludedTextField.setEditable(false); + daysIncludedTextField.setHorizontalAlignment(javax.swing.JTextField.TRAILING); + daysIncludedTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.daysIncludedTextField.text")); // NOI18N + daysIncludedTextField.setEnabled(false); + daysIncludedTextField.setMinimumSize(new java.awt.Dimension(60, 20)); + daysIncludedTextField.setPreferredSize(new java.awt.Dimension(60, 20)); + + org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.daysIncludedLabel.text")); // NOI18N + daysIncludedLabel.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(fullPathsLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.fullPathsLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(flagEncryptionProgramsCheckBox, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text")); // NOI18N + flagEncryptionProgramsCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + flagEncryptionProgramsCheckBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.ruleNameLabel.text")); // NOI18N + + ruleNameEditTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.ruleNameEditTextField.text")); // NOI18N + ruleNameEditTextField.setEnabled(false); + + newRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newRuleButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.newRuleButton.text")); // NOI18N + newRuleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newRuleButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.descriptionLabel.text")); // NOI18N + + editRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/edit16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(editRuleButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.editRuleButton.text")); // NOI18N + editRuleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + editRuleButtonActionPerformed(evt); + } + }); + + descriptionEditTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.descriptionEditTextField.text")); // NOI18N + descriptionEditTextField.setEnabled(false); + + deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.deleteRuleButton.text")); // NOI18N + deleteRuleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteRuleButtonActionPerformed(evt); + } + }); + + fullPathsTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + "" + } + ) { + Class[] types = new Class [] { + java.lang.String.class + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + }); + fullPathsTable.setColumnSelectionAllowed(true); + fullPathsTable.setEnabled(false); + fullPathsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + fullPathsTable.setShowHorizontalLines(false); + fullPathsTable.setShowVerticalLines(false); + fullPathsTable.getTableHeader().setReorderingAllowed(false); + jScrollPane5.setViewportView(fullPathsTable); + fullPathsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); + if (fullPathsTable.getColumnModel().getColumnCount() > 0) { + fullPathsTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.fullPathsTable.columnModel.title0")); // NOI18N + } + + filenamesTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + "" + } + ) { + Class[] types = new Class [] { + java.lang.String.class + }; + boolean[] canEdit = new boolean [] { + false + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return canEdit [columnIndex]; + } + }); + filenamesTable.setEnabled(false); + filenamesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + filenamesTable.setShowHorizontalLines(false); + filenamesTable.setShowVerticalLines(false); + filenamesTable.getTableHeader().setReorderingAllowed(false); + jScrollPane6.setViewportView(filenamesTable); + if (filenamesTable.getColumnModel().getColumnCount() > 0) { + filenamesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.filenamesTable.columnModel.title0")); // NOI18N + } + + shouldSaveCheckBox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(shouldSaveCheckBox, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.shouldSaveCheckBox.text")); // NOI18N + shouldSaveCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.shouldSaveCheckBox.toolTipText")); // NOI18N + shouldSaveCheckBox.setEnabled(false); + + shouldAlertCheckBox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(shouldAlertCheckBox, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.shouldAlertCheckBox.text")); // NOI18N + shouldAlertCheckBox.setEnabled(false); + + folderNamesTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + "" + } + ) { + Class[] types = new Class [] { + java.lang.String.class + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + }); + folderNamesTable.setEnabled(false); + folderNamesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + folderNamesTable.setShowHorizontalLines(false); + folderNamesTable.setShowVerticalLines(false); + folderNamesTable.getTableHeader().setReorderingAllowed(false); + jScrollPane7.setViewportView(folderNamesTable); + if (folderNamesTable.getColumnModel().getColumnCount() > 0) { + folderNamesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.folderNamesTable.columnModel.title0")); // NOI18N + } + + org.openide.awt.Mnemonics.setLocalizedText(extensionsLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.extensionsLabel.text")); // NOI18N + + extensionsTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.extensionsTextField.text")); // NOI18N + extensionsTextField.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(filenamesLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.filenamesLabel.text")); // NOI18N + + configFileTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.configFileTextField.text")); // NOI18N + configFileTextField.setToolTipText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.configFileTextField.toolTipText")); // NOI18N + configFileTextField.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(ruleSetFileLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.ruleSetFileLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(finalizeImageWriter, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.finalizeImageWriter.text")); // NOI18N + finalizeImageWriter.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + finalizeImageWriterActionPerformed(evt); + } + }); + + rulesTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + "Rule Set", "Description" + } + ) { + Class[] types = new Class [] { + java.lang.String.class, java.lang.String.class + }; + boolean[] canEdit = new boolean [] { + false, false + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return canEdit [columnIndex]; + } + }); + rulesTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); + rulesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + rulesTable.setShowHorizontalLines(false); + rulesTable.setShowVerticalLines(false); + rulesTable.getTableHeader().setReorderingAllowed(false); + rulesTable.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + rulesTableMouseClicked(evt); + } + }); + rulesTable.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + rulesTableKeyReleased(evt); + } + }); + jScrollPane1.setViewportView(rulesTable); + if (rulesTable.getColumnModel().getColumnCount() > 0) { + rulesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.rulesTable.columnModel.title0")); // NOI18N + rulesTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.rulesTable.columnModel.title1")); // NOI18N + } + + org.openide.awt.Mnemonics.setLocalizedText(folderNamesLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.folderNamesLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(fileSizeLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.fileSizeLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(minSizeLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.minSizeLabel.text")); // NOI18N + + minSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); + minSizeTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.minSizeTextField.text")); // NOI18N + minSizeTextField.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(maxSizeLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.maxSizeLabel.text")); // NOI18N + + maxSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); + maxSizeTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.maxSizeTextField.text")); // NOI18N + maxSizeTextField.setEnabled(false); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(480, 480, 480) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(daysIncludedLabel)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(ruleNameEditTextField, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(descriptionEditTextField, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(extensionsTextField, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane5, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane6, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane7, javax.swing.GroupLayout.Alignment.LEADING)) + .addContainerGap()))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 377, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(newRuleButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(editRuleButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(deleteRuleButton))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(flagEncryptionProgramsCheckBox) + .addComponent(finalizeImageWriter))) + .addGroup(layout.createSequentialGroup() + .addGap(393, 393, 393) + .addComponent(shouldSaveCheckBox)) + .addGroup(layout.createSequentialGroup() + .addGap(397, 397, 397) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(extensionsLabel) + .addComponent(filenamesLabel) + .addComponent(descriptionLabel) + .addComponent(ruleNameLabel))) + .addGroup(layout.createSequentialGroup() + .addGap(397, 397, 397) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(modifiedDateLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(fileSizeLabel) + .addComponent(fullPathsLabel) + .addComponent(folderNamesLabel)) + .addGap(4, 4, 4) + .addComponent(minSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(maxSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addGap(393, 393, 393) + .addComponent(shouldAlertCheckBox))) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(17, 17, 17) + .addComponent(ruleSetFileLabel) + .addGap(18, 18, 18) + .addComponent(configFileTextField)) + .addGroup(layout.createSequentialGroup() + .addGap(397, 397, 397) + .addComponent(jSeparator1))) + .addGap(10, 10, 10)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(configFileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(ruleSetFileLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 527, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(newRuleButton) + .addComponent(editRuleButton) + .addComponent(deleteRuleButton))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(30, 30, 30) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(descriptionEditTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(descriptionLabel)) + .addGap(9, 9, 9) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(extensionsTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(extensionsLabel))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(ruleNameEditTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(ruleNameLabel))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane6, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(filenamesLabel) + .addGap(0, 0, Short.MAX_VALUE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane7, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(folderNamesLabel) + .addGap(0, 0, Short.MAX_VALUE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(fullPathsLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(jScrollPane5, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGap(11, 11, 11))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(minSizeLabel) + .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(maxSizeLabel) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(fileSizeLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(modifiedDateLabel) + .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(daysIncludedLabel)) + .addGap(3, 3, 3) + .addComponent(shouldAlertCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(shouldSaveCheckBox) + .addGap(18, 18, 18) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(flagEncryptionProgramsCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(finalizeImageWriter))) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void finalizeImageWriterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_finalizeImageWriterActionPerformed + config.setFinalizeImageWriter(finalizeImageWriter.isSelected()); + }//GEN-LAST:event_finalizeImageWriterActionPerformed + + private void rulesTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rulesTableMouseClicked + rulesTableSelect(); + }//GEN-LAST:event_rulesTableMouseClicked + + private void rulesTableKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_rulesTableKeyReleased + rulesTableSelect(); + }//GEN-LAST:event_rulesTableKeyReleased + + @NbBundle.Messages({ + "ConfigVisualPanel2.editRuleSet=Edit rule set", + "ConfigVisualPanel2.editRuleError=Edit rule error" + }) + private void editRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleButtonActionPerformed + int row = rulesTable.getSelectedRow(); + if (row != -1) { + String ruleName = (String) rulesTable.getModel().getValueAt(row, 0); + LogicalImagerRule rule = config.getRuleSet().get(ruleName); + EditRulePanel editPanel = new EditRulePanel(okButton, cancelButton, ruleName, rule); + editPanel.setEnabled(true); + editPanel.setVisible(true); + + while (true) { + int option = JOptionPane.showOptionDialog(this, editPanel.getPanel(), Bundle.ConfigVisualPanel2_editRuleSet(), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, + null, new Object[]{okButton, cancelButton}, okButton); + if (option == JOptionPane.OK_OPTION) { + try { + ImmutablePair ruleMap = editPanel.toRule(); + appendRow(ruleMap); + break; + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, + ex.getMessage(), + Bundle.ConfigVisualPanel2_editRuleError(), + JOptionPane.ERROR_MESSAGE); + // let user fix the error + } + } else { + break; + } + } + } + }//GEN-LAST:event_editRuleButtonActionPerformed + + private void newRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newRuleButtonActionPerformed + NewRuleSetPanel panel; + panel = new NewRuleSetPanel(okButton, cancelButton); + panel.setEnabled(true); + panel.setVisible(true); + + while (true) { + int option = JOptionPane.showOptionDialog(this, panel, "New rule set", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, + null, new Object[]{okButton, cancelButton}, okButton); + if (option == JOptionPane.OK_OPTION) { + try { + // Save the new rule + ImmutablePair ruleMap = panel.toRule(); + appendRow(ruleMap); + break; + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, + ex.getMessage(), + "New rule error", + JOptionPane.ERROR_MESSAGE); + // let user fix the error + } + } else { + break; + } + } + }//GEN-LAST:event_newRuleButtonActionPerformed + + @NbBundle.Messages({ + "ConfigVisualPanel2.deleteRuleSet=Delete rule set ", + "ConfigVisualPanel2.deleteRuleSetConfirmation=Delete rule set confirmation", + }) + private void deleteRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteRuleButtonActionPerformed + int index = rulesTable.getSelectedRow(); + if (index != -1) { + String ruleName = (String) rulesTable.getModel().getValueAt(index, 0); + + int option = JOptionPane.showOptionDialog(this, + Bundle.ConfigVisualPanel2_deleteRuleSet() + ruleName, + Bundle.ConfigVisualPanel2_deleteRuleSetConfirmation(), + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); + if (option == JOptionPane.NO_OPTION) { + return; + } + + config.getRuleSet().remove(ruleName); + updatePanel(configFilename, config); + if (rulesTable.getRowCount() > 0) { + rulesTable.setRowSelectionInterval(0, 0); + rulesTableSelect(); + } + } + }//GEN-LAST:event_deleteRuleButtonActionPerformed + + private void flagEncryptionProgramsCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_flagEncryptionProgramsCheckBoxActionPerformed + flagEncryptionPrograms = flagEncryptionProgramsCheckBox.isSelected(); + toggleEncryptionProgramsRule(flagEncryptionPrograms); + }//GEN-LAST:event_flagEncryptionProgramsCheckBoxActionPerformed + + private void toggleEncryptionProgramsRule(boolean flagEncryptionPrograms) { + if (flagEncryptionPrograms) { + // add the special rule + ImmutablePair ruleMap = createEncryptionProgramsRule(); + appendRow(ruleMap); + } else { + // remove it + int index = ((RulesTableModel) rulesTable.getModel()).findRow(EncryptionProgramsRule.getName()); + if (index != -1) { + config.getRuleSet().remove(EncryptionProgramsRule.getName()); + updatePanel(configFilename, config); + if (rulesTable.getRowCount() > 0) { + rulesTable.setRowSelectionInterval(0, 0); + rulesTableSelect(); + } + } + } + } + + /* + * Create an encryption programs rule + */ + private ImmutablePair createEncryptionProgramsRule() { + LogicalImagerRule.Builder builder = new LogicalImagerRule.Builder(); + builder.description(EncryptionProgramsRule.getDescription()) + .shouldAlert(true) + .shouldSave(true) + .filenames(EncryptionProgramsRule.getFilenames()); + LogicalImagerRule rule = builder.build(); + return new ImmutablePair<>(EncryptionProgramsRule.getName(), rule); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTextField configFileTextField; + private javax.swing.JLabel daysIncludedLabel; + private javax.swing.JTextField daysIncludedTextField; + private javax.swing.JButton deleteRuleButton; + private javax.swing.JTextField descriptionEditTextField; + private javax.swing.JLabel descriptionLabel; + private javax.swing.JButton editRuleButton; + private javax.swing.JLabel extensionsLabel; + private javax.swing.JTextField extensionsTextField; + private javax.swing.JLabel fileSizeLabel; + private javax.swing.JLabel filenamesLabel; + private javax.swing.JTable filenamesTable; + private javax.swing.JCheckBox finalizeImageWriter; + private javax.swing.JCheckBox flagEncryptionProgramsCheckBox; + private javax.swing.JLabel folderNamesLabel; + private javax.swing.JTable folderNamesTable; + private javax.swing.JLabel fullPathsLabel; + private javax.swing.JTable fullPathsTable; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane5; + private javax.swing.JScrollPane jScrollPane6; + private javax.swing.JScrollPane jScrollPane7; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JLabel maxSizeLabel; + private javax.swing.JFormattedTextField maxSizeTextField; + private javax.swing.JLabel minSizeLabel; + private javax.swing.JFormattedTextField minSizeTextField; + private javax.swing.JLabel modifiedDateLabel; + private javax.swing.JButton newRuleButton; + private javax.swing.JTextField ruleNameEditTextField; + private javax.swing.JLabel ruleNameLabel; + private javax.swing.JLabel ruleSetFileLabel; + private javax.swing.JTable rulesTable; + private javax.swing.JCheckBox shouldAlertCheckBox; + private javax.swing.JCheckBox shouldSaveCheckBox; + // End of variables declaration//GEN-END:variables + + private void updatePanel(String configFilePath, LogicalImagerConfig config, String rowSelectionkey) { + configFileTextField.setText(configFilePath); + finalizeImageWriter.setSelected(config.isFinalizeImageWriter()); + Map ruleSet = config.getRuleSet(); + flagEncryptionProgramsCheckBox.setSelected(ruleSet.get(EncryptionProgramsRule.getName()) != null); + RulesTableModel rulesTableModel = new RulesTableModel(); + int row = 0; + int selectThisRow = 0; + for (Map.Entry rule : ruleSet.entrySet()) { + rulesTableModel.setValueAt(rule.getKey(), row, 0); + if (rowSelectionkey != null && rule.getKey().equals(rowSelectionkey)) { + selectThisRow = row; + } + rulesTableModel.setValueAt(rule.getValue().getDescription(), row, 1); + rulesTableModel.setValueAt(rule.getValue(), row, 2); + row++; + } + rulesTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + rulesTable.setModel(rulesTableModel); + // If there are any rules, select the first one + if (rulesTableModel.getRowCount() > 0) { + rulesTable.setRowSelectionInterval(selectThisRow, selectThisRow); + rulesTableSelect(); + } else { + updateRuleSetButtons(false); + } + } + + private void updatePanel(String configFilePath, LogicalImagerConfig config) { + updatePanel(configFilePath, config, null); + } + + private void rulesTableSelect() { + int index = rulesTable.getSelectedRow(); + if (index != -1) { + String ruleName = (String) rulesTable.getModel().getValueAt(index, 0); + String description = (String) rulesTable.getModel().getValueAt(index, 1); + updateRuleDetails(ruleName, description, config); + updateRuleSetButtons(true); + } else { + updateRuleSetButtons(false); + } + } + + private void updateRuleDetails(String ruleName, String description, LogicalImagerConfig config) { + clearRuleDetails(); + LogicalImagerRule rule = config.getRuleSet().get(ruleName); + shouldAlertCheckBox.setSelected(rule.isShouldAlert()); + shouldSaveCheckBox.setSelected(rule.isShouldSave()); + ruleNameEditTextField.setText(ruleName); + descriptionEditTextField.setText(description); + updateExtensions(rule.getExtensions()); + updateList(filenamesTable, rule.getFilenames()); + updateList(folderNamesTable, rule.getPaths()); + updateList(fullPathsTable, rule.getFullPaths()); + if (rule.getMinFileSize() == null) { + minSizeTextField.setText(""); + } else { + minSizeTextField.setText(rule.getMinFileSize().toString()); + } + if (rule.getMaxFileSize() == null) { + maxSizeTextField.setText(""); + } else { + maxSizeTextField.setText(rule.getMaxFileSize().toString()); + } + if (rule.getMinDays() == null) { + daysIncludedTextField.setText(""); + } else { + daysIncludedTextField.setText(Integer.toString(rule.getMinDays())); + } + } + + private void clearRuleDetails() { + extensionsTextField.setText(""); + shouldAlertCheckBox.setSelected(false); + shouldSaveCheckBox.setSelected(false); + } + + private void updateExtensions(List extensions) { + extensionsTextField.setText(""); + if (extensions == null) { + return; + } + String content = ""; + boolean first = true; + for (String ext : extensions) { + content += (first ? "" : ",") + ext; + first = false; + } + extensionsTextField.setText(content); + } + + private void updateList(javax.swing.JTable jTable, List set) { + SingleColumnTableModel tableModel = new SingleColumnTableModel(); + jTable.setTableHeader(null); + if (set == null) { + jTable.setModel(tableModel); + return; + } + int row = 0; + for (String s : set) { + tableModel.setValueAt(s, row, 0); + row++; + } + jTable.setModel(tableModel); + } + + void setConfiguration(String configFilename, LogicalImagerConfig config, boolean newFile) { + this.configFilename = configFilename; + this.config = config; + if (newFile) { + initPanel(); + } + updatePanel(configFilename, config); + } + + private void initPanel() { + configFileTextField.setText(""); + rulesTable.setModel(new RulesTableModel()); + shouldAlertCheckBox.setSelected(true); + shouldSaveCheckBox.setSelected(false); + ruleNameEditTextField.setText(""); + descriptionEditTextField.setText(""); + extensionsTextField.setText(""); + updateList(filenamesTable, EMPTY_LIST); + updateList(folderNamesTable, EMPTY_LIST); + } + + boolean isPanelValid() { + return true; + } + + private void appendRow(ImmutablePair ruleMap) { + config.getRuleSet().put(ruleMap.getKey(), ruleMap.getValue()); + updatePanel(configFilename, config, ruleMap.getKey()); + } + + private void updateRuleSetButtons(boolean isRowSelected) { + newRuleButton.setEnabled(true); + editRuleButton.setEnabled(isRowSelected); + deleteRuleButton.setEnabled(isRowSelected); + } + + private class RulesTableModel extends AbstractTableModel { + + private final List ruleName = new ArrayList<>(); + private final List ruleDescription = new ArrayList<>(); + private final List rule = new ArrayList<>(); + + public int findRow(String name) { + return ruleName.indexOf(name); + } + + @Override + public int getRowCount() { + return ruleName.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @NbBundle.Messages({ + "ConfigVisualPanel2.rulesTable.columnModel.title0=Rule set", + "ConfigVisualPanel2.rulesTable.columnModel.title1=Description" + }) + @Override + public String getColumnName(int column) { + String colName = null; + switch (column) { + case 0: + colName = Bundle.ConfigVisualPanel2_rulesTable_columnModel_title0(); + break; + case 1: + colName = Bundle.ConfigVisualPanel2_rulesTable_columnModel_title1(); + break; + default: + break; + } + return colName; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + Object ret = null; + switch (columnIndex) { + case 0: + ret = ruleName.get(rowIndex); + break; + case 1: + ret = ruleDescription.get(rowIndex); + break; + case 2: + ret = rule.get(rowIndex); + break; + default: + throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS + } + return ret; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + ruleName.add((String) aValue); + break; + case 1: + ruleDescription.add((String) aValue); + break; + case 2: + rule.add((LogicalImagerRule) aValue); + break; + default: + throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS + } + // Only show the name and description column + if (columnIndex < 2) { + super.setValueAt(aValue, rowIndex, columnIndex); + } + } + } + + private class SingleColumnTableModel extends AbstractTableModel { + + private final List list = new ArrayList<>(); + + @Override + public int getRowCount() { + return list.size(); + } + + @Override + public int getColumnCount() { + return 1; + } + + @Override + public String getColumnName(int column) { + return ""; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + Object ret = null; + switch (columnIndex) { + case 0: + ret = list.get(rowIndex); + break; + default: + throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS + } + return ret; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return true; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + list.add((String) aValue); + break; + default: + throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel1.java new file mode 100644 index 0000000000..727880b1d5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel1.java @@ -0,0 +1,149 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.WizardDescriptor; +import org.openide.WizardValidationException; +import org.openide.util.HelpCtx; + +/** + * Configuration Wizard Panel 1 + */ +public class ConfigWizardPanel1 implements WizardDescriptor.ValidatingPanel { + + /** + * The visual component that displays this panel. If you need to access the + * component from this class, just use getComponent(). + */ + private ConfigVisualPanel1 component; + private String configFilename = null; + private LogicalImagerConfig config = null; + boolean isValid = false; + private boolean newFile = true; + + // Get the visual component for the panel. In this template, the component + // is kept separate. This can be more efficient: if the wizard is created + // but never displayed, or not all panels are displayed, it is better to + // create only those which really need to be visible. + @Override + public ConfigVisualPanel1 getComponent() { + if (component == null) { + component = new ConfigVisualPanel1(); + component.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("UPDATE_UI")) { // NON-NLS + isValid = component.isPanelValid(); + fireChangeEvent(); + } + } + }); + + } + return component; + } + + @Override + public HelpCtx getHelp() { + // Show no Help button for this panel: + return HelpCtx.DEFAULT_HELP; + // If you have context help: + // return new HelpCtx("help.key.here"); + } + + @Override + public boolean isValid() { + return isValid; + // If it depends on some condition (form filled out...) and + // this condition changes (last form field filled in...) then + // use ChangeSupport to implement add/removeChangeListener below. + // WizardDescriptor.ERROR/WARNING/INFORMATION_MESSAGE will also be useful. + } + + private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 + + /** + * Adds a listener to changes of the panel's validity. + * + * @param l the change listener to add + */ + @Override + public final void addChangeListener(ChangeListener l) { + synchronized (listeners) { + listeners.add(l); + } + } + + /** + * Removes a listener to changes of the panel's validity. + * + * @param l the change listener to move + */ + @Override + public final void removeChangeListener(ChangeListener l) { + synchronized (listeners) { + listeners.remove(l); + } + } + + /** + * This method is auto-generated. It seems that this method is used to + * listen to any change in this wizard panel. + */ + protected final void fireChangeEvent() { + Iterator it; + synchronized (listeners) { + it = new HashSet<>(listeners).iterator(); + } + ChangeEvent ev = new ChangeEvent(this); + while (it.hasNext()) { + it.next().stateChanged(ev); + } + } + + @Override + public void readSettings(WizardDescriptor wiz) { + // use wiz.getProperty to retrieve previous panel state + component.setConfigFilename((String) wiz.getProperty("configFilename")); // NON-NLS + } + + @Override + public void storeSettings(WizardDescriptor wiz) { + // use wiz.putProperty to remember current panel state + configFilename = component.getConfigFilename(); + config = component.getConfig(); + newFile = component.isNewFile(); + wiz.putProperty("configFilename", configFilename); // NON-NLS + wiz.putProperty("config", config); // NON-NLS + wiz.putProperty("newFile", newFile); // NON-NLS + } + + @Override + public void validate() throws WizardValidationException { + isValid = component.isPanelValid(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel2.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel2.java new file mode 100644 index 0000000000..f9fde8c9a8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigWizardPanel2.java @@ -0,0 +1,158 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import javax.swing.event.ChangeListener; +import org.apache.commons.io.FileUtils; +import org.openide.WizardDescriptor; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Configuration Wizard Panel 2 + */ +public class ConfigWizardPanel2 implements WizardDescriptor.Panel { + + private static final Logger LOGGER = Logger.getLogger(ConfigWizardPanel2.class.getName()); + + /** + * The visual component that displays this panel. If you need to access the + * component from this class, just use getComponent(). + */ + private ConfigVisualPanel2 component; + private String configFilename; + private LogicalImagerConfig config; + private boolean newFile; + boolean isValid = false; + + // Get the visual component for the panel. In this template, the component + // is kept separate. This can be more efficient: if the wizard is created + // but never displayed, or not all panels are displayed, it is better to + // create only those which really need to be visible. + @Override + public ConfigVisualPanel2 getComponent() { + if (component == null) { + component = new ConfigVisualPanel2(); + } + return component; + } + + @Override + public HelpCtx getHelp() { + // Show no Help button for this panel: + return HelpCtx.DEFAULT_HELP; + // If you have context help: + // return new HelpCtx("help.key.here"); + } + + @Override + public boolean isValid() { + // If it is always OK to press Next or Finish, then: + return true; + // If it depends on some condition (form filled out...) and + // this condition changes (last form field filled in...) then + // use ChangeSupport to implement add/removeChangeListener below. + // WizardDescriptor.ERROR/WARNING/INFORMATION_MESSAGE will also be useful. + } + + @Override + public void readSettings(WizardDescriptor wiz) { + // use wiz.getProperty to retrieve previous panel state + configFilename = (String) wiz.getProperty("configFilename"); // NON-NLS + config = (LogicalImagerConfig) wiz.getProperty("config"); // NON-NLS + newFile = (boolean) wiz.getProperty("newFile"); // NON-NLS + component.setConfiguration(configFilename, config, newFile); + } + + @Override + public void storeSettings(WizardDescriptor wiz) { + // use wiz.putProperty to remember current panel state + } + + @Override + public void addChangeListener(ChangeListener cl) { + } + + @Override + public void removeChangeListener(ChangeListener cl) { + } + + public String getConfigFilename() { + return configFilename; + } + + public LogicalImagerConfig getConfig() { + return config; + } + + @NbBundle.Messages({ + "# {0} - configFilename", + "ConfigWizardPanel2.failedToSaveConfigMsg=Failed to save configuration file: {0}", + "# {0} - reason", + "ConfigWizardPanel2.reason=\nReason: ", + "ConfigWizardPanel2.failedToSaveExeMsg=Failed to save tsk_logical_imager.exe file", + }) + public void saveConfigFile() { + GsonBuilder gsonBuilder = new GsonBuilder() + .setPrettyPrinting() + .excludeFieldsWithoutExposeAnnotation() + .disableHtmlEscaping(); + Gson gson = gsonBuilder.create(); + String toJson = gson.toJson(config); + try { + List lines = Arrays.asList(toJson.split("\\n")); + FileUtils.writeLines(new File(configFilename), "UTF-8", lines, System.getProperty("line.separator")); + } catch (IOException ex) { + JOptionPane.showMessageDialog(component, Bundle.ConfigWizardPanel2_failedToSaveConfigMsg(configFilename) + + Bundle.ConfigWizardPanel2_reason(ex.getMessage())); + } catch (JsonIOException jioe) { + LOGGER.log(Level.SEVERE, "Failed to save configuration file: " + configFilename, jioe); // NON-NLS + JOptionPane.showMessageDialog(component, Bundle.ConfigWizardPanel2_failedToSaveConfigMsg(configFilename) + + Bundle.ConfigWizardPanel2_reason(jioe.getMessage())); + } + try { + writeTskLogicalImagerExe(Paths.get(configFilename).getParent()); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Failed to save tsk_logical_imager.exe file", ex); // NON-NLS + JOptionPane.showMessageDialog(component, Bundle.ConfigWizardPanel2_failedToSaveExeMsg() + + Bundle.ConfigWizardPanel2_reason(ex.getMessage())); + } + } + + private void writeTskLogicalImagerExe(Path destDir) throws IOException { + try (InputStream in = getClass().getResourceAsStream("tsk_logical_imager.exe")) { // NON-NLS + File destFile = Paths.get(destDir.toString(), "tsk_logical_imager.exe").toFile(); // NON-NLS + FileUtils.copyInputStreamToFile(in, destFile); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigureLogicalImager.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigureLogicalImager.java new file mode 100644 index 0000000000..73ed437a04 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/ConfigureLogicalImager.java @@ -0,0 +1,85 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JComponent; +import org.openide.DialogDisplayer; +import org.openide.WizardDescriptor; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; + +/** + * Configuration Logical Imager + */ + +@ActionID( + category = "Tools", + id = "org.sleuthkit.autopsy.configurelogicalimager.ConfigureLogicalImager" +) +@ActionRegistration( + displayName = "#CTL_ConfigureLogicalImager" +) +@ActionReference(path = "Menu/Tools", position = 2000, separatorAfter = 2050) +@Messages("CTL_ConfigureLogicalImager=Configure Logical Imager") +public final class ConfigureLogicalImager implements ActionListener { + + @NbBundle.Messages({ + "ConfigureLogicalImager.title=Configure Logical Imager" + }) + @Override + public void actionPerformed(ActionEvent e) { + List> panels = new ArrayList<>(); + panels.add(new ConfigWizardPanel1()); + panels.add(new ConfigWizardPanel2()); + String[] steps = new String[panels.size()]; + for (int i = 0; i < panels.size(); i++) { + Component c = panels.get(i).getComponent(); + // Default step name to component name of panel. + steps[i] = c.getName(); + if (c instanceof JComponent) { // assume Swing components + JComponent jc = (JComponent) c; + jc.putClientProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, i); + jc.putClientProperty(WizardDescriptor.PROP_CONTENT_DATA, steps); + jc.putClientProperty(WizardDescriptor.PROP_AUTO_WIZARD_STYLE, true); + jc.putClientProperty(WizardDescriptor.PROP_CONTENT_DISPLAYED, true); + jc.putClientProperty(WizardDescriptor.PROP_CONTENT_NUMBERED, true); + } + } + WizardDescriptor wiz = new WizardDescriptor(new WizardDescriptor.ArrayIterator<>(panels)); + // {0} will be replaced by WizardDesriptor.Panel.getComponent().getName() + wiz.setTitleFormat(new MessageFormat("{0}")); // NON-NLS + wiz.setTitle(Bundle.ConfigureLogicalImager_title()); + if (DialogDisplayer.getDefault().notify(wiz) == WizardDescriptor.FINISH_OPTION) { + // do something + if (panels.get(1) instanceof ConfigWizardPanel2) { + ConfigWizardPanel2 panel = (ConfigWizardPanel2) panels.get(1); + panel.saveConfigFile(); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.form b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.form new file mode 100644 index 0000000000..c6585970b3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.form @@ -0,0 +1,138 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.java new file mode 100644 index 0000000000..58b1d0ffd6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditFullPathsRulePanel.java @@ -0,0 +1,265 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.openide.util.NbBundle; + +/** + * Edit full paths rule panel + */ +public class EditFullPathsRulePanel extends javax.swing.JPanel { + + private JButton okButton; + private JButton cancelButton; + List newFullPaths = new ArrayList<>(); + private final JTextArea fullPathsTextArea; + + /** + * Creates new form EditFullPathsRulePanel + */ + @NbBundle.Messages({ + "EditFullPathsRulePanel.example=Example: " + }) + public EditFullPathsRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule) { + initComponents(); + this.setRule(ruleName, rule); + this.setButtons(okButton, cancelButton); + + fullPathsTextArea = new JTextArea(); + initTextArea(fullPathsScrollPane, fullPathsTextArea); + setTextArea(fullPathsTextArea, rule.getFullPaths()); + + EditRulePanel.setTextFieldPrompts(fullPathsTextArea, + "" + Bundle.EditFullPathsRulePanel_example() + "
    /Program Files/Common Files/system/wab32.dll
    /Windows/System32/1033/VsGraphicsResources.dll"); // NON-NLS + ruleNameTextField.requestFocus(); + validate(); + repaint(); + } + + private void initTextArea(JScrollPane pane, JTextArea textArea) { + textArea.setColumns(20); + textArea.setRows(5); + pane.setViewportView(textArea); + textArea.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_TAB) { + if (e.getModifiers() > 0) { + textArea.transferFocusBackward(); + } else { + textArea.transferFocus(); + } + e.consume(); + } + } + }); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + shouldSaveCheckBox = new javax.swing.JCheckBox(); + shouldAlertCheckBox = new javax.swing.JCheckBox(); + fullPathsLabel = new javax.swing.JLabel(); + descriptionTextField = new javax.swing.JTextField(); + descriptionLabel = new javax.swing.JLabel(); + ruleNameLabel = new javax.swing.JLabel(); + ruleNameTextField = new javax.swing.JTextField(); + fullPathsScrollPane = new javax.swing.JScrollPane(); + + shouldSaveCheckBox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(shouldSaveCheckBox, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.shouldSaveCheckBox.text")); // NOI18N + + shouldAlertCheckBox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(shouldAlertCheckBox, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.shouldAlertCheckBox.text")); // NOI18N + shouldAlertCheckBox.setActionCommand(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(fullPathsLabel, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.fullPathsLabel.text")); // NOI18N + fullPathsLabel.setToolTipText(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.fullPathsLabel.toolTipText")); // NOI18N + + descriptionTextField.setText(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.descriptionTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.descriptionLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.ruleNameLabel.text")); // NOI18N + + ruleNameTextField.setText(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.ruleNameTextField.text")); // NOI18N + ruleNameTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + ruleNameTextFieldActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(shouldSaveCheckBox) + .addComponent(shouldAlertCheckBox) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(ruleNameLabel) + .addComponent(descriptionLabel) + .addComponent(fullPathsLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 519, Short.MAX_VALUE) + .addComponent(descriptionTextField) + .addComponent(fullPathsScrollPane)))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(ruleNameLabel) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(descriptionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(descriptionLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(fullPathsLabel) + .addGap(0, 167, Short.MAX_VALUE)) + .addComponent(fullPathsScrollPane)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(shouldAlertCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(shouldSaveCheckBox) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void ruleNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ruleNameTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_ruleNameTextFieldActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel descriptionLabel; + private javax.swing.JTextField descriptionTextField; + private javax.swing.JLabel fullPathsLabel; + private javax.swing.JScrollPane fullPathsScrollPane; + private javax.swing.JLabel ruleNameLabel; + private javax.swing.JTextField ruleNameTextField; + private javax.swing.JCheckBox shouldAlertCheckBox; + private javax.swing.JCheckBox shouldSaveCheckBox; + // End of variables declaration//GEN-END:variables + + /** + * Sets whether or not the OK button should be enabled based upon other UI + * elements + */ + private void setOkButton() { + if (this.okButton != null) { + this.okButton.setEnabled(true); + } + } + + /** + * Gets the JOptionPane that is used to contain this panel if there is one + * + * @param parent + * + * @return + */ + private JOptionPane getOptionPane(JComponent parent) { + JOptionPane pane; + if (!(parent instanceof JOptionPane)) { + pane = getOptionPane((JComponent) parent.getParent()); + } else { + pane = (JOptionPane) parent; + } + return pane; + } + + /** + * Sets the buttons for ending the panel + * + * @param ok The ok button + * @param cancel The cancel button + */ + private void setButtons(JButton ok, JButton cancel) { + this.okButton = ok; + this.cancelButton = cancel; + okButton.addActionListener((ActionEvent e) -> { + JOptionPane pane = getOptionPane(okButton); + pane.setValue(okButton); + }); + cancelButton.addActionListener((ActionEvent e) -> { + JOptionPane pane = getOptionPane(cancelButton); + pane.setValue(cancelButton); + }); + this.setOkButton(); + } + + private void setRule(String ruleName, LogicalImagerRule rule) { + ruleNameTextField.setText(ruleName); + descriptionTextField.setText(rule.getDescription()); + shouldAlertCheckBox.setSelected(rule.isShouldAlert()); + shouldSaveCheckBox.setSelected(rule.isShouldSave()); + } + + private void setTextArea(JTextArea textArea, List set) { + String text = ""; + for (String s : set) { + text += s + System.getProperty("line.separator"); // NON-NLS + } + textArea.setText(text); + } + + @NbBundle.Messages({ + "EditFullPathsRulePanel.fullPaths=Full paths", + }) + public ImmutablePair toRule() throws IOException { + List fullPaths = EditRulePanel.validateTextList(fullPathsTextArea, Bundle.EditFullPathsRulePanel_fullPaths()); + String ruleName = EditRulePanel.validRuleName(ruleNameTextField.getText()); + LogicalImagerRule.Builder builder = new LogicalImagerRule.Builder(); + builder.shouldAlert(shouldAlertCheckBox.isSelected()) + .shouldSave(shouldSaveCheckBox.isSelected()) + .description(descriptionTextField.getText()) + .fullPaths(fullPaths); + LogicalImagerRule rule = builder.build(); + return new ImmutablePair<>(ruleName, rule); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.form b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.form new file mode 100644 index 0000000000..eb1c9be235 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.form @@ -0,0 +1,280 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.java new file mode 100644 index 0000000000..708ba26671 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditNonFullPathsRulePanel.java @@ -0,0 +1,482 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.strip; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.openide.util.NbBundle; + +/** + * Edit non-full paths rule panel + */ +public class EditNonFullPathsRulePanel extends javax.swing.JPanel { + + private JButton okButton; + private JButton cancelButton; + private final javax.swing.JTextArea filenamesTextArea; + private final javax.swing.JTextArea folderNamesTextArea; + + /** + * Creates new form EditRulePanel + */ + @NbBundle.Messages({ + "EditNonFullPathsRulePanel.example=Example: " + }) + EditNonFullPathsRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule) { + initComponents(); + + this.setRule(ruleName, rule); + this.setButtons(okButton, cancelButton); + + setExtensions(rule.getExtensions()); + + filenamesTextArea = new JTextArea(); + initTextArea(filenamesScrollPane, filenamesTextArea); + setTextArea(filenamesTextArea, rule.getFilenames()); + + folderNamesTextArea = new JTextArea(); + initTextArea(folderNamesScrollPane, folderNamesTextArea); + setTextArea(folderNamesTextArea, rule.getPaths()); + + setMinDays(rule.getMinDays()); + + minSizeTextField.setText(rule.getMinFileSize() == null ? "" : rule.getMinFileSize().toString()); + maxSizeTextField.setText(rule.getMaxFileSize() == null ? "" : rule.getMaxFileSize().toString()); + ruleNameTextField.requestFocus(); + + EditRulePanel.setTextFieldPrompts(extensionsTextField, Bundle.EditNonFullPathsRulePanel_example() + "gif,jpg,png"); // NON-NLS + EditRulePanel.setTextFieldPrompts(filenamesTextArea, "" + Bundle.EditNonFullPathsRulePanel_example() + "
    filename.txt
    readme.txt"); // NON-NLS + EditRulePanel.setTextFieldPrompts(folderNamesTextArea, "" + Bundle.EditNonFullPathsRulePanel_example() + "
    [USER_FOLDER]/My Documents/Downloads
    /Program Files/Common Files"); // NON-NLS + validate(); + repaint(); + } + + private void initTextArea(JScrollPane pane, JTextArea textArea) { + textArea.setColumns(20); + textArea.setRows(5); + pane.setViewportView(textArea); + textArea.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_TAB) { + if (e.getModifiers() > 0) { + textArea.transferFocusBackward(); + } else { + textArea.transferFocus(); + } + e.consume(); + } + } + }); + } + + private void setMinDays(Integer minDays) { + minDaysTextField.setText(minDays == null ? "" : minDays.toString()); + } + + private void setTextArea(JTextArea textArea, List set) { + String text = ""; + if (set != null) { + for (String s : set) { + text += s + System.getProperty("line.separator"); // NON-NLS + } + } + textArea.setText(text); + } + + private void setExtensions(List extensions) { + extensionsTextField.setText(""); + String content = ""; + if (extensions != null) { + boolean first = true; + for (String ext : extensions) { + content += (first ? "" : ",") + ext; + first = false; + } + } + extensionsTextField.setText(content); + } + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + modifiedDateLabel = new javax.swing.JLabel(); + daysIncludedLabel = new javax.swing.JLabel(); + shouldSaveCheckBox = new javax.swing.JCheckBox(); + shouldAlertCheckBox = new javax.swing.JCheckBox(); + extensionsLabel = new javax.swing.JLabel(); + extensionsTextField = new javax.swing.JTextField(); + filenamesLabel = new javax.swing.JLabel(); + folderNamesLabel = new javax.swing.JLabel(); + fileSizeLabel = new javax.swing.JLabel(); + descriptionTextField = new javax.swing.JTextField(); + descriptionLabel = new javax.swing.JLabel(); + ruleNameLabel = new javax.swing.JLabel(); + ruleNameTextField = new javax.swing.JTextField(); + filenamesScrollPane = new javax.swing.JScrollPane(); + folderNamesScrollPane = new javax.swing.JScrollPane(); + minSizeLabel = new javax.swing.JLabel(); + minSizeTextField = new javax.swing.JFormattedTextField(); + maxSizeLabel = new javax.swing.JLabel(); + maxSizeTextField = new javax.swing.JFormattedTextField(); + minDaysTextField = new javax.swing.JFormattedTextField(); + + org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.modifiedDateLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.daysIncludedLabel.text")); // NOI18N + daysIncludedLabel.setEnabled(false); + + shouldSaveCheckBox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(shouldSaveCheckBox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.shouldSaveCheckBox.text")); // NOI18N + + shouldAlertCheckBox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(shouldAlertCheckBox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.shouldAlertCheckBox.text")); // NOI18N + shouldAlertCheckBox.setActionCommand(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(extensionsLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsLabel.text")); // NOI18N + + extensionsTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(filenamesLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.filenamesLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(folderNamesLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.folderNamesLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(fileSizeLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.fileSizeLabel.text")); // NOI18N + + descriptionTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.descriptionTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.descriptionLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.ruleNameLabel.text")); // NOI18N + + ruleNameTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.ruleNameTextField.text")); // NOI18N + ruleNameTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + ruleNameTextFieldActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(minSizeLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.minSizeLabel.text")); // NOI18N + + minSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); + minSizeTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.minSizeTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(maxSizeLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.maxSizeLabel.text")); // NOI18N + + maxSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); + maxSizeTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.maxSizeTextField.text")); // NOI18N + + minDaysTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("####; ")))); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(27, 27, 27) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(ruleNameLabel) + .addComponent(descriptionLabel) + .addComponent(extensionsLabel) + .addComponent(filenamesLabel) + .addComponent(folderNamesLabel) + .addComponent(fileSizeLabel) + .addComponent(modifiedDateLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(filenamesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(extensionsTextField, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(descriptionTextField, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.Alignment.TRAILING))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(23, 23, 23) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(shouldSaveCheckBox) + .addComponent(shouldAlertCheckBox))) + .addGroup(layout.createSequentialGroup() + .addGap(108, 108, 108) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(minDaysTextField) + .addComponent(minSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(maxSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(daysIncludedLabel)))) + .addGap(0, 236, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(ruleNameLabel) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(descriptionLabel) + .addComponent(descriptionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(extensionsLabel) + .addComponent(extensionsTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(filenamesLabel) + .addComponent(filenamesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 70, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(folderNamesLabel) + .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(fileSizeLabel) + .addComponent(minSizeLabel) + .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(maxSizeLabel) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(modifiedDateLabel) + .addComponent(daysIncludedLabel) + .addComponent(minDaysTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(14, 14, 14) + .addComponent(shouldAlertCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(shouldSaveCheckBox) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void ruleNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ruleNameTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_ruleNameTextFieldActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel daysIncludedLabel; + private javax.swing.JLabel descriptionLabel; + private javax.swing.JTextField descriptionTextField; + private javax.swing.JLabel extensionsLabel; + private javax.swing.JTextField extensionsTextField; + private javax.swing.JLabel fileSizeLabel; + private javax.swing.JLabel filenamesLabel; + private javax.swing.JScrollPane filenamesScrollPane; + private javax.swing.JLabel folderNamesLabel; + private javax.swing.JScrollPane folderNamesScrollPane; + private javax.swing.JLabel maxSizeLabel; + private javax.swing.JFormattedTextField maxSizeTextField; + private javax.swing.JFormattedTextField minDaysTextField; + private javax.swing.JLabel minSizeLabel; + private javax.swing.JFormattedTextField minSizeTextField; + private javax.swing.JLabel modifiedDateLabel; + private javax.swing.JLabel ruleNameLabel; + private javax.swing.JTextField ruleNameTextField; + private javax.swing.JCheckBox shouldAlertCheckBox; + private javax.swing.JCheckBox shouldSaveCheckBox; + // End of variables declaration//GEN-END:variables + + private void setRule(String ruleName, LogicalImagerRule rule) { + ruleNameTextField.setText(ruleName); + descriptionTextField.setText(rule.getDescription()); + shouldAlertCheckBox.setSelected(rule.isShouldAlert()); + shouldSaveCheckBox.setSelected(rule.isShouldSave()); + } + + /** + * Sets whether or not the OK button should be enabled based upon other UI + * elements + */ + private void setOkButton() { + if (this.okButton != null) { + this.okButton.setEnabled(true); + } + } + + /** + * Gets the JOptionPane that is used to contain this panel if there is one + * + * @param parent + * + * @return + */ + private JOptionPane getOptionPane(JComponent parent) { + JOptionPane pane; + if (!(parent instanceof JOptionPane)) { + pane = getOptionPane((JComponent) parent.getParent()); + } else { + pane = (JOptionPane) parent; + } + return pane; + } + + /** + * Sets the buttons for ending the panel + * + * @param ok The ok button + * @param cancel The cancel button + */ + private void setButtons(JButton ok, JButton cancel) { + this.okButton = ok; + this.cancelButton = cancel; + okButton.addActionListener((ActionEvent e) -> { + JOptionPane pane = getOptionPane(okButton); + pane.setValue(okButton); + }); + cancelButton.addActionListener((ActionEvent e) -> { + JOptionPane pane = getOptionPane(cancelButton); + pane.setValue(cancelButton); + }); + this.setOkButton(); + } + + @NbBundle.Messages({ + "EditNonFullPathsRulePanel.modifiedDaysNotPositiveException=Modified days must be a positive", + "# {0} - message", + "EditNonFullPathsRulePanel.modifiedDaysMustBeNumberException=Modified days must be a number: {0}", + "EditNonFullPathsRulePanel.minFileSizeNotPositiveException=Minimum file size must be a positive", + "# {0} - message", + "EditNonFullPathsRulePanel.minFileSizeMustBeNumberException=Minimum file size must be a number: {0}", + "EditNonFullPathsRulePanel.maxFileSizeNotPositiveException=Maximum file size must be a positive", + "# {0} - message", + "EditNonFullPathsRulePanel.maxFileSizeMustBeNumberException=Maximum file size must be a number: {0}", + "# {0} - maxFileSize", + "# {1} - minFileSize", + "EditNonFullPathsRulePanel.maxFileSizeSmallerThanMinException=Maximum file size: {0} must be bigger than minimum file size: {1}", + "EditNonFullPathsRulePanel.fileNames=File names", + "EditNonFullPathsRulePanel.folderNames=Folder names", + }) + ImmutablePair toRule() throws IOException { + String ruleName = EditRulePanel.validRuleName(ruleNameTextField.getText()); + List extensions = validateExtensions(extensionsTextField); + List filenames = EditRulePanel.validateTextList(filenamesTextArea, Bundle.EditNonFullPathsRulePanel_fileNames()); + List folderNames = EditRulePanel.validateTextList(folderNamesTextArea, Bundle.EditNonFullPathsRulePanel_folderNames()); + + LogicalImagerRule.Builder builder = new LogicalImagerRule.Builder(); + builder.description(descriptionTextField.getText()) + .shouldAlert(shouldAlertCheckBox.isSelected()) + .shouldSave(shouldSaveCheckBox.isSelected()) + .extensions(extensions) + .filenames(filenames) + .paths(folderNames); + + int minDays; + if (!isBlank(minDaysTextField.getText())) { + try { + minDaysTextField.commitEdit(); + minDays = ((Number)minDaysTextField.getValue()).intValue(); + if (minDays < 0) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_modifiedDaysNotPositiveException()); + } + builder.minDays(minDays); + } catch (NumberFormatException | ParseException ex) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_modifiedDaysMustBeNumberException(ex.getMessage())); + } + } + + int minFileSize = 0; + if (!isBlank(minSizeTextField.getText())) { + try { + minSizeTextField.commitEdit(); + minFileSize = ((Number)minSizeTextField.getValue()).intValue(); + if (minFileSize < 0) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_minFileSizeNotPositiveException()); + } + } catch (NumberFormatException | ParseException ex) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_minFileSizeMustBeNumberException(ex.getMessage())); + } + } + + int maxFileSize = 0; + if (!isBlank(maxSizeTextField.getText())) { + try { + maxSizeTextField.commitEdit(); + maxFileSize = ((Number)maxSizeTextField.getValue()).intValue(); + if (maxFileSize < 0) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_maxFileSizeNotPositiveException()); + } + } catch (NumberFormatException | ParseException ex) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_maxFileSizeMustBeNumberException(ex.getMessage())); + } + } + + if (maxFileSize != 0 && (maxFileSize < minFileSize)) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_maxFileSizeSmallerThanMinException(maxFileSize, minFileSize)); + } + if (minFileSize != 0) { + builder.minFileSize(minFileSize); + } + if (maxFileSize != 0) { + builder.maxFileSize(maxFileSize); + } + + LogicalImagerRule rule = builder.build(); + return new ImmutablePair<>(ruleName, rule); + } + + @NbBundle.Messages({ + "EditNonFullPathsRulePanel.emptyExtensionException=Extensions cannot have an empty entry", + }) + private List validateExtensions(JTextField textField) throws IOException { + List extensions = new ArrayList<>(); + if (isBlank(textField.getText())) { + return null; + } + for (String extension : textField.getText().split(",")) { + extension = strip(extension); + if (extension.isEmpty()) { + throw new IOException(Bundle.EditNonFullPathsRulePanel_emptyExtensionException()); + } + extensions.add(extension); + } + if (extensions.isEmpty()) { + return null; + } + return extensions; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditRulePanel.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditRulePanel.java new file mode 100644 index 0000000000..ae1704dcd9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EditRulePanel.java @@ -0,0 +1,127 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.text.JTextComponent; +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.strip; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.corecomponents.TextPrompt; + +/** + * Edit rule panel + */ +public class EditRulePanel extends JPanel { + + private EditFullPathsRulePanel editFullPathsRulePanel = null; + private EditNonFullPathsRulePanel editNonFullPathsRulePanel = null; + + /** + * Creates new form EditRulePanel + */ + public EditRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule) { + if (rule.getFullPaths() != null && rule.getFullPaths().size() > 0) { + editFullPathsRulePanel = new EditFullPathsRulePanel(okButton, cancelButton, ruleName, rule); + } else { + editNonFullPathsRulePanel = new EditNonFullPathsRulePanel(okButton, cancelButton, ruleName, rule); + } + } + + void setRule(LogicalImagerRule rule) { + + } + + JPanel getPanel() { + if (editFullPathsRulePanel != null) { + return editFullPathsRulePanel; + } else { + return editNonFullPathsRulePanel; + } + } + + ImmutablePair toRule() throws IOException, NumberFormatException { + ImmutablePair ruleMap; + if (editFullPathsRulePanel != null) { + ruleMap = editFullPathsRulePanel.toRule(); + } else { + ruleMap = editNonFullPathsRulePanel.toRule(); + } + return ruleMap; + } + + static void setTextFieldPrompts(JTextComponent textField, String text) { + /** + * Add text prompt to the text field. + */ + TextPrompt textPrompt; + if (textField instanceof JTextArea) { + textPrompt = new TextPrompt(text, textField, BorderLayout.NORTH); + } else { + textPrompt = new TextPrompt(text, textField); + } + + /** + * Sets the foreground color and transparency of the text prompt. + */ + textPrompt.setForeground(Color.LIGHT_GRAY); + textPrompt.changeAlpha(0.9f); // Mostly opaque + } + + @NbBundle.Messages({ + "EditRulePanel.validateRuleNameExceptionMsg=Rule name cannot be empty" + }) + static public String validRuleName(String name) throws IOException { + if (name.isEmpty()) { + throw new IOException(Bundle.EditRulePanel_validateRuleNameExceptionMsg()); + } + return name; + } + + @NbBundle.Messages({ + "# ({0} - fieldName", + "EditRulePanel.blankLineException={0} cannot have a blank line", + }) + static public List validateTextList(JTextArea textArea, String fieldName) throws IOException { + List list = new ArrayList<>(); + if (isBlank(textArea.getText())) { + return null; + } + for (String line : textArea.getText().split("\\n")) { // NON-NLS + line = strip(line); + if (line.isEmpty()) { + throw new IOException(Bundle.EditRulePanel_blankLineException(fieldName)); + } + list.add(line); + } + if (list.isEmpty()) { + return null; + } + return list; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EncryptionProgramsRule.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EncryptionProgramsRule.java new file mode 100644 index 0000000000..61d18e4d37 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/EncryptionProgramsRule.java @@ -0,0 +1,54 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.util.ArrayList; +import java.util.List; +import org.openide.util.NbBundle; + +/** + * Encryption programs rule + */ +@NbBundle.Messages({ + "EncryptionProgramsRule.encryptionProgramsRuleName=Encryption Programs", + "EncryptionProgramsRule.encryptionProgramsRuleDescription=Find encryption programs" +}) +public final class EncryptionProgramsRule { + + private static final String ENCRYPTION_PROGRAMS_RULE_NAME = Bundle.EncryptionProgramsRule_encryptionProgramsRuleName(); + private static final String ENCRYPTION_PROGRAMS_RULE_DESCRIPTION = Bundle.EncryptionProgramsRule_encryptionProgramsRuleDescription(); + private static final List FILENAMES = new ArrayList<>(); + + // TODO: Add more files here + static { + FILENAMES.add("truecrypt.exe"); //NON-NLS + } + + public static String getName() { + return ENCRYPTION_PROGRAMS_RULE_NAME; + } + + public static String getDescription() { + return ENCRYPTION_PROGRAMS_RULE_DESCRIPTION; + } + + public static List getFilenames() { + return FILENAMES; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfig.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfig.java new file mode 100644 index 0000000000..68fe81fe60 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfig.java @@ -0,0 +1,67 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import java.util.HashMap; +import java.util.Map; + +/** + * Logical Imager Configuration file JSON + */ +public class LogicalImagerConfig { + + @SerializedName("finalize-image-writer") + @Expose(serialize = true) + private boolean finalizeImageWriter; + + @SerializedName("rule-set") + @Expose(serialize = true) + private Map ruleSet; + + public LogicalImagerConfig() { + this.finalizeImageWriter = false; + this.ruleSet = new HashMap<>(); + } + + public LogicalImagerConfig( + boolean finalizeImageWriter, + Map ruleSet + ) { + this.finalizeImageWriter = finalizeImageWriter; + this.ruleSet = ruleSet; + } + + public boolean isFinalizeImageWriter() { + return finalizeImageWriter; + } + + public void setFinalizeImageWriter(boolean finalizeImageWriter) { + this.finalizeImageWriter = finalizeImageWriter; + } + + public Map getRuleSet() { + return ruleSet; + } + + public void setRuleSet(Map ruleSet) { + this.ruleSet = ruleSet; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfigDeserializer.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfigDeserializer.java new file mode 100644 index 0000000000..b206315dd9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerConfigDeserializer.java @@ -0,0 +1,187 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.openide.util.NbBundle; + +/** + * Logical Imager Configuration JSON deserializer + */ +@NbBundle.Messages({ + "LogicalImagerConfigDeserializer.missingRuleSetException=Missing rule-set", + "# {0} - key", + "LogicalImagerConfigDeserializer.unsupportedKeyException=Unsupported key: {0}", + "LogicalImagerConfigDeserializer.fullPathsException=A rule with full-paths cannot have other rule definitions", +}) +public class LogicalImagerConfigDeserializer implements JsonDeserializer { + + @Override + public LogicalImagerConfig deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { + boolean finalizeImageWriter = false; + Map ruleSet = new HashMap<>(); + + final JsonObject jsonObject = je.getAsJsonObject(); + final JsonElement jsonFinalizeImageWriter = jsonObject.get("finalize-image-writer"); // NON-NLS + if (jsonFinalizeImageWriter != null) { + finalizeImageWriter = jsonFinalizeImageWriter.getAsBoolean(); + } + + final JsonObject jsonRuleSet = jsonObject.get("rule-set").getAsJsonObject(); // NON-NLS + if (jsonRuleSet == null) { + throw new JsonParseException(Bundle.LogicalImagerConfigDeserializer_missingRuleSetException()); + } + for (Map.Entry entry : jsonRuleSet.entrySet()) { + String key = entry.getKey(); + JsonElement element = entry.getValue(); + Set> entrySet = element.getAsJsonObject().entrySet(); + String key1; + Boolean shouldSave = false; + Boolean shouldAlert = true; + String description = null; + List extensions = null; + List paths = null; + List fullPaths = null; + List filenames = null; + Integer minFileSize = null; + Integer maxFileSize = null; + Integer minDays = null; + Integer minDate = null; + Integer maxDate = null; + + for (Map.Entry entry1 : entrySet) { + key1 = entry1.getKey(); + switch (key1) { + case "shouldAlert": // NON-NLS + shouldAlert = entry1.getValue().getAsBoolean(); + break; + case "shouldSave": // NON-NLS + shouldSave = entry1.getValue().getAsBoolean(); + break; + case "description": // NON-NLS + description = entry1.getValue().getAsString(); + break; + case "extensions": // NON-NLS + JsonArray extensionsArray = entry1.getValue().getAsJsonArray(); + extensions = new ArrayList<>(); + for (JsonElement e : extensionsArray) { + extensions.add(e.getAsString()); + } + break; + case "folder-names": // NON-NLS + JsonArray pathsArray = entry1.getValue().getAsJsonArray(); + paths = new ArrayList<>(); + for (JsonElement e : pathsArray) { + paths.add(e.getAsString()); + } + break; + case "file-names": // NON-NLS + JsonArray filenamesArray = entry1.getValue().getAsJsonArray(); + filenames = new ArrayList<>(); + for (JsonElement e : filenamesArray) { + filenames.add(e.getAsString()); + } + break; + case "full-paths": // NON-NLS + JsonArray fullPathsArray = entry1.getValue().getAsJsonArray(); + fullPaths = new ArrayList<>(); + for (JsonElement e : fullPathsArray) { + fullPaths.add(e.getAsString()); + } + break; + case "size-range": // NON-NLS + JsonObject sizeRangeObject = entry1.getValue().getAsJsonObject(); + Set> entrySet1 = sizeRangeObject.entrySet(); + for (Map.Entry entry2 : entrySet1) { + String sizeKey = entry2.getKey(); + switch (sizeKey) { + case "min": // NON-NLS + minFileSize = entry2.getValue().getAsInt(); + break; + case "max": // NON-NLS + maxFileSize = entry2.getValue().getAsInt(); + break; + default: + throw new JsonParseException(Bundle.LogicalImagerConfigDeserializer_unsupportedKeyException(sizeKey)); + } + }; + break; + case "date-range": // NON-NLS + JsonObject dateRangeObject = entry1.getValue().getAsJsonObject(); + Set> entrySet2 = dateRangeObject.entrySet(); + for (Map.Entry entry2 : entrySet2) { + String dateKey = entry2.getKey(); + switch (dateKey) { + case "min": // NON-NLS + minDate = entry2.getValue().getAsInt(); + break; + case "max": // NON-NLS + maxDate = entry2.getValue().getAsInt(); + break; + case "min-days": // NON-NLS + minDays = entry2.getValue().getAsInt(); + break; + default: + throw new JsonParseException(Bundle.LogicalImagerConfigDeserializer_unsupportedKeyException(dateKey)); + } + }; + break; + default: + throw new JsonParseException(Bundle.LogicalImagerConfigDeserializer_unsupportedKeyException(key1)); + } + } + + // A rule with full-paths cannot have other rule definitions + if ((fullPaths != null && !fullPaths.isEmpty()) && ((extensions != null && !extensions.isEmpty()) + || (paths != null && !paths.isEmpty()) + || (filenames != null && !filenames.isEmpty()))) { + throw new JsonParseException(Bundle.LogicalImagerConfigDeserializer_fullPathsException()); + } + + LogicalImagerRule rule = new LogicalImagerRule.Builder() + .shouldAlert(shouldAlert) + .shouldSave(shouldSave) + .description(description) + .extensions(extensions) + .paths(paths) + .fullPaths(fullPaths) + .filenames(filenames) + .minFileSize(minFileSize) + .maxFileSize(maxFileSize) + .minDays(minDays) + .minDate(minDate) + .maxDate(maxDate) + .build(); + ruleSet.put(key, rule); + } + LogicalImagerConfig config = new LogicalImagerConfig(finalizeImageWriter, ruleSet); + return config; + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerRule.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerRule.java new file mode 100644 index 0000000000..781f00ae2a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/LogicalImagerRule.java @@ -0,0 +1,239 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * The class definition for the Logical Imager Rule. + */ +public class LogicalImagerRule { + + @Expose(serialize = true) + private final Boolean shouldAlert; + @Expose(serialize = true) + private final Boolean shouldSave; + @Expose(serialize = true) + private final String description; + @Expose(serialize = true) + private List extensions = new ArrayList<>(); + @SerializedName("file-names") + @Expose(serialize = true) + private List filenames = new ArrayList<>(); + @SerializedName("folder-names") + @Expose(serialize = true) + private List paths = new ArrayList<>(); + @SerializedName("full-paths") + @Expose(serialize = true) + private List fullPaths = new ArrayList<>(); + @SerializedName("size-range") + @Expose(serialize = true) + private Map sizeRange = new HashMap<>(); + @SerializedName("date-range") + @Expose(serialize = true) + private Map dateRange = new HashMap<>(); + + // The following fields should not be serialized, internal use only + @Expose(serialize = false) + private Integer minFileSize; + @Expose(serialize = false) + private Integer maxFileSize; + @Expose(serialize = false) + private Integer minDays; + @Expose(serialize = false) + private Integer minDate; + @Expose(serialize = false) + private Integer maxDate; + + private LogicalImagerRule(Boolean shouldAlert, Boolean shouldSave, String description, + List extensions, + List filenames, + List paths, + List fullPaths, + Integer minFileSize, + Integer maxFileSize, + Integer minDays, + Integer minDate, + Integer maxDate + ) { + this.shouldAlert = shouldAlert; + this.shouldSave = shouldSave; + this.description = description; + this.extensions = extensions; + this.filenames = filenames; + this.paths = paths; + this.fullPaths = fullPaths; + + this.sizeRange.put("min", minFileSize); // NON-NLS + this.minFileSize = minFileSize; + this.sizeRange.put("max", maxFileSize); // NON-NLS + this.maxFileSize = maxFileSize; + this.dateRange.put("min-days", minDays); // NON-NLS + this.minDays = minDays; + this.dateRange.put("min-date", minDate); // NON-NLS + this.minDate = minDate; + this.dateRange.put("max-date", maxDate); // NON-NLS + this.maxDate = maxDate; + } + + public LogicalImagerRule() { + this.shouldAlert = true; + this.shouldSave = false; + this.description = null; + } + + public Boolean isShouldAlert() { + return shouldAlert; + } + + public Boolean isShouldSave() { + return shouldSave; + } + + public String getDescription() { + return description; + } + + public List getExtensions() { + return extensions; + } + + public List getFilenames() { + return filenames; + } + + public List getPaths() { + return paths; + } + + public List getFullPaths() { + return fullPaths; + } + + public Integer getMinFileSize() { + return minFileSize; + } + + public Integer getMaxFileSize() { + return maxFileSize; + } + + public Integer getMinDays() { + return minDays; + } + + public Integer getMinDate() { + return minDate; + } + + public Integer getMaxDate() { + return maxDate; + } + + public static class Builder { + private Boolean shouldAlert = null; + private Boolean shouldSave = null; + private String description = null; + private List extensions = null; + private List filenames = null; + private List paths = null; + private List fullPaths = null; + private Integer minFileSize = null; + private Integer maxFileSize = null; + private Integer minDays = null; + private Integer minDate = null; + private Integer maxDate = null; + + public Builder() {} + + public Builder shouldAlert(boolean shouldAlert) { + this.shouldAlert = shouldAlert; + return this; + } + + public Builder shouldSave(boolean shouldSave) { + this.shouldSave = shouldSave; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder extensions(List extensions) { + this.extensions = extensions; + return this; + } + + public Builder filenames(List filenames) { + this.filenames = filenames; + return this; + } + + public Builder paths(List paths) { + this.paths = paths; + return this; + } + + public Builder fullPaths(List fullPaths) { + this.fullPaths = fullPaths; + return this; + } + + public Builder minFileSize(Integer minFileSize) { + this.minFileSize = minFileSize; + return this; + } + + public Builder maxFileSize(Integer maxFileSize) { + this.maxFileSize = maxFileSize; + return this; + } + + public Builder minDays(Integer minDays) { + this.minDays = minDays; + return this; + } + + public Builder minDate(Integer minDate) { + this.minDate = minDate; + return this; + } + + public Builder maxDate(Integer maxDate) { + this.maxDate = maxDate; + return this; + } + + public LogicalImagerRule build() { + return new LogicalImagerRule(shouldAlert, shouldSave, description, + extensions, filenames, paths, fullPaths, + minFileSize, maxFileSize, + minDays, minDate, maxDate + ); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.form b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.form new file mode 100644 index 0000000000..e8c80c9737 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.form @@ -0,0 +1,89 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.java b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.java new file mode 100644 index 0000000000..6e4532a5be --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/NewRuleSetPanel.java @@ -0,0 +1,153 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.configurelogicalimager; + +import java.awt.BorderLayout; +import java.io.IOException; +import javax.swing.JButton; +import javax.swing.JPanel; +import org.apache.commons.lang3.tuple.ImmutablePair; + +/** + * New rule set panel + */ +public class NewRuleSetPanel extends javax.swing.JPanel { + + private final JPanel nonFullPathsJPanel; + private final EditNonFullPathsRulePanel editNonFullPathsRulePanel; + private final JPanel fullPathsPanel; + private final EditFullPathsRulePanel editFullPathsRulePanel; + + /** + * Creates new form NewRuleSetPanel + */ + public NewRuleSetPanel(JButton okButton, JButton cancelButton) { + initComponents(); + + nonFullPathsJPanel = createPanel(); + editNonFullPathsRulePanel = new EditNonFullPathsRulePanel(okButton, cancelButton, "", new LogicalImagerRule()); + nonFullPathsJPanel.add(editNonFullPathsRulePanel, BorderLayout.NORTH); + + fullPathsPanel = createPanel(); + editFullPathsRulePanel = new EditFullPathsRulePanel(okButton, cancelButton, "", new LogicalImagerRule()); + fullPathsPanel.add(editFullPathsRulePanel, BorderLayout.NORTH); + + sharedLayeredPane.add(nonFullPathsJPanel, new Integer(0)); + sharedLayeredPane.add(fullPathsPanel, new Integer(1)); + nonFullPathsJPanel.setVisible(true); + fullPathsPanel.setVisible(false); + } + + private JPanel createPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setSize(800, 640); + return panel; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + chooseLabel = new javax.swing.JLabel(); + chooseComboBox = new javax.swing.JComboBox<>(); + sharedLayeredPane = new javax.swing.JLayeredPane(); + + org.openide.awt.Mnemonics.setLocalizedText(chooseLabel, org.openide.util.NbBundle.getMessage(NewRuleSetPanel.class, "NewRuleSetPanel.chooseLabel.text")); // NOI18N + + chooseComboBox.setMaximumRowCount(2); + chooseComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "By Attribute", "Full Path" })); + chooseComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chooseComboBoxActionPerformed(evt); + } + }); + + javax.swing.GroupLayout sharedLayeredPaneLayout = new javax.swing.GroupLayout(sharedLayeredPane); + sharedLayeredPane.setLayout(sharedLayeredPaneLayout); + sharedLayeredPaneLayout.setHorizontalGroup( + sharedLayeredPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + sharedLayeredPaneLayout.setVerticalGroup( + sharedLayeredPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 373, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(chooseLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chooseComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(718, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(sharedLayeredPane) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(chooseLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chooseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(sharedLayeredPane) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void chooseComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chooseComboBoxActionPerformed + int index = chooseComboBox.getSelectedIndex(); + if (index == 0) { + nonFullPathsJPanel.setVisible(true); + fullPathsPanel.setVisible(false); + } else { + nonFullPathsJPanel.setVisible(false); + fullPathsPanel.setVisible(true); + } + chooseComboBox.setEnabled(false); + }//GEN-LAST:event_chooseComboBoxActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox chooseComboBox; + private javax.swing.JLabel chooseLabel; + private javax.swing.JLayeredPane sharedLayeredPane; + // End of variables declaration//GEN-END:variables + + ImmutablePair toRule() throws IOException, NumberFormatException { + ImmutablePair ruleMap; + if (chooseComboBox.isEnabled()) { + ruleMap = editNonFullPathsRulePanel.toRule(); + } else { + ruleMap = editFullPathsRulePanel.toRule(); + } + return ruleMap; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/configurelogicalimager/tsk_logical_imager.exe b/Core/src/org/sleuthkit/autopsy/configurelogicalimager/tsk_logical_imager.exe new file mode 100644 index 0000000000000000000000000000000000000000..05f68c637658c53162dfe040a6e87b0f69475fa3 GIT binary patch literal 1398272 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

    gQLpl_4Lis17(Sm>PJ0 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