mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-02 15:29:54 +00:00
Merge branch '5370-CacheMultipleSearches' of https://github.com/wschaeferB/autopsy into 5677-DisplayVisualStatusIndicators
This commit is contained in:
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+197
@@ -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;
|
||||
}
|
||||
}
|
||||
+6
@@ -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;
|
||||
}
|
||||
}
|
||||
+47
@@ -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);
|
||||
}
|
||||
}
|
||||
+82
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user