Merge branch '5370-CacheMultipleSearches' of https://github.com/wschaeferB/autopsy into 5677-DisplayVisualStatusIndicators

This commit is contained in:
William Schaefer
2019-10-29 17:44:05 -04:00
43 changed files with 2618 additions and 897 deletions
+1 -1
View File
@@ -137,7 +137,7 @@
</target>
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist,getTestDataFiles">
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist">
<mkdir dir="${ext.dir}"/>
<copy file="${thirdparty.dir}/LICENSE-2.0.txt" todir="${ext.dir}" />
<!-- fetch all the dependencies from Ivy and stick them in the right places -->
@@ -162,6 +162,8 @@ class CaseInformationPanel extends javax.swing.JPanel {
editCasePropertiesDialog.setResizable(true);
editCasePropertiesDialog.pack();
editCasePropertiesDialog.setLocationRelativeTo(this);
// Workaround to ensure dialog is not hidden on macOS
editCasePropertiesDialog.setAlwaysOnTop(true);
editCasePropertiesDialog.setVisible(true);
editCasePropertiesDialog.toFront();
caseDetailsPanel.updateCaseInfo();
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -84,10 +85,17 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.setFileFilter(caseMetadataFileFilter);
if (null != ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE)) {
fileChooser.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); //NON-NLS
}
/**
* If the open multi user case dialog is open make sure it's not set
* to always be on top as this hides the file chooser on macOS.
*/
OpenMultiUserCaseDialog multiUserCaseDialog = OpenMultiUserCaseDialog.getInstance();
multiUserCaseDialog.setAlwaysOnTop(false);
String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title");
String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning");
if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) {
@@ -95,7 +103,12 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
* Pop up a file chooser to allow the user to select a case metadata
* file (.aut file).
*/
int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow());
/**
* The parent of the fileChooser will either be the multi user
* case dialog or the startup window.
*/
int retval = fileChooser.showOpenDialog(multiUserCaseDialog.isVisible()
? multiUserCaseDialog : (Component) StartupWindowProvider.getInstance().getStartupWindow());
if (retval == JFileChooser.APPROVE_OPTION) {
/*
* Close the startup window, if it is open.
@@ -105,7 +118,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
/*
* Close the Open Multi-User Case window, if it is open.
*/
OpenMultiUserCaseDialog.getInstance().setVisible(false);
multiUserCaseDialog.setVisible(false);
/*
* Try to open the case associated with the case metadata file
@@ -159,6 +172,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
OpenMultiUserCaseDialog multiUserCaseWindow = OpenMultiUserCaseDialog.getInstance();
multiUserCaseWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
// Workaround to ensure that dialog is not hidden on macOS.
multiUserCaseWindow.setAlwaysOnTop(true);
multiUserCaseWindow.setVisible(true);
WindowManager.getDefault().getMainWindow().setCursor(null);
@@ -249,6 +249,8 @@ public class CueBannerPanel extends javax.swing.JPanel {
private void openRecentCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openRecentCaseButtonActionPerformed
recentCasesWindow.setLocationRelativeTo(this);
OpenRecentCasePanel.getInstance(); //refreshes the recent cases table
// Workaround to ensure that dialog is not hidden on macOS.
recentCasesWindow.setAlwaysOnTop(true);
recentCasesWindow.setVisible(true);
}//GEN-LAST:event_openRecentCaseButtonActionPerformed
@@ -71,6 +71,8 @@ final class NewCaseWizardAction extends CallableSystemAction {
wizardDescriptor.setTitleFormat(new MessageFormat("{0}"));
wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text"));
Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor);
// Workaround to ensure new case dialog is not hidden on macOS
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
dialog.toFront();
if (wizardDescriptor.getValue() == WizardDescriptor.FINISH_OPTION) {
@@ -144,4 +144,13 @@ public class StartupWindowProvider implements StartupWindowInterface {
startupWindowToUse.close();
}
}
/**
* Get the chosen startup window.
*
* @return The startup window.
*/
public StartupWindowInterface getStartupWindow() {
return startupWindowToUse;
}
}
@@ -224,6 +224,7 @@ public class IngestEventsListener {
* in the central repository.
*
* @param originalArtifact the artifact to create the interesting item for
* @param caseDisplayNames the case names the artifact was previously seen in
*/
@NbBundle.Messages({"IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository)",
"# {0} - typeName",
@@ -16,8 +16,8 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
@@ -106,6 +106,9 @@
<Dimension value="[200, 21]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_InitCodePost" type="java.lang.String" value="progressSlider.setUI(new CircularJSliderUI(progressSlider, new CircularJSliderConfiguration(new Dimension(18,18))));"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="progressLabel">
<Properties>
@@ -172,7 +175,7 @@
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="3" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="8" ipadY="7" insetsTop="6" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="3" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="8" ipadY="7" insetsTop="6" insetsLeft="14" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@@ -192,6 +195,9 @@
<Dimension value="[200, 21]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_InitCodePost" type="java.lang.String" value="audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15))));"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="4" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="-116" ipadY="7" insetsTop="3" insetsLeft="1" insetsBottom="0" insetsRight="10" anchor="18" weightX="0.0" weightY="0.0"/>
@@ -19,8 +19,16 @@
package org.sleuthkit.autopsy.contentviewers;
import com.google.common.io.Files;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -30,6 +38,7 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.swing.BoxLayout;
@@ -52,8 +61,12 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskData;
import javafx.embed.swing.JFXPanel;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicSliderUI;
import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
import org.freedesktop.gstreamer.ClockTime;
import org.freedesktop.gstreamer.Format;
import org.freedesktop.gstreamer.GstException;
@@ -189,12 +202,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
private ExtractMedia extractMediaWorker;
//Serialize setting the value of the Video progress slider.
//The slider is a shared resource between the VideoPanelUpdater
//and the TrackListener of the JSliderUI.
private final Semaphore sliderLock;
/**
* Creates new form MediaViewVideoPanel
*/
public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError {
initComponents();
customizeComponents();
//True for fairness. In other words,
//acquire() calls are processed in order of invocation.
sliderLock = new Semaphore(1, true);
}
private void customizeComponents() {
@@ -532,6 +553,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
public void actionPerformed(ActionEvent e) {
if (!progressSlider.getValueIsAdjusting()) {
sliderLock.acquireUninterruptibly();
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
/**
@@ -547,6 +569,210 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
SwingUtilities.invokeLater(() -> {
updateTimeLabel(position, duration);
});
sliderLock.release();
}
}
}
/**
* Represents the default configuration for the circular JSliderUI.
*/
private class CircularJSliderConfiguration {
//Thumb configurations
private final Color thumbColor;
private final Dimension thumbDimension;
//Track configurations
//Progress bar can be bisected into a seen group
//and an unseen group.
private final Color unseen;
private final Color seen;
/**
* Default configuration
*
* JSlider is light blue RGB(0,130,255). Seen track is light blue
* RGB(0,130,255). Unseen track is light grey RGB(192, 192, 192).
*
* @param thumbDimension Size of the oval thumb.
*/
public CircularJSliderConfiguration(Dimension thumbDimension) {
Color lightBlue = new Color(0, 130, 255);
seen = lightBlue;
unseen = Color.LIGHT_GRAY;
thumbColor = lightBlue;
this.thumbDimension = new Dimension(thumbDimension);
}
public Color getThumbColor() {
return thumbColor;
}
public Color getUnseenTrackColor() {
return unseen;
}
public Color getSeenTrackColor() {
return seen;
}
public Dimension getThumbDimension() {
return new Dimension(thumbDimension);
}
}
/**
* Custom view for the JSlider.
*/
private class CircularJSliderUI extends BasicSliderUI {
private final CircularJSliderConfiguration config;
/**
* Creates a custom view for the JSlider. This view draws a blue oval
* thumb at the given width and height. It also paints the track blue as
* the thumb progresses.
*
* @param slider JSlider component
* @param config Configuration object. Contains info about thumb
* dimensions and colors.
*/
public CircularJSliderUI(JSlider slider, CircularJSliderConfiguration config) {
super(slider);
this.config = config;
}
@Override
protected Dimension getThumbSize() {
return config.getThumbDimension();
}
/**
* Modifies the View to be an oval rather than the rectangle Controller.
*/
@Override
public void paintThumb(Graphics graphic) {
Rectangle thumb = this.thumbRect;
Color original = graphic.getColor();
//Change the thumb view from the rectangle
//controller to an oval.
graphic.setColor(config.getThumbColor());
Dimension thumbDimension = config.getThumbDimension();
graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height);
//Preserve the graphics original color
graphic.setColor(original);
}
@Override
public void paintTrack(Graphics graphic) {
//This rectangle is the bounding box for the progress bar
//portion of the slider. The track is painted in the middle
//of this rectangle and the thumb laid overtop.
Rectangle track = this.trackRect;
//Get the location of the thumb, this point splits the
//progress bar into 2 line segments, seen and unseen.
Rectangle thumb = this.thumbRect;
int thumbX = thumb.x;
int thumbY = thumb.y;
Color original = graphic.getColor();
//Paint the seen side
graphic.setColor(config.getSeenTrackColor());
graphic.drawLine(track.x, track.y + track.height / 2,
thumbX, thumbY + track.height / 2);
//Paint the unseen side
graphic.setColor(config.getUnseenTrackColor());
graphic.drawLine(thumbX, thumbY + track.height / 2,
track.x + track.width, track.y + track.height / 2);
//Preserve the graphics color.
graphic.setColor(original);
}
@Override
protected TrackListener createTrackListener(JSlider slider) {
return new CustomTrackListener();
}
@Override
protected void scrollDueToClickInTrack(int direction) {
//Set the thumb position to the mouse press location, as opposed
//to the closest "block" which is the default behavior.
Point mousePosition = slider.getMousePosition();
if (mousePosition == null) {
return;
}
int value = this.valueForXPosition(mousePosition.x);
//Lock the slider down, which is a shared resource.
//The VideoPanelUpdater (dedicated thread) keeps the
//slider in sync with the video position, so without
//proper locking our change could be overwritten.
sliderLock.acquireUninterruptibly();
slider.setValueIsAdjusting(true);
slider.setValue(value);
slider.setValueIsAdjusting(false);
sliderLock.release();
}
/**
* Applies anti-aliasing if available.
*/
@Override
public void update(Graphics graphic, JComponent component) {
if (graphic instanceof Graphics2D) {
Graphics2D graphic2 = (Graphics2D) graphic;
graphic2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
super.update(graphic, component);
}
/**
* This track listener will force the thumb to be snapped to the mouse
* location. This makes grabbing and dragging the JSlider much easier.
* Using the default track listener, the user would have to click
* exactly on the slider thumb to drag it. Now the thumb positions
* itself under the mouse so that it can always be dragged.
*/
private class CustomTrackListener extends CircularJSliderUI.TrackListener {
@Override
public void mousePressed(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
//Snap the thumb to position of the mouse
scrollDueToClickInTrack(0);
//Pause the video for convenience
gstPlayBin.pause();
//Handle the event as normal.
super.mousePressed(e);
}
@Override
public void mouseReleased(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
super.mouseReleased(e);
//Unpause once the mouse has been released.
gstPlayBin.play();
}
}
}
@@ -592,6 +818,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
progressSlider.setDoubleBuffered(true);
progressSlider.setMinimumSize(new java.awt.Dimension(36, 21));
progressSlider.setPreferredSize(new java.awt.Dimension(200, 21));
progressSlider.setUI(new CircularJSliderUI(progressSlider, new CircularJSliderConfiguration(new Dimension(18,18))));
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
@@ -645,7 +872,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
gridBagConstraints.ipadx = 8;
gridBagConstraints.ipady = 7;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.insets = new java.awt.Insets(6, 6, 0, 0);
gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0);
buttonPanel.add(VolumeIcon, gridBagConstraints);
audioSlider.setMajorTickSpacing(10);
@@ -655,6 +882,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
audioSlider.setValue(25);
audioSlider.setMinimumSize(new java.awt.Dimension(200, 21));
audioSlider.setPreferredSize(new java.awt.Dimension(200, 21));
audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15))));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 4;
gridBagConstraints.gridy = 0;
@@ -739,8 +967,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -23,32 +23,42 @@ import org.openide.nodes.Node;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Utility classes for content viewers. In theory, this would live in the
* contentviewer package, but the initial method was needed only be viewers in
* corecomponents and therefore can stay out of public API.
* Utility methods for content viewers.
*/
public class DataContentViewerUtility {
/**
* Returns the first non-Blackboard Artifact from a Node. Needed for (at
* least) Hex and Strings that want to view all types of content (not just
* AbstractFile), but don't want to display an artifact unless that's the
* only thing there. Scenario is hash hit or interesting item hit.
* Gets a Content object from the Lookup of a display Node object,
* preferring to return any Content object other than a BlackboardArtifact
* object.
*
* @param node Node passed into content viewer
* This method was written with the needs of the hex and strings content
* viewers in mind - the algorithm is exactly what those viewers require.
*
* @return highest priority content or null if there is no content
* @param node A display Node object.
*
* @return If there are multiple Content objects associated with the Node,
* the first Content object that is not a BlackboardArtifact object
* is returned. If no Content objects other than artifacts are found,
* the first BlackboardArtifact object found is returned. If no
* Content objects are found, null is returned.
*/
public static Content getDefaultContent(Node node) {
Content bbContentSeen = null;
for (Content content : (node).getLookup().lookupAll(Content.class)) {
if (content instanceof BlackboardArtifact) {
bbContentSeen = content;
Content artifact = null;
for (Content content : node.getLookup().lookupAll(Content.class)) {
if (content instanceof BlackboardArtifact && artifact == null) {
artifact = content;
} else {
return content;
}
}
return bbContentSeen;
return artifact;
}
/*
* Private constructor to prevent instantiation of utility class.
*/
private DataContentViewerUtility() {
}
}
@@ -76,8 +76,7 @@ class FileSearch {
/**
* Run the file search and returns the SearchResults object for debugging.
* Clears cache of search results, caching new results for access at later
* time.
* Caching new results for access at later time.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply
@@ -230,8 +229,7 @@ class FileSearch {
}
/**
* Run the file search. Clears cache of search results, caching new results
* for access at later time.
* Run the file search. Caching new results for access at later time.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply
@@ -0,0 +1,197 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Representation of a Waypoint created from a BlackboardArtifact.
*
*/
class ArtifactWaypoint implements Waypoint {
final private Long timestamp;
final private Double longitude;
final private Double latitude;
final private Double altitude;
final private String label;
final private AbstractFile image;
final private BlackboardArtifact artifact;
// This list is not expected to change after construction so the
// constructor will take care of creating an unmodifiable List
final private List<Waypoint.Property> immutablePropertiesList;
/**
* Construct a waypoint with the given artifact.
*
* @param artifact BlackboardArtifact for this waypoint
*
* @throws GeoLocationDataException Exception will be thrown if artifact did
* not have a valid longitude and latitude.
*/
ArtifactWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact,
getAttributesFromArtifactAsMap(artifact));
}
/**
* Constructor that sets all of the member variables.
*
* @param artifact BlackboardArtifact for this waypoint
* @param label String waypoint label
* @param timestamp Long timestamp, unix/java epoch seconds
* @param latitude Double waypoint latitude
* @param longitude Double waypoint longitude
* @param altitude Double waypoint altitude
* @param image AbstractFile image for waypoint, this maybe null
* @param attributeMap A Map of attributes for the given artifact
*
* @throws GeoLocationDataException Exception will be thrown if artifact did
* not have a valid longitude and latitude.
*/
ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
if (longitude == null || latitude == null) {
throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude");
}
this.artifact = artifact;
this.label = label;
this.image = image;
this.timestamp = timestamp;
this.longitude = longitude;
this.latitude = latitude;
this.altitude = altitude;
immutablePropertiesList = Collections.unmodifiableList(Waypoint.createGeolocationProperties(attributeMap));
}
/**
* Constructs a new ArtifactWaypoint.
*
* @param artifact BlackboardArtifact for this waypoint
* @param attributeMap A Map of the BlackboardAttributes for the given
* artifact.
*
* @throws GeoLocationDataException
*/
private ArtifactWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
this(artifact,
getLabelFromArtifact(attributeMap),
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null,
null, attributeMap);
}
/**
* Get the BlackboardArtifact that this waypoint represents.
*
* @return BlackboardArtifact for this waypoint.
*/
BlackboardArtifact getArtifact() {
return artifact;
}
@Override
public Long getTimestamp() {
return timestamp;
}
@Override
public String getLabel() {
return label;
}
@Override
public Double getLatitude() {
return latitude;
}
@Override
public Double getLongitude() {
return longitude;
}
@Override
public Double getAltitude() {
return altitude;
}
@Override
public AbstractFile getImage() {
return image;
}
@Override
public List<Waypoint.Property> getOtherProperties() {
return immutablePropertiesList;
}
/**
* Gets the label for this waypoint.
*
* @param artifact BlackboardArtifact for waypoint
*
* @return Returns a label for the waypoint, or empty string if no label was
* found.
*/
private static String getLabelFromArtifact(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (attribute != null) {
return attribute.getDisplayString();
}
return "";
}
/**
* Gets the list of attributes from the artifact and puts them into a map
* with the ATRIBUTE_TYPE as the key.
*
* @param artifact BlackboardArtifact current artifact
*
* @return A Map of BlackboardAttributes for the given artifact with
* ATTRIBUTE_TYPE as the key.
*
* @throws GeoLocationDataException
*/
static Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> getAttributesFromArtifactAsMap(BlackboardArtifact artifact) throws GeoLocationDataException {
Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap = new HashMap<>();
try {
List<BlackboardAttribute> attributeList = artifact.getAttributes();
for (BlackboardAttribute attribute : attributeList) {
BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID());
attributeMap.put(type, attribute);
}
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get attributes from artifact", ex);
}
return attributeMap;
}
}
@@ -0,0 +1,6 @@
LastKnownWaypoint_Label=Last Known Location
Route_End_Label=End
Route_Label=As-the-crow-flies Route
Route_Start_Label=Start
SearchWaypoint_DisplayLabel=GPS Search
TrackpointWaypoint_DisplayLabel=GPS Trackpoint
@@ -0,0 +1,85 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.Map;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Waypoint wrapper class for TSK_METADATA_EXIF artifacts.
*/
final class EXIFWaypoint extends ArtifactWaypoint {
/**
* Construct a way point with the given artifact.
*
* @param artifact BlackboardArtifact for waypoint
*
* @throws GeoLocationDataException
*/
EXIFWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, getAttributesFromArtifactAsMap(artifact), getImageFromArtifact(artifact));
}
/**
* Constructs new waypoint using the given artifact and attribute map.
*
* @param artifact Waypoint BlackboardArtifact
* @param attributeMap Map of artifact attributes
* @param image EXIF AbstractFile image
*
* @throws GeoLocationDataException
*/
private EXIFWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap, AbstractFile image) throws GeoLocationDataException {
super(artifact,
image != null ? image.getName() : "",
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED).getValueLong() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null,
image, attributeMap);
}
/**
* Gets the image from the given artifact.
*
* @param artifact BlackboardArtifact for waypoint
*
* @return AbstractFile image for this waypoint or null if one is not
* available
*
* @throws GeoLocationDataException
*/
private static AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException {
AbstractFile abstractFile = null;
BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID());
if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) {
try {
abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
} catch (TskCoreException ex) {
throw new GeoLocationDataException(String.format("Unable to getAbstractFileByID for artifactID: %d", artifact.getArtifactID()), ex);
}
}
return abstractFile;
}
}
@@ -0,0 +1,47 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
/**
* An exception class for the Geolocation dateModel;
*
*/
public class GeoLocationDataException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Create exception containing the error message
*
* @param msg the message
*/
public GeoLocationDataException(String msg) {
super(msg);
}
/**
* Create exception containing the error message and cause exception
*
* @param msg the message
* @param ex cause exception
*/
public GeoLocationDataException(String msg, Exception ex) {
super(msg, ex);
}
}
@@ -0,0 +1,82 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
/**
* A Last Known Location Waypoint object.
*/
final class LastKnownWaypoint extends ArtifactWaypoint {
/**
* Constructs a new waypoint.
*
* @param artifact BlackboardArtifact from which to construct the waypoint
*
* @throws GeoLocationDataException
*/
LastKnownWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, getAttributesFromArtifactAsMap(artifact));
}
/**
* Constructs a new waypoint with the given artifact and attribute map.
*
* @param artifact BlackboardArtifact from which to construct the
* waypoint
* @param attributeMap Map of artifact attributes
*
* @throws GeoLocationDataException
*/
private LastKnownWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact,
getLabelFromArtifact(attributeMap),
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null,
null, attributeMap);
}
/**
* Gets the label for a TSK_LAST_KNOWN_LOCATION.
*
* @param attributeMap Map of artifact attributes for this waypoint
*
* @return String value from attribute TSK_NAME or LastKnownWaypoint_Label
*
* @throws GeoLocationDataException
*/
@Messages({
"LastKnownWaypoint_Label=Last Known Location",})
private static String getLabelFromArtifact(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
String label = attribute.getDisplayString();
if (label == null || label.isEmpty()) {
label = Bundle.LastKnownWaypoint_Label();
}
return label;
}
}
@@ -0,0 +1,189 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A Route represents a TSK_GPS_ROUTE artifact which has a start and end point
* however the class was written with the assumption that some routes may have
* more that two points.
*
*/
public final class Route {
private final List<Waypoint> points;
private final Long timestamp;
private final Double altitude;
// This list is not expected to change after construction so the
// constructor will take care of creating an unmodifiable List
private final List<Waypoint.Property> immutablePropertiesList;
/**
* Gets the list of Routes from the TSK_GPS_ROUTE artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Route objects, empty list will be returned if no Routes
* were found
*
* @throws GeoLocationDataException
*/
static public List<Route> getRoutes(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try {
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE);
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);
}
List<Route> routes = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
Route route = new Route(artifact);
routes.add(route);
}
return routes;
}
/**
* Construct a route for the given artifact.
*
* @param artifact TSK_GPS_ROUTE artifact object
*/
Route(BlackboardArtifact artifact) throws GeoLocationDataException {
points = new ArrayList<>();
Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap = ArtifactWaypoint.getAttributesFromArtifactAsMap(artifact);
points.add(getRouteStartPoint(attributeMap));
points.add(getRouteEndPoint(attributeMap));
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
altitude = attribute != null ? attribute.getValueDouble() : null;
attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
timestamp = attribute != null ? attribute.getValueLong() : null;
immutablePropertiesList = Collections.unmodifiableList(Waypoint.createGeolocationProperties(attributeMap));
}
/**
* Get the list of way points for this route;
*
* @return List an unmodifiableList of ArtifactWaypoints for this route
*/
public List<Waypoint> getRoute() {
return Collections.unmodifiableList(points);
}
/**
* Get the timestamp for this Route
*
* @return The timestamp (java/unix epoch seconds) or null if none was set.
*/
public Long getTimestamp() {
return timestamp;
}
/**
* Get the altitude for this route.
*
* @return The Double altitude value or null if none was set.
*/
public Double getAltitude() {
return altitude;
}
/**
* Get the "Other attributes" for this route. The map will contain display
* name, formatted value pairs. This list is unmodifiable.
*
* @return Map of key, value pairs.
*/
public List<Waypoint.Property> getOtherProperties() {
return immutablePropertiesList;
}
/**
* Get the route label.
*/
@Messages({
// This is the original static hardcoded label from the
// original kml-report code
"Route_Label=As-the-crow-flies Route"
})
public String getLabel() {
return Bundle.Route_Label();
}
/**
* Get the route start point.
*
* @param attributeMap Map of artifact attributes for this waypoint
*
* @return Start RoutePoint
*
* @throws GeoLocationDataException when longitude or latitude is null
*/
@Messages({
"Route_Start_Label=Start"
})
private Waypoint getRouteStartPoint(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START);
BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START);
if (latitude != null && longitude != null) {
return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_Start_Label());
} else {
throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude");
}
}
/**
* Get the route End point.
*
* @param attributeMap Map of artifact attributes for this waypoint
*
* @return End RoutePoint or null if valid longitude and latitude are not
* found
*
* @throws GeoLocationDataException when longitude or latitude is null
*/
@Messages({
"Route_End_Label=End"
})
private Waypoint getRouteEndPoint(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END);
BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END);
if (latitude != null && longitude != null) {
return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_End_Label());
}else {
throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude");
}
}
}
@@ -0,0 +1,85 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.List;
import org.sleuthkit.datamodel.AbstractFile;
/**
* A point in a Route. For future use this point will have a pointer to its
* parent route.
*/
final class RoutePoint implements Waypoint {
private final Route parent;
private final Double longitude;
private final Double latitude;
private final String label;
/**
* Construct a route for a route.
*
* @param parent The parent route object.
* @param latitude Latitude for point
* @param longitude Longitude for point
* @param label Way point label.
*/
RoutePoint(Route parent, double latitude, double longitude, String label) {
this.longitude = longitude;
this.latitude = latitude;
this.label = label;
this.parent = parent;
}
@Override
public Long getTimestamp() {
return parent.getTimestamp();
}
@Override
public String getLabel() {
return label;
}
@Override
public Double getLatitude() {
return latitude;
}
@Override
public Double getLongitude() {
return longitude;
}
@Override
public Double getAltitude() {
return parent.getAltitude();
}
@Override
public List<Waypoint.Property> getOtherProperties() {
return parent.getOtherProperties();
}
@Override
public AbstractFile getImage() {
return null;
}
}
@@ -0,0 +1,76 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
/**
* A SearchWaypoint is a subclass of ArtifactWaypoint.
*/
final class SearchWaypoint extends ArtifactWaypoint {
/**
* Construct a waypoint for TSK_GPS_SEARCH artifact.
*
* @throws GeoLocationDataException
*/
SearchWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, getAttributesFromArtifactAsMap(artifact));
}
private SearchWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact,
getLabelFromArtifact(attributeMap),
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null,
null, attributeMap);
}
/**
* Returns a Label for a GPS_SEARCH artifact.
*
* @param attributeMap Map of artifact attributes
*
* @return String label for the artifacts way point.
*
* @throws GeoLocationDataException
*/
@Messages({
"SearchWaypoint_DisplayLabel=GPS Search"
})
private static String getLabelFromArtifact(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (attribute != null) {
return attribute.getDisplayString();
}
attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION);
if (attribute != null) {
return attribute.getDisplayString();
}
return Bundle.SearchWaypoint_DisplayLabel();
}
}
@@ -0,0 +1,82 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
/**
* A wrapper class for TSK_GPS_TRACKPOINT artifacts.
*/
final class TrackpointWaypoint extends ArtifactWaypoint {
/**
* Construct a waypoint for trackpoints.
*
* @throws GeoLocationDataException
*/
TrackpointWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, getAttributesFromArtifactAsMap(artifact));
}
private TrackpointWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact,
getLabelFromArtifact(attributeMap),
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null,
attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null,
null, attributeMap);
}
/**
* Returns a Label for a GPS_Trackpoint artifact. This function assumes the
* calling function has already checked TSK_NAME.
*
* @param artifact BlackboardArtifact for waypoint
*
* @return String label for the artifacts way point.
*
* @throws GeoLocationDataException
*/
@Messages({
"TrackpointWaypoint_DisplayLabel=GPS Trackpoint"
})
private static String getLabelFromArtifact(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (attribute != null) {
return attribute.getDisplayString();
}
attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME);
if (attribute != null) {
return attribute.getDisplayString();
}
attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG);
if (attribute != null) {
return attribute.getDisplayString();
}
return Bundle.TrackpointWaypoint_DisplayLabel();
}
}
@@ -0,0 +1,359 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> 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.geolocation.datamodel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* The basic details of a waypoint.
*
*/
public interface Waypoint {
static final Logger logger = Logger.getLogger(Waypoint.class.getName());
/**
* This is a list of attributes that are already being handled by the
* waypoint classes and will have get functions.
*/
BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = {
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,};
/**
* Interface to describe a waypoint. A waypoint is made up of
* a longitude, latitude, label, timestamp, type, image and altitude.
*
* A good way point should have at minimum a longitude and latutude.
*
* @return Timestamp in java/unix epoch seconds or null if none was set.
*/
Long getTimestamp();
/**
* Get the label for this point object.
*
* @return String label for the point or null if none was set
*/
String getLabel();
/**
* Get the latitude for this point.
*
* @return Returns the latitude for the point or null if none was set
*/
Double getLatitude();
/**
* Get the longitude for this point.
*
* @return Returns the longitude for the point or null if none was set
*/
Double getLongitude();
/**
* Get the altitude for this point.
*
* @return Returns the altitude for the point or null if none was set
*/
Double getAltitude();
/**
* Gets an unmodifiable List of other properties that may be interesting to this way point.
* The List will not include properties for which getter functions
* exist.
*
* @return A List of waypoint properties
*/
List<Property> getOtherProperties();
/**
* Get the image for this waypoint.
*
* @return AbstractFile image or null if one was not set
*/
AbstractFile getImage();
/**
* Returns a list of Waypoints for the artifacts with geolocation
* information.
*
* List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH
* TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static List<Waypoint> getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<Waypoint> points = new ArrayList<>();
points.addAll(getTrackpointWaypoints(skCase));
points.addAll(getEXIFWaypoints(skCase));
points.addAll(getSearchWaypoints(skCase));
points.addAll(getLastKnownWaypoints(skCase));
points.addAll(getBookmarkWaypoints(skCase));
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static List<Waypoint> getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try{
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT);
} catch(TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex);
}
List<Waypoint> points = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
try{
ArtifactWaypoint point = new TrackpointWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID()));
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_METADATA_EXIF artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static List<Waypoint> getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try{
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
} catch(TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try{
ArtifactWaypoint point = new EXIFWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
// I am a little relucant to log this error because I suspect
// this will happen more often than not. It is valid for
// METADAT_EXIF to not have longitude and latitude
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_SEARCH artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static List<Waypoint> getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try{
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH);
} catch(TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try{
ArtifactWaypoint point = new SearchWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static List<Waypoint> getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try{
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION);
} catch(TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try{
ArtifactWaypoint point = new LastKnownWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static List<Waypoint> getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try{
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK);
} catch(TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try{
ArtifactWaypoint point = new ArtifactWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Get a list of Waypoint.Property objects for the given artifact. This list
* will not include attributes that the Waypoint interfact has get functions
* for.
*
* @param artifact Blackboard artifact to get attributes\properties from
*
* @return A List of Waypoint.Property objects
*
* @throws GeoLocationDataException
*/
static List<Waypoint.Property> createGeolocationProperties(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
List<Waypoint.Property> list = new ArrayList<>();
Set<BlackboardAttribute.ATTRIBUTE_TYPE> keys = attributeMap.keySet();
for (BlackboardAttribute.ATTRIBUTE_TYPE type : ALREADY_HANDLED_ATTRIBUTES) {
keys.remove(type);
}
for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) {
String key = type.getDisplayName();
String value = attributeMap.get(type).getDisplayString();
list.add(new Waypoint.Property(key, value));
}
return list;
}
/**
* Simple property class for waypoint properties that a purely
* informational.
*/
class Property {
private final String displayName;
private final String value;
/**
* Construct a Property object.
*
* @param displayName String display name for property. Ideally not null
* or empty string.
* @param value String value for property. Can be null.
*/
Property(String displayName, String value) {
this.displayName = displayName;
this.value = value;
}
/**
* Get the display name for this property.
*
* @return String display name.
*/
public String getDisplayName() {
return displayName;
}
/**
* Get the property value.
*
* @return String value.
*/
public String getValue() {
return value;
}
}
}
@@ -23,3 +23,10 @@ ReportKML.genReport.srcModuleName.text=Geospatial Data
ReportKML.genReport.reportName=KML Report
ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n
ReportKML.latLongEndPoint={0};{1};;{2} (End)\n
Route_Details_Header=GPS Route
Waypoint_Bookmark_Display_String=GPS Bookmark
Waypoint_EXIF_Display_String=EXIF Metadata With Location
Waypoint_Last_Known_Display_String=GPS Last Known Location
Waypoint_Route_Point_Display_String=GPS Individual Route Point
Waypoint_Search_Display_String=GPS Search
Waypoint_Trackpoint_Display_String=GPS Trackpoint
@@ -21,13 +21,10 @@ package org.sleuthkit.autopsy.report.modules.kml;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import javax.swing.JPanel;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.*;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.BlackboardArtifact;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -36,6 +33,7 @@ import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.logging.Level;
import org.jdom2.Document;
import org.jdom2.Element;
@@ -44,10 +42,18 @@ import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.CDATA;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
import org.sleuthkit.autopsy.report.ReportBranding;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Generates a KML file based on geospatial information from the BlackBoard.
@@ -63,7 +69,14 @@ class KMLReport implements GeneralReportModule {
private SleuthkitCase skCase;
private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
private Namespace ns;
private final String SEP = "<br>";
private final static String HTML_PROP_FORMAT = "<b>%s: </b>%s<br>";
private Element gpsExifMetadataFolder;
private Element gpsBookmarksFolder;
private Element gpsLastKnownLocationFolder;
private Element gpsRouteFolder;
private Element gpsSearchesFolder;
private Element gpsTrackpointsFolder;
private enum FeatureColor {
RED("style.kml#redFeature"),
@@ -101,7 +114,7 @@ class KMLReport implements GeneralReportModule {
* @param baseReportDir path to save the report
* @param progressPanel panel to update the report's progress
*/
@NbBundle.Messages({
@Messages({
"KMLReport.unableToExtractPhotos=Could not extract photo information.",
"KMLReport.exifPhotoError=Could not extract photos with EXIF metadata.",
"KMLReport.bookmarkError=Could not extract Bookmark information.",
@@ -111,14 +124,22 @@ class KMLReport implements GeneralReportModule {
"KMLReport.gpsRouteError=Could not extract GPS Route information.",
"KMLReport.gpsRouteDatabaseError=Could not get GPS Routes from database.",
"KMLReport.gpsSearchDatabaseError=Could not get GPS Searches from database.",
"KMLReport.trackpointError=Could not extract Trackpoint information.",
"KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
"KMLReport.trackpointError=Could not extract Trackpoint information.",
"KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
"KMLReport.stylesheetError=Error placing KML stylesheet. The .KML file will not function properly.",
"KMLReport.kmlFileWriteError=Could not write the KML file.",
"# {0} - filePath",
"KMLReport.errorGeneratingReport=Error adding {0} to case as a report.",
"KMLReport.unableToOpenCase=Exception while getting open case."
"KMLReport.unableToOpenCase=Exception while getting open case.",
"Waypoint_Bookmark_Display_String=GPS Bookmark",
"Waypoint_Last_Known_Display_String=GPS Last Known Location",
"Waypoint_EXIF_Display_String=EXIF Metadata With Location",
"Waypoint_Route_Point_Display_String=GPS Individual Route Point",
"Waypoint_Search_Display_String=GPS Search",
"Waypoint_Trackpoint_Display_String=GPS Trackpoint",
"Route_Details_Header=GPS Route"
})
@Override
public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) {
try {
@@ -133,272 +154,20 @@ class KMLReport implements GeneralReportModule {
progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.querying"));
String kmlFileFullPath = baseReportDir + REPORT_KML; //NON-NLS
String errorMessage = "";
skCase = currentCase.getSleuthkitCase();
progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.loading"));
ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS
Element kml = new Element("kml", ns); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS
Document kmlDocument = new Document(kml);
Element document = new Element("Document", ns); //NON-NLS
kml.addContent(document);
Element name = new Element("name", ns); //NON-NLS
ReportBranding rb = new ReportBranding();
name.setText(rb.getReportTitle() + " KML"); //NON-NLS
document.addContent(name);
// Check if ingest has finished
if (IngestManager.getInstance().isIngestRunning()) {
Element ingestwarning = new Element("snippet", ns); //NON-NLS
ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS
document.addContent(ingestwarning);
}
// Create folder structure
Element gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS
Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS
Element gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS
Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS
gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS
Element gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS
Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS
Element gpsRouteFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS
gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS
Element gpsSearchesFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS
Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS
gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS
Element gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS
gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS
gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS
gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS
document.addContent(gpsExifMetadataFolder);
document.addContent(gpsBookmarksFolder);
document.addContent(gpsLastKnownLocationFolder);
document.addContent(gpsRouteFolder);
document.addContent(gpsSearchesFolder);
document.addContent(gpsTrackpointsFolder);
Document kmlDocument = setupReportDocument();
ReportProgressPanel.ReportStatus result = ReportProgressPanel.ReportStatus.COMPLETE;
/**
* In the following code, nulls are okay, and are handled when we go to
* write out the KML feature. Nulls are expected to be returned from any
* method where the artifact is not found and is handled in the
* individual feature creation methods. This is done because we don't
* know beforehand which attributes will be included for which artifact,
* as anyone could write a module that adds additional attributes to an
* artifact.
*
* If there are any issues reading the database getting artifacts and
* attributes, or any exceptions thrown during this process, a severe
* error is logged, the report is marked as "Incomplete KML Report", and
* we use a best-effort method to generate KML information on everything
* we can successfully pull out of the database.
*/
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) {
String fileName = "";
long fileId = 0;
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED);
String desc = getDescriptionFromArtifact(artifact, "EXIF Metadata With Locations"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE));
if (lat != null && lat != 0.0 && lon != null && lon != 0.0) {
AbstractFile abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
fileName = abstractFile.getName();
fileId = abstractFile.getId();
Path path;
copyFileUsingStream(abstractFile, Paths.get(baseReportDir, abstractFile.getName()).toFile());
try {
path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath()));
} catch (TskCoreException ex) {
path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
}
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
if (path == null) {
path = Paths.get(abstractFile.getName());
}
gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, desc, timestamp, point, path, formattedCoordinates));
}
} catch (ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).", fileName, fileId), ex);
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_unableToExtractPhotos();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_exifPhotoError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Bookmark"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE));
String bookmarkName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
gpsBookmarksFolder.addContent(makePlacemark(bookmarkName, FeatureColor.BLUE, desc, timestamp, point, formattedCoordinates));
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_bookmarkError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_gpsBookmarkError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Last Known Location"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element point = makePoint(lat, lon, alt);
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
gpsLastKnownLocationFolder.addContent(makePlacemark("Last Known Location", FeatureColor.PURPLE, desc, timestamp, point, formattedCoordinates)); //NON-NLS
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_locationError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_locationDatabaseError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Route");
Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START);
Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START);
Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END);
Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END);
Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element route = makeLineString(latitudeStart, longitudeStart, altitude, latitudeEnd, longitudeEnd, altitude);
Element startingPoint = makePoint(latitudeStart, longitudeStart, altitude);
Element endingPoint = makePoint(latitudeEnd, longitudeEnd, altitude);
String formattedCoordinates = String.format("%.2f, %.2f to %.2f, %.2f", latitudeStart, longitudeStart, latitudeEnd, longitudeEnd);
gpsRouteFolder.addContent(makePlacemark("As-the-crow-flies Route", FeatureColor.GREEN, desc, timestamp, route, formattedCoordinates)); //NON-NLS
formattedCoordinates = String.format("%.2f, %.2f", latitudeStart, longitudeStart);
gpsRouteFolder.addContent(makePlacemark("Start", FeatureColor.GREEN, desc, timestamp, startingPoint, formattedCoordinates)); //NON-NLS
formattedCoordinates = String.format("%.2f, %.2f", latitudeEnd, longitudeEnd);
gpsRouteFolder.addContent(makePlacemark("End", FeatureColor.GREEN, desc, timestamp, endingPoint, formattedCoordinates)); //NON-NLS
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_gpsRouteError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_gpsRouteDatabaseError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)) {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Search"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element point = makePoint(lat, lon, alt);
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
String searchName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (searchName == null || searchName.isEmpty()) {
searchName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION);
}
if (searchName == null || searchName.isEmpty()) {
searchName = "GPS Search";
}
gpsSearchesFolder.addContent(makePlacemark(searchName, FeatureColor.WHITE, desc, timestamp, point, formattedCoordinates)); //NON-NLS
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_gpsSearchDatabaseError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Trackpoint"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element point = makePoint(lat, lon, alt);
String formattedCoordinates = String.format("%.2f, %.2f, %.2f", lat, lon, alt);
String trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (trackName == null || trackName.isEmpty()) {
trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME);
}
if (trackName == null || trackName.isEmpty()) {
trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG);
}
if (trackName == null || trackName.isEmpty()) {
trackName = "GPS Trackpoint";
}
gpsTrackpointsFolder.addContent(makePlacemark(trackName, FeatureColor.YELLOW, desc, timestamp, point, formattedCoordinates));
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_trackpointError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_trackpointDatabaseError();
makeRoutes(skCase);
addLocationsToReport(skCase, baseReportDir);
} catch (GeoLocationDataException | IOException ex) {
errorMessage = "Failed to complete report.";
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
@@ -442,247 +211,256 @@ class KMLReport implements GeneralReportModule {
}
/**
* Get a Double from an artifact if it exists, return null otherwise.
* Do all of the setting up of elements needed for the report.
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
*
* @return The Double if it exists, or null if not
* @return The report document object.
*/
private Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
Double returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
Double value = bba.getValueDouble();
returnValue = value;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting Double value: " + type.toString(), ex); //NON-NLS
private Document setupReportDocument() {
ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS
Element kml = new Element("kml", ns); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS
Document kmlDocument = new Document(kml);
Element document = new Element("Document", ns); //NON-NLS
kml.addContent(document);
Element name = new Element("name", ns); //NON-NLS
ReportBranding rb = new ReportBranding();
name.setText(rb.getReportTitle() + " KML"); //NON-NLS
document.addContent(name);
// Check if ingest has finished
if (IngestManager.getInstance().isIngestRunning()) {
Element ingestwarning = new Element("snippet", ns); //NON-NLS
ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS
document.addContent(ingestwarning);
}
return returnValue;
// Create folder structure
gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS
Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS
gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS
Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS
gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS
gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS
Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS
gpsRouteFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS
gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS
gpsSearchesFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS
Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS
gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS
gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS
gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS
gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS
gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS
document.addContent(gpsExifMetadataFolder);
document.addContent(gpsBookmarksFolder);
document.addContent(gpsLastKnownLocationFolder);
document.addContent(gpsRouteFolder);
document.addContent(gpsSearchesFolder);
document.addContent(gpsTrackpointsFolder);
return kmlDocument;
}
/**
* Get a Long from an artifact if it exists, return null otherwise.
* For the given point, create the data needed for the EXIF_METADATA
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
* @param location The geolocation of the data
* @param baseReportDirectory The report directory where the image will be
* created.
*
* @return The Long if it exists, or null if not
* @throws IOException
*/
private Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
Long returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
Long value = bba.getValueLong();
returnValue = value;
void addExifMetadataContent(List<Waypoint> points, String baseReportDirectory) throws IOException {
for(Waypoint point: points) {
Element mapPoint = makePoint(point);
if (mapPoint == null) {
return;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting Long value: " + type.toString(), ex); //NON-NLS
AbstractFile abstractFile = point.getImage();
String details = getFormattedDetails(point, Bundle.Waypoint_EXIF_Display_String());
Path path;
copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile());
try {
path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath()));
} catch (TskCoreException ex) {
path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
}
if (path == null) {
path = Paths.get(abstractFile.getName());
}
gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, formattedCoordinates(point.getLatitude(), point.getLongitude())));
}
return returnValue;
}
/**
* Get an Integer from an artifact if it exists, return null otherwise.
* Add the new location to the correct folder based on artifact type.
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
* @param skCase Currently open case
* @param baseReportDir Output directory for the report.
*
* @return The Integer if it exists, or null if not
* @throws TskCoreException
* @throws IOException
*/
private Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
Integer returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
Integer value = bba.getValueInt();
returnValue = value;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting Integer value: " + type.toString(), ex); //NON-NLS
void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException {
addExifMetadataContent(Waypoint.getEXIFWaypoints(skCase), baseReportDir);
addWaypoints(Waypoint.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
addWaypoints(Waypoint.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
addWaypoints(Waypoint.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
addWaypoints(Waypoint.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
}
/**
* For each point in the waypoint list an Element to represent the given waypoint
* is created and added it to the given Element folder.
*
* @param points List of waypoints to add to the report
* @param folder The Element folder to add the points to
* @param waypointColor The color the waypoint should appear in the report
*/
void addWaypoints(List<Waypoint> points, Element folder, FeatureColor waypointColor, String headerLabel) {
for(Waypoint point: points) {
addContent(folder, point.getLabel(), waypointColor, getFormattedDetails(point, headerLabel), point.getTimestamp(), makePoint(point), point.getLatitude(), point.getLongitude());
}
}
/**
* Adds the waypoint Element with details to the report in the given folder.
*
* @param folder Element folder to add the waypoint to
* @param waypointLabel String waypoint Label
* @param waypointColor FeatureColor for the waypoint
* @param formattedDetails String HTML formatted waypoint details
* @param timestamp Long timestamp (unix\jave epoch seconds)
* @param point Element point object
* @param latitude Double latitude value
* @param longitude Double longitude value
*/
void addContent(Element folder, String waypointLabel, FeatureColor waypointColor, String formattedDetails, Long timestamp, Element point, Double latitude, Double longitude) {
if(folder != null && point != null) {
String formattedCords = formattedCoordinates(latitude, longitude);
folder.addContent(makePlacemark(waypointLabel, waypointColor, formattedDetails, timestamp, point, formattedCords));
}
return returnValue;
}
/**
* Get a String from an artifact if it exists, return null otherwise.
* Add the route to the route folder in the document.
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
* @param skCase Currently open case.
*
* @return The String if it exists, or null if not
* @throws TskCoreException
*/
private String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
String returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
String value = bba.getValueString();
if (value != null && !value.isEmpty()) {
returnValue = value;
}
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting String value: " + type.toString(), ex); //NON-NLS
void makeRoutes(SleuthkitCase skCase) throws GeoLocationDataException {
List<Route> routes = Route.getRoutes(skCase);
if(routes == null) {
return;
}
for (Route route : routes) {
addRouteToReport(route);
}
}
void addRouteToReport(Route route) {
List<Waypoint> routePoints = route.getRoute();
Waypoint start = null;
Waypoint end = null;
// This is hardcoded knowledge that there is only two points
// a start and end. In the long run it would be nice to
// support the idea of a route with multiple points. The Route
// class supports that idea. Would be nice to figure out how to support
// for report.
if (routePoints != null && routePoints.size() > 1) {
start = routePoints.get(0);
end = routePoints.get(1);
}
if (start == null || end == null) {
return;
}
Element reportRoute = makeLineString(start.getLatitude(), start.getLongitude(), end.getLatitude(), end.getLongitude());
Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude());
Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude());
String formattedEnd = formattedCoordinates(end.getLatitude(), end.getLongitude());
String formattedStart = formattedCoordinates(start.getLatitude(), start.getLongitude());
String formattedCoordinates = String.format("%s to %s", formattedStart, formattedEnd);
if (reportRoute != null) {
gpsRouteFolder.addContent(makePlacemark(route.getLabel(), FeatureColor.GREEN, getFormattedDetails(route), route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS
}
if (startingPoint != null) {
gpsRouteFolder.addContent(makePlacemark(start.getLabel(),
FeatureColor.GREEN, getFormattedDetails(start, Bundle.Waypoint_Route_Point_Display_String()),
start.getTimestamp(), startingPoint, formattedStart)); //NON-NLS
}
if (endingPoint != null) {
gpsRouteFolder.addContent(makePlacemark(end.getLabel(),
FeatureColor.GREEN,
getFormattedDetails(end, Bundle.Waypoint_Route_Point_Display_String()),
end.getTimestamp(), endingPoint, formattedEnd)); //NON-NLS
}
return returnValue;
}
/**
* This method creates a text description for a map feature using all the
* geospatial and time data we can for the Artifact. It queries the
* following attributes:
* Format a point time stamp (in seconds) to the report format.
*
* TSK_GEO_LATITUDE 54; TSK_GEO_LONGITUDE 55; TSK_GEO_LATITUDE_START 98;
* TSK_GEO_LATITUDE_END 99; TSK_GEO_LONGITUDE_START 100;
* TSK_GEO_LONGITUDE_END 101; TSK_GEO_VELOCITY 56; TSK_GEO_ALTITUDE 57;
* TSK_GEO_BEARING 58; TSK_GEO_HPRECISION 59; TSK_GEO_VPRECISION 60;
* TSK_GEO_MAPDATUM 61; TSK_DATETIME_START 83; TSK_DATETIME_END 84;
* TSK_LOCATION 86; TSK_PATH_SOURCE 94;
* @param timeStamp The timestamp in epoch seconds.
*
* @param artifact the artifact to query.
* @param featureType the type of Artifact we're working on.
*
* @return a String with the information we have available
* @return The formatted timestamp
*/
private String getDescriptionFromArtifact(BlackboardArtifact artifact, String featureType) {
StringBuilder result = new StringBuilder("<h3>" + featureType + "</h3>"); //NON-NLS
String name = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (name != null && !name.isEmpty()) {
result.append("<b>Name:</b> ").append(name).append(SEP); //NON-NLS
}
String location = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION);
if (location != null && !location.isEmpty()) {
result.append("<b>Location:</b> ").append(location).append(SEP); //NON-NLS
}
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
if (timestamp != null) {
result.append("<b>Timestamp:</b> ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS
result.append("<b>Unix timestamp:</b> ").append(timestamp).append(SEP); //NON-NLS
}
Long startingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START);
if (startingTimestamp != null) {
result.append("<b>Starting Timestamp:</b> ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS
result.append("<b>Starting Unix timestamp:</b> ").append(startingTimestamp).append(SEP); //NON-NLS
}
Long endingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END);
if (endingTimestamp != null) {
result.append("<b>Ending Timestamp:</b> ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS
result.append("<b>Ending Unix timestamp:</b> ").append(endingTimestamp).append(SEP); //NON-NLS
}
Long createdTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED);
if (createdTimestamp != null) {
result.append("<b>Created Timestamp:</b> ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS
result.append("<b>Created Unix timestamp:</b> ").append(createdTimestamp).append(SEP); //NON-NLS
}
Double latitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
if (latitude != null) {
result.append("<b>Latitude:</b> ").append(latitude).append(SEP); //NON-NLS
}
Double longitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
if (longitude != null) {
result.append("<b>Longitude:</b> ").append(longitude).append(SEP); //NON-NLS
}
Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START);
if (latitudeStart != null) {
result.append("<b>Latitude Start:</b> ").append(latitudeStart).append(SEP); //NON-NLS
}
Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START);
if (longitudeStart != null) {
result.append("<b>Longitude Start:</b> ").append(longitudeStart).append(SEP); //NON-NLS
}
Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END);
if (latitudeEnd != null) {
result.append("<b>Latitude End:</b> ").append(latitudeEnd).append(SEP); //NON-NLS
}
Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END);
if (longitudeEnd != null) {
result.append("<b>Longitude End:</b> ").append(longitudeEnd).append(SEP); //NON-NLS
}
Double velocity = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY);
if (velocity != null) {
result.append("<b>Velocity:</b> ").append(velocity).append(SEP); //NON-NLS
}
Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
if (altitude != null) {
result.append("<b>Altitude:</b> ").append(altitude).append(SEP); //NON-NLS
}
Double bearing = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING);
if (bearing != null) {
result.append("<b>Bearing:</b> ").append(bearing).append(SEP); //NON-NLS
}
Integer hPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION);
if (hPrecision != null) {
result.append("<b>Horizontal Precision Figure of Merit:</b> ").append(hPrecision).append(SEP); //NON-NLS
}
Integer vPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION);
if (vPrecision != null) {
result.append("<b>Vertical Precision Figure of Merit:</b> ").append(vPrecision).append(SEP); //NON-NLS
}
String mapDatum = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM);
if (mapDatum != null && !mapDatum.isEmpty()) {
result.append("<b>Map Datum:</b> ").append(mapDatum).append(SEP); //NON-NLS
}
String programName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME);
if (programName != null && !programName.isEmpty()) {
result.append("<b>Reported by:</b> ").append(programName).append(SEP); //NON-NLS
}
String flag = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG);
if (flag != null && !flag.isEmpty()) {
result.append("<b>Flag:</b> ").append(flag).append(SEP); //NON-NLS
}
String pathSource = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE);
if (pathSource != null && !pathSource.isEmpty()) {
result.append("<b>Source:</b> ").append(pathSource).append(SEP); //NON-NLS
}
String deviceMake = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE);
if (deviceMake != null && !deviceMake.isEmpty()) {
result.append("<b>Device Make:</b> ").append(deviceMake).append(SEP); //NON-NLS
}
String deviceModel = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL);
if (deviceModel != null && !deviceModel.isEmpty()) {
result.append("<b>Device Model:</b> ").append(deviceModel).append(SEP); //NON-NLS
}
return result.toString();
}
private String getTimeStamp(long timeStamp) {
return kmlDateFormat.format(new java.util.Date(timeStamp * 1000));
}
/**
* Create the point for the given artifact.
*
* @param point Artifact point.
*
* @return point element.
*/
private Element makePoint(Waypoint point) {
return makePoint(point.getLatitude(), point.getLongitude(), point.getAltitude());
}
/**
* Create a Point for use in a Placemark. Note in this method altitude is
* ignored, as Google Earth apparently has trouble using altitudes for
* LineStrings, though the parameters are still in the call. Also note that
* any null value passed in will be set to 0.0, under the idea that it is
* better to show some data with gaps, than to show nothing at all.
* LineStrings, though the parameters are still in the call.
*
* @param latitude point latitude
* @param longitude point longitude
@@ -691,21 +469,16 @@ class KMLReport implements GeneralReportModule {
* @return the Point as an Element
*/
private Element makePoint(Double latitude, Double longitude, Double altitude) {
if (latitude == null) {
latitude = 0.0;
}
if (longitude == null) {
longitude = 0.0;
}
if (altitude == null) {
altitude = 0.0;
if (latitude == null || longitude == null) {
return null;
}
Element point = new Element("Point", ns); //NON-NLS
// KML uses lon, lat. Deliberately reversed.
Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + altitude); //NON-NLS
// KML uses lon, lat. Deliberately reversed.1
Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + (altitude != null ? altitude : 0.0)); //NON-NLS
if (altitude != 0) {
if (altitude != null && altitude != 0) {
/*
* Though we are including a non-zero altitude, clamp it to the
* ground because inaccuracies from the GPS data can cause the
@@ -725,9 +498,10 @@ class KMLReport implements GeneralReportModule {
* Create a LineString for use in a Placemark. Note in this method, start
* and stop altitudes get ignored, as Google Earth apparently has trouble
* using altitudes for LineStrings, though the parameters are still in the
* call. Also note that any null value passed in will be set to 0.0, under
* the idea that it is better to show some data with gaps, than to show
* nothing at all.
* call.
*
* If null values are pass for the latitudes or longitudes a line will not
* be drawn.
*
* @param startLatitude Starting latitude
* @param startLongitude Starting longitude
@@ -738,24 +512,9 @@ class KMLReport implements GeneralReportModule {
*
* @return the Line as an Element
*/
private Element makeLineString(Double startLatitude, Double startLongitude, Double startAltitude, Double stopLatitude, Double stopLongitude, Double stopAltitude) {
if (startLatitude == null) {
startLatitude = 0.0;
}
if (startLongitude == null) {
startLongitude = 0.0;
}
if (startAltitude == null) {
startAltitude = 0.0;
}
if (stopLatitude == null) {
stopLatitude = 0.0;
}
if (stopLongitude == null) {
stopLongitude = 0.0;
}
if (stopAltitude == null) {
stopAltitude = 0.0;
private Element makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude) {
if (startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) {
return null;
}
Element lineString = new Element("LineString", ns); //NON-NLS
@@ -925,4 +684,104 @@ class KMLReport implements GeneralReportModule {
}
return strbuf.toString();
}
/**
* Get the nicely formatted details for the given waypoint.
*
* @param point Waypoint object
* @param header String details header
*
* @return HTML formatted String of details for given waypoint
*/
private String getFormattedDetails(Waypoint point, String header) {
StringBuilder result = new StringBuilder(); //NON-NLS
result.append(String.format("<h3>%s</h3>", header))
.append(formatAttribute("Name", point.getLabel()));
Long timestamp = point.getTimestamp();
if (timestamp != null) {
result.append(formatAttribute("Timestamp", getTimeStamp(timestamp)));
}
result.append(formatAttribute("Latitude", point.getLatitude().toString()))
.append(formatAttribute("Longitude", point.getLongitude().toString()));
if (point.getAltitude() != null) {
result.append(formatAttribute("Altitude", point.getAltitude().toString()));
}
List<Waypoint.Property> list = point.getOtherProperties();
for(Waypoint.Property prop: list) {
String value = prop.getValue();
if(value != null && !value.isEmpty()) {
result.append(formatAttribute(prop.getDisplayName(), value));
}
}
return result.toString();
}
private String formatAttribute(String title, String value) {
return String.format(HTML_PROP_FORMAT, title, value);
}
/**
* Returns an HTML formatted string of all the
*
* @param route
*
* @return
*/
private String getFormattedDetails(Route route) {
List<Waypoint> points = route.getRoute();
StringBuilder result = new StringBuilder(); //NON-NLS
result.append(String.format("<h3>%s</h3>", Bundle.Route_Details_Header()))
.append(formatAttribute("Name", route.getLabel()));
Long timestamp = route.getTimestamp();
if (timestamp != null) {
result.append(formatAttribute("Timestamp", getTimeStamp(timestamp)));
}
if (points.size() > 1) {
Waypoint start = points.get(0);
Waypoint end = points.get(1);
result.append(formatAttribute("Start Latitude", start.getLatitude().toString()))
.append(formatAttribute("Start Longitude", start.getLongitude().toString()))
.append(formatAttribute("End Latitude", end.getLatitude().toString()))
.append(formatAttribute("End Longitude", end.getLongitude().toString()));
}
if (route.getAltitude() != null) {
result.append(formatAttribute("Altitude", route.getAltitude().toString()));
}
List<Waypoint.Property> list = route.getOtherProperties();
for(Waypoint.Property prop: list) {
String value = prop.getValue();
if(value != null && !value.isEmpty()) {
result.append(formatAttribute(prop.getDisplayName(), value));
}
}
return result.toString();
}
/**
* Helper functions for consistently formatting longitude and latitude.
*
* @param latitude Double latitude value
* @param longitude Double longitude value
*
* @return String Nicely formatted double values separated by a comma
*/
private String formattedCoordinates(Double latitude, Double longitude) {
if (latitude == null || longitude == null) {
return "";
}
return String.format("%.2f, %.2f", latitude, longitude);
}
}
@@ -2,16 +2,15 @@ OptionsCategory_Name_Machine_Translation=Machine Translation
OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings
TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB)
TranslatedContentPanel.comboBoxOption.translatedText=Translated Text
TranslatedContentViewer.emptyTranslation=The resulting translation was empty.
TranslatedContentViewer.errorExtractingText=Could not extract text from file.
TranslatedContentViewer.errorMsg=Error encountered while getting file text.
TranslatedContentViewer.extractingFileText=Extracting text from file, please wait...
TranslatedContentViewer.extractingImageText=Extracting text from image, please wait...
TranslatedContentViewer.noIndexedTextMsg=Run the Keyword Search Ingest Module to get text for translation.
TranslatedContentViewer.noServiceProvider=Machine Translation software was not found.
TranslatedContentViewer.textAlreadyIndexed=Please view the original text in the Indexed Text viewer.
TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text.
# {0} - exception message
TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).
TranslatedContentViewer.extractingText=Extracting text, please wait...
TranslatedContentViewer.fileHasNoText=File has no text.
TranslatedContentViewer.noServiceProvider=The machine translation software was not found.
TranslatedContentViewer.translatingText=Translating text, please wait...
TranslatedContentViewer.translationException=Error encountered while attempting translation.
# {0} - exception message
TranslatedContentViewer.translationException=An error occurred while translating the text ({0}).
TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated
TranslatedTextViewer.title=Translation
TranslatedTextViewer.toolTip=Displays translated file text.
@@ -53,6 +53,7 @@ import org.sleuthkit.autopsy.texttranslation.TranslationException;
import org.sleuthkit.datamodel.Content;
import java.util.List;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayDropdownOptions;
@@ -63,7 +64,7 @@ import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayD
@ServiceProvider(service = TextViewer.class, position = 4)
public final class TranslatedTextViewer implements TextViewer {
private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName());
private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName());
private static final boolean OCR_ENABLED = true;
private static final boolean OCR_DISABLED = false;
@@ -72,7 +73,7 @@ public final class TranslatedTextViewer implements TextViewer {
private final TranslationContentPanel panel = new TranslationContentPanel();
private volatile Node node;
private volatile BackgroundTranslationTask updateTask;
private volatile ExtractAndTranslateTextTask backgroundTask;
private final ThreadFactory translationThreadFactory
= new ThreadFactoryBuilder().setNameFormat("translation-content-viewer-%d").build();
private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory);
@@ -95,7 +96,7 @@ public final class TranslatedTextViewer implements TextViewer {
panel.addLanguagePackNames(INSTALLED_LANGUAGE_PACKS);
}
}
int payloadMaxInKB = TextTranslationService.getInstance().getMaxTextChars() / 1000;
panel.setWarningLabelMsg(String.format(Bundle.TranslatedTextViewer_maxPayloadSize(), payloadMaxInKB));
@@ -129,10 +130,10 @@ public final class TranslatedTextViewer implements TextViewer {
public void resetComponent() {
panel.reset();
this.node = null;
if (updateTask != null) {
updateTask.cancel(true);
if (backgroundTask != null) {
backgroundTask.cancel(true);
}
updateTask = null;
backgroundTask = null;
}
@Override
@@ -157,62 +158,74 @@ public final class TranslatedTextViewer implements TextViewer {
}
/**
* Fetches file text and performs translation.
* Extracts text from a file and optionally translates it.
*/
private class BackgroundTranslationTask extends SwingWorker<String, Void> {
private class ExtractAndTranslateTextTask extends SwingWorker<String, Void> {
private final AbstractFile file;
private final boolean translateText;
private ExtractAndTranslateTextTask(AbstractFile file, boolean translateText) {
this.file = file;
this.translateText = translateText;
}
@NbBundle.Messages({
"TranslatedContentViewer.noIndexedTextMsg=Run the Keyword Search Ingest Module to get text for translation.",
"TranslatedContentViewer.textAlreadyIndexed=Please view the original text in the Indexed Text viewer.",
"TranslatedContentViewer.errorMsg=Error encountered while getting file text.",
"TranslatedContentViewer.errorExtractingText=Could not extract text from file.",
"TranslatedContentViewer.translatingText=Translating text, please wait..."
"TranslatedContentViewer.extractingText=Extracting text, please wait...",
"TranslatedContentViewer.translatingText=Translating text, please wait...",
"# {0} - exception message", "TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).",
"TranslatedContentViewer.fileHasNoText=File has no text.",
"TranslatedContentViewer.noServiceProvider=The machine translation software was not found.",
"# {0} - exception message", "TranslatedContentViewer.translationException=An error occurred while translating the text ({0})."
})
@Override
public String doInBackground() throws InterruptedException {
if (this.isCancelled()) {
throw new InterruptedException();
}
String dropdownSelection = panel.getDisplayDropDownSelection();
if (dropdownSelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString())) {
try {
return getFileText(node);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error getting text", ex);
return Bundle.TranslatedContentViewer_errorMsg();
} catch (TextExtractor.InitReaderException ex) {
logger.log(Level.WARNING, "Error getting text", ex);
return Bundle.TranslatedContentViewer_errorExtractingText();
}
} else {
try {
return translate(getFileText(node));
} catch (IOException ex) {
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_errorMsg();
} catch (TextExtractor.InitReaderException ex) {
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_errorExtractingText();
}
SwingUtilities.invokeLater(() -> {
panel.display(Bundle.TranslatedContentViewer_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
});
String fileText;
try {
fileText = getFileText(file);
} catch (IOException | TextExtractor.InitReaderException ex) {
logger.log(Level.WARNING, String.format("Error extracting text for file %s (objId=%d)", file.getName(), file.getId()), ex);
return Bundle.TranslatedContentViewer_errorExtractingText(ex.getMessage());
}
}
/**
* Update the extraction loading message depending on the file type.
*
* @param isImage Boolean indicating if the selecting node is an image
*/
@NbBundle.Messages({"TranslatedContentViewer.extractingImageText=Extracting text from image, please wait...",
"TranslatedContentViewer.extractingFileText=Extracting text from file, please wait...",})
private void updateExtractionLoadingMessage(boolean isImage) {
if (isImage) {
panel.display(Bundle.TranslatedContentViewer_extractingImageText(),
ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
} else {
panel.display(Bundle.TranslatedContentViewer_extractingFileText(),
ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
if (this.isCancelled()) {
throw new InterruptedException();
}
if (fileText == null || fileText.isEmpty()) {
return Bundle.TranslatedContentViewer_fileHasNoText();
}
if (!this.translateText) {
return fileText;
}
SwingUtilities.invokeLater(() -> {
panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
});
String translation;
try {
translation = translate(fileText);
} catch (NoServiceProviderException ex) {
logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex);
translation = Bundle.TranslatedContentViewer_noServiceProvider();
} catch (TranslationException ex) {
logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex);
translation = Bundle.TranslatedContentViewer_translationException(ex.getMessage());
}
if (this.isCancelled()) {
throw new InterruptedException();
}
return translation;
}
@Override
@@ -227,8 +240,12 @@ public final class TranslatedTextViewer implements TextViewer {
String orientDetectSubstring = result.substring(0, maxOrientChars);
ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring);
panel.display(result, orientation, Font.PLAIN);
} catch (InterruptedException | ExecutionException | CancellationException ignored) {
//InterruptedException & CancellationException - User cancelled, no error.
} catch (InterruptedException | CancellationException ignored) {
// Task cancelled, no error.
} catch (ExecutionException ex) {
logger.log(Level.WARNING, String.format("Error occurred during background task execution for file %s (objId=%d)", file.getName(), file.getId()), ex);
panel.display(Bundle.TranslatedContentViewer_translationException(ex.getMessage()), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
}
}
@@ -240,36 +257,21 @@ public final class TranslatedTextViewer implements TextViewer {
* @return Translated text or error message
*/
@NbBundle.Messages({
"TranslatedContentViewer.emptyTranslation=The resulting translation was empty.",
"TranslatedContentViewer.noServiceProvider=Machine Translation software was not found.",
"TranslatedContentViewer.translationException=Error encountered while attempting translation."})
private String translate(String input) throws InterruptedException {
if (this.isCancelled()) {
throw new InterruptedException();
}
panel.display(Bundle.TranslatedContentViewer_translatingText(),
ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
try {
TextTranslationService translatorInstance = TextTranslationService.getInstance();
String translatedResult = translatorInstance.translate(input);
if (translatedResult.isEmpty()) {
return Bundle.TranslatedContentViewer_emptyTranslation();
}
return translatedResult;
} catch (NoServiceProviderException ex) {
return Bundle.TranslatedContentViewer_noServiceProvider();
} catch (TranslationException ex) {
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_translationException() + " (" + ex.getMessage() + ")";
"TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text."
})
private String translate(String input) throws NoServiceProviderException, TranslationException {
TextTranslationService translatorInstance = TextTranslationService.getInstance();
String translatedResult = translatorInstance.translate(input);
if (translatedResult.isEmpty()) {
return Bundle.TranslatedContentViewer_emptyTranslation();
}
return translatedResult;
}
/**
* Extracts text from the given node
*
* @param node Selected node in UI
* @param file Selected node in UI
*
* @return Extracted text
*
@@ -277,33 +279,22 @@ public final class TranslatedTextViewer implements TextViewer {
* @throws InterruptedException
* @throws
* org.sleuthkit.autopsy.textextractors.TextExtractor.InitReaderException
* @throws NoOpenCoreException
* @throws KeywordSearchModuleException
*/
private String getFileText(Node node) throws IOException,
private String getFileText(AbstractFile file) throws IOException,
InterruptedException, TextExtractor.InitReaderException {
AbstractFile source = (AbstractFile) DataContentViewerUtility.getDefaultContent(node);
boolean isImage = false;
if (source != null) {
isImage = source.getMIMEType().toLowerCase().startsWith("image/");
}
updateExtractionLoadingMessage(isImage);
final boolean isImage = file.getMIMEType().toLowerCase().startsWith("image/"); // NON-NLS
String result;
if (isImage) {
result = extractText(source, OCR_ENABLED);
result = extractText(file, OCR_ENABLED);
} else {
result = extractText(source, OCR_DISABLED);
result = extractText(file, OCR_DISABLED);
}
//Correct for UTF-8
byte[] resultInUTF8Bytes = result.getBytes("UTF8");
byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0,
Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES) );
byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0,
Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES));
return new String(trimToArraySize, "UTF-8");
}
@@ -348,7 +339,7 @@ public final class TranslatedTextViewer implements TextViewer {
textBuilder.append(cbuf, 0, read);
bytesRead += read;
}
return textBuilder.toString();
}
@@ -399,23 +390,28 @@ public final class TranslatedTextViewer implements TextViewer {
*/
private abstract class SelectionChangeListener implements ActionListener {
public String currentSelection = null;
private String currentSelection;
public abstract String getSelection();
abstract String getSelection();
@Override
public final void actionPerformed(ActionEvent e) {
String selection = getSelection();
if (!selection.equals(currentSelection)) {
currentSelection = selection;
if (updateTask != null && !updateTask.isDone()) {
updateTask.cancel(true);
if (backgroundTask != null && !backgroundTask.isDone()) {
backgroundTask.cancel(true);
}
updateTask = new BackgroundTranslationTask();
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
String textDisplaySelection = panel.getDisplayDropDownSelection();
boolean translateText = !textDisplaySelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString());
backgroundTask = new ExtractAndTranslateTextTask(file, translateText);
//Pass the background task to a single threaded pool to keep
//the number of jobs running to one.
executorService.execute(updateTask);
executorService.execute(backgroundTask);
}
}
}
@@ -426,7 +422,7 @@ public final class TranslatedTextViewer implements TextViewer {
private class DisplayDropDownChangeListener extends SelectionChangeListener {
@Override
public String getSelection() {
String getSelection() {
return panel.getDisplayDropDownSelection();
}
}
@@ -437,7 +433,7 @@ public final class TranslatedTextViewer implements TextViewer {
private class OCRDropdownChangeListener extends SelectionChangeListener {
@Override
public String getSelection() {
String getSelection() {
return panel.getSelectedOcrLanguagePack();
}
}