mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 10:17:41 +00:00
Merge remote-tracking branch 'upstream/develop' into 5618-show-country-code-for-acct-number-in-the-cvt-acct-table
This commit is contained in:
commit
0809c92c23
36
.travis.yml
36
.travis.yml
@ -1,8 +1,11 @@
|
||||
language: java
|
||||
sudo: required
|
||||
dist: bionic
|
||||
os:
|
||||
- linux
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: linux
|
||||
dist: bionic
|
||||
- os: osx
|
||||
|
||||
env:
|
||||
global:
|
||||
@ -12,6 +15,7 @@ addons:
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- testdisk
|
||||
- libafflib-dev
|
||||
- libewf-dev
|
||||
- libpq-dev
|
||||
@ -29,11 +33,13 @@ addons:
|
||||
update: true
|
||||
packages:
|
||||
- ant
|
||||
- ant-optional
|
||||
- wget
|
||||
- libpq
|
||||
- libewf
|
||||
- gettext
|
||||
- cppunit
|
||||
- afflib
|
||||
- testdisk
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
@ -43,9 +49,7 @@ before_install:
|
||||
- python setupSleuthkitBranch.py
|
||||
|
||||
install:
|
||||
- sudo apt-get install testdisk
|
||||
- cd sleuthkit/sleuthkit
|
||||
- ./travis_install_libs.sh
|
||||
- pushd sleuthkit/sleuthkit && ./travis_install_libs.sh && popd
|
||||
|
||||
before_script:
|
||||
- if [ $TRAVIS_OS_NAME = linux ]; then
|
||||
@ -54,13 +58,13 @@ before_script:
|
||||
export PATH=/usr/bin:$PATH;
|
||||
unset JAVA_HOME;
|
||||
fi
|
||||
- if [ $TRAVIS_OS_NAME = osx ]; then
|
||||
brew uninstall java --force;
|
||||
brew cask uninstall java --force;
|
||||
brew tap homebrew/cask-versions;
|
||||
brew cask install corretto8;
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home;
|
||||
fi
|
||||
- java -version
|
||||
|
||||
script:
|
||||
- set -e
|
||||
- echo "Building TSK..."
|
||||
- ./bootstrap && ./configure --prefix=/usr && make
|
||||
- pushd bindings/java/ && ant -q dist-PostgreSQL && popd
|
||||
- echo "Building Autopsy..." && echo -en 'travis_fold:start:script.build\\r'
|
||||
- cd $TRAVIS_BUILD_DIR/
|
||||
- ant build
|
||||
- echo -en 'travis_fold:end:script.build\\r'
|
||||
script: ./travis_build.sh
|
||||
|
@ -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 -->
|
||||
|
115
Core/ivy.xml
115
Core/ivy.xml
@ -1,56 +1,59 @@
|
||||
<ivy-module version="2.0">
|
||||
<info organisation="org.sleuthkit.autopsy" module="emailparser"/>
|
||||
<configurations >
|
||||
<!-- module dependencies -->
|
||||
<conf name="core"/>
|
||||
|
||||
</configurations>
|
||||
<dependencies >
|
||||
|
||||
<dependency conf="core->default" org="com.github.jgraph" name="jgraphx" rev="v3.8.0"/>
|
||||
|
||||
<dependency conf="core->default" org="org.apache.activemq" name="activemq-all" rev="5.11.1"/>
|
||||
<dependency conf="core->default" org="org.apache.curator" name="curator-client" rev="2.8.0"/>
|
||||
<dependency conf="core->default" org="org.apache.curator" name="curator-framework" rev="2.8.0"/>
|
||||
<dependency conf="core->default" org="org.apache.curator" name="curator-recipes" rev="2.8.0"/>
|
||||
|
||||
<dependency conf="core->default" org="org.python" name="jython-standalone" rev="2.7.0" />
|
||||
<dependency conf="core->default" org="org.apache.tika" name="tika-core" rev="1.20"/>
|
||||
<!-- Exclude the version of cxf-rt-rs-client from Tika 1.20, one of its depedencies breaks Ivy -->
|
||||
<dependency conf="core->default" org="org.apache.tika" name="tika-parsers" rev="1.20">
|
||||
<exclude module="cxf-rt-rs-client"/>
|
||||
<exclude module="cleartk-ml"/>
|
||||
</dependency>
|
||||
|
||||
<!-- Pull down the latest cxf-rt-rs-client which has the Ivy fix -->
|
||||
<dependency conf="core->default" org="org.apache.cxf" name="cxf-rt-rs-client" rev="3.3.0"/>
|
||||
<dependency conf="core->default" org="org.cleartk" name="cleartk-ml" rev="2.0.0"/>
|
||||
|
||||
<dependency conf="core->default" org="com.adobe.xmp" name="xmpcore" rev="5.1.2"/>
|
||||
<dependency conf="core->default" org="org.apache.zookeeper" name="zookeeper" rev="3.4.6"/>
|
||||
|
||||
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess" rev="2.2.0"/>
|
||||
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess-encrypt" rev="2.1.4"/>
|
||||
|
||||
<dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
|
||||
<dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
|
||||
<dependency conf="core->default" org="commons-codec" name="commons-codec" rev="1.11"/>
|
||||
|
||||
<dependency conf="core->default" org="org.jsoup" name="jsoup" rev="1.10.3"/>
|
||||
|
||||
<dependency conf="core->default" org="com.fasterxml.jackson.core" name="jackson-core" rev="2.9.7"/>
|
||||
|
||||
<dependency conf="core->default" org="com.google.cloud" name="google-cloud-translate" rev="1.70.0"/>
|
||||
<dependency conf="core->default" org="org.apache.opennlp" name="opennlp-tools" rev="1.9.1"/>
|
||||
|
||||
<dependency org="org.sejda.webp-imageio" name="webp-imageio-sejda" rev="0.1.0"/>
|
||||
<dependency org="com.googlecode.libphonenumber" name="libphonenumber" rev="3.5" />
|
||||
|
||||
<dependency conf="core->default" org="commons-validator" name="commons-validator" rev="1.6"/>
|
||||
<dependency conf="core->default" org="net.htmlparser.jericho" name="jericho-html" rev="3.3"/>
|
||||
<dependency org="com.squareup.okhttp" name="okhttp" rev="2.7.5"/>
|
||||
<!-- https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api -->
|
||||
<dependency org="javax.ws.rs" name="javax.ws.rs-api" rev="2.0"/>
|
||||
<override org="jakarta.ws.rs" module="jakarta.ws.rs-api" rev="2.1.5"/>
|
||||
</dependencies>
|
||||
</ivy-module>
|
||||
<ivy-module version="2.0">
|
||||
<info organisation="org.sleuthkit.autopsy" module="emailparser"/>
|
||||
<configurations >
|
||||
<!-- module dependencies -->
|
||||
<conf name="core"/>
|
||||
|
||||
</configurations>
|
||||
<dependencies >
|
||||
|
||||
<dependency conf="core->default" org="com.github.jgraph" name="jgraphx" rev="v3.8.0"/>
|
||||
|
||||
<dependency conf="core->default" org="org.apache.activemq" name="activemq-all" rev="5.11.1"/>
|
||||
<dependency conf="core->default" org="org.apache.curator" name="curator-client" rev="2.8.0"/>
|
||||
<dependency conf="core->default" org="org.apache.curator" name="curator-framework" rev="2.8.0"/>
|
||||
<dependency conf="core->default" org="org.apache.curator" name="curator-recipes" rev="2.8.0"/>
|
||||
|
||||
<dependency conf="core->default" org="org.python" name="jython-standalone" rev="2.7.0" />
|
||||
<dependency conf="core->default" org="org.apache.tika" name="tika-core" rev="1.20"/>
|
||||
<!-- Exclude the version of cxf-rt-rs-client from Tika 1.20, one of its depedencies breaks Ivy -->
|
||||
<dependency conf="core->default" org="org.apache.tika" name="tika-parsers" rev="1.20">
|
||||
<exclude module="cxf-rt-rs-client"/>
|
||||
<exclude module="cleartk-ml"/>
|
||||
</dependency>
|
||||
|
||||
<!-- Pull down the latest cxf-rt-rs-client which has the Ivy fix -->
|
||||
<dependency conf="core->default" org="org.apache.cxf" name="cxf-rt-rs-client" rev="3.3.0"/>
|
||||
<dependency conf="core->default" org="org.cleartk" name="cleartk-ml" rev="2.0.0"/>
|
||||
|
||||
<dependency conf="core->default" org="com.adobe.xmp" name="xmpcore" rev="5.1.2"/>
|
||||
<dependency conf="core->default" org="org.apache.zookeeper" name="zookeeper" rev="3.4.6"/>
|
||||
|
||||
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess" rev="2.2.0"/>
|
||||
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess-encrypt" rev="2.1.4"/>
|
||||
|
||||
<dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
|
||||
<dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
|
||||
<dependency conf="core->default" org="commons-codec" name="commons-codec" rev="1.11"/>
|
||||
|
||||
<dependency conf="core->default" org="org.jsoup" name="jsoup" rev="1.10.3"/>
|
||||
|
||||
<dependency conf="core->default" org="com.fasterxml.jackson.core" name="jackson-core" rev="2.9.7"/>
|
||||
|
||||
<dependency conf="core->default" org="com.google.cloud" name="google-cloud-translate" rev="1.70.0"/>
|
||||
<dependency conf="core->default" org="org.apache.opennlp" name="opennlp-tools" rev="1.9.1"/>
|
||||
|
||||
<dependency org="org.sejda.webp-imageio" name="webp-imageio-sejda" rev="0.1.0"/>
|
||||
<dependency org="com.googlecode.libphonenumber" name="libphonenumber" rev="3.5" />
|
||||
<dependency conf="core->default" org="commons-validator" name="commons-validator" rev="1.6"/>
|
||||
<dependency conf="core->default" org="net.htmlparser.jericho" name="jericho-html" rev="3.3"/>
|
||||
<dependency org="com.squareup.okhttp" name="okhttp" rev="2.7.5"/>
|
||||
|
||||
<!-- map support for geolocation -->
|
||||
<dependency conf="core->default" org="org.jxmapviewer" name="jxmapviewer2" rev="2.4"/>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api -->
|
||||
<dependency org="javax.ws.rs" name="javax.ws.rs-api" rev="2.0"/>
|
||||
<override org="jakarta.ws.rs" module="jakarta.ws.rs-api" rev="2.1.5"/>
|
||||
</dependencies>
|
||||
</ivy-module>
|
||||
|
@ -50,6 +50,7 @@ file.reference.jsoup-1.11.3.jar=release\\modules\\ext\\jsoup-1.11.3.jar
|
||||
file.reference.jul-to-slf4j-1.7.25.jar=release\\modules\\ext\\jul-to-slf4j-1.7.25.jar
|
||||
file.reference.juniversalchardet-1.0.3.jar=release\\modules\\ext\\juniversalchardet-1.0.3.jar
|
||||
file.reference.junrar-2.0.0.jar=release\\modules\\ext\\junrar-2.0.0.jar
|
||||
file.reference.jxmapviewer2-2.4.jar=release/modules/ext/jxmapviewer2-2.4.jar
|
||||
file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar
|
||||
file.reference.libphonenumber-3.5.jar=release/modules/ext/libphonenumber-3.5.jar
|
||||
file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar
|
||||
|
@ -327,6 +327,7 @@
|
||||
<package>org.sleuthkit.autopsy.datasourceprocessors</package>
|
||||
<package>org.sleuthkit.autopsy.directorytree</package>
|
||||
<package>org.sleuthkit.autopsy.events</package>
|
||||
<package>org.sleuthkit.autopsy.exceptions</package>
|
||||
<package>org.sleuthkit.autopsy.filesearch</package>
|
||||
<package>org.sleuthkit.autopsy.guiutils</package>
|
||||
<package>org.sleuthkit.autopsy.healthmonitor</package>
|
||||
@ -453,6 +454,10 @@
|
||||
<runtime-relative-path>ext/commons-pool2-2.4.2.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/commons-pool2-2.4.2.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/jxmapviewer2-2.4.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/jxmapviewer2-2.4.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/jdom-2.0.5-contrib.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/jdom-2.0.5-contrib.jar</binary-origin>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
@ -72,15 +73,24 @@ public class AddBlackboardArtifactTagAction extends AddTagAction {
|
||||
|
||||
@Override
|
||||
protected void addTag(TagName tagName, String comment) {
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this invocation
|
||||
* of addTag(), we don't want to tag the same BlackboardArtifact more
|
||||
* than once, so we dedupe the BlackboardArtifacts by stuffing them into
|
||||
* a HashSet.
|
||||
*/
|
||||
final Collection<BlackboardArtifact> selectedArtifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
|
||||
|
||||
final Collection<BlackboardArtifact> selectedArtifacts = new HashSet<>();
|
||||
//If the contentToTag is empty look up the selected content
|
||||
if (getContentToTag().isEmpty()) {
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this
|
||||
* invocation of addTag(), we don't want to tag the same
|
||||
* BlackboardArtifact more than once, so we dedupe the
|
||||
* BlackboardArtifacts by stuffing them into a HashSet.
|
||||
*/
|
||||
selectedArtifacts.addAll(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
|
||||
} else {
|
||||
for (Content content : getContentToTag()) {
|
||||
if (content instanceof BlackboardArtifact) {
|
||||
selectedArtifacts.add((BlackboardArtifact) content);
|
||||
}
|
||||
}
|
||||
}
|
||||
new Thread(() -> {
|
||||
for (BlackboardArtifact artifact : selectedArtifacts) {
|
||||
try {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -76,16 +76,26 @@ public class AddContentTagAction extends AddTagAction {
|
||||
|
||||
@Override
|
||||
protected void addTag(TagName tagName, String comment) {
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this invocation
|
||||
* of addTag(), we don't want to tag the same AbstractFile more than
|
||||
* once, so we dedupe the AbstractFiles by stuffing them into a HashSet.
|
||||
*
|
||||
* We don't want VirtualFile and DerivedFile objects to be tagged.
|
||||
*/
|
||||
final Collection<AbstractFile> selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
|
||||
final Collection<AbstractFile> selectedFiles = new HashSet<>();
|
||||
//If the contentToTag is empty look up the selected content
|
||||
if (getContentToTag().isEmpty()) {
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this
|
||||
* invocation of addTag(), we don't want to tag the same
|
||||
* AbstractFile more than once, so we dedupe the AbstractFiles by
|
||||
* stuffing them into a HashSet.
|
||||
*
|
||||
* We don't want VirtualFile and DerivedFile objects to be tagged.
|
||||
*/
|
||||
selectedFiles.addAll(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
} else {
|
||||
for (Content content : getContentToTag()) {
|
||||
if (content instanceof AbstractFile) {
|
||||
selectedFiles.add((AbstractFile) content);
|
||||
}
|
||||
}
|
||||
}
|
||||
new Thread(() -> {
|
||||
for (AbstractFile file : selectedFiles) {
|
||||
try {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -20,6 +20,9 @@ package org.sleuthkit.autopsy.actions;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
@ -33,6 +36,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
@ -45,6 +49,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String NO_COMMENT = "";
|
||||
private final Collection<Content> content = new HashSet<>();
|
||||
|
||||
AddTagAction(String menuText) {
|
||||
super(menuText);
|
||||
@ -52,6 +57,32 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
|
||||
|
||||
@Override
|
||||
public JMenuItem getPopupPresenter() {
|
||||
content.clear();
|
||||
return new TagMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection of content which may have been specified for this
|
||||
* action. Empty collection returned when no content was specified.
|
||||
*
|
||||
* @return The specified content for this action.
|
||||
*/
|
||||
Collection<Content> getContentToTag() {
|
||||
return Collections.unmodifiableCollection(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the menu for adding tags to the specified collection of Content.
|
||||
*
|
||||
* @param contentToTag The collection of Content the menu actions will be
|
||||
* applied to.
|
||||
*
|
||||
* @return The menu which will allow users to choose the tag they want to
|
||||
* apply to the Content specified.
|
||||
*/
|
||||
public JMenuItem getMenuForContent(Collection<? extends Content> contentToTag) {
|
||||
content.clear();
|
||||
content.addAll(contentToTag);
|
||||
return new TagMenu();
|
||||
}
|
||||
|
||||
@ -118,26 +149,26 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
|
||||
tagNameItem.addActionListener((ActionEvent e) -> {
|
||||
getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT);
|
||||
});
|
||||
|
||||
// Show custom tags before predefined tags in the menu
|
||||
|
||||
// Show custom tags before predefined tags in the menu
|
||||
if (standardTagNames.contains(tagDisplayName)) {
|
||||
standardTagMenuitems.add(tagNameItem);
|
||||
} else {
|
||||
add(tagNameItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (getItemCount() > 0) {
|
||||
addSeparator();
|
||||
}
|
||||
|
||||
|
||||
standardTagMenuitems.forEach((menuItem) -> {
|
||||
add(menuItem);
|
||||
});
|
||||
|
||||
|
||||
addSeparator();
|
||||
|
||||
|
||||
// Create a "Choose Tag and Comment..." menu item. Selecting this item initiates
|
||||
// a dialog that can be used to create or select a tag name with an
|
||||
// optional comment and adds a tag with the resulting name.
|
||||
@ -150,7 +181,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
|
||||
}
|
||||
});
|
||||
add(tagAndCommentItem);
|
||||
|
||||
|
||||
// Create a "New Tag..." menu item.
|
||||
// Selecting this item initiates a dialog that can be used to create
|
||||
// or select a tag name and adds a tag with the resulting name.
|
||||
@ -162,7 +193,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
|
||||
}
|
||||
});
|
||||
add(newTagMenuItem);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,10 +225,10 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
|
||||
tagName = openCase.getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName);
|
||||
} catch (TskCoreException ex1) {
|
||||
Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database but an error occurred in retrieving it.", ex1); //NON-NLS
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
addTag(tagName, comment);
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2017-2018 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2017-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.
|
||||
@ -53,7 +53,7 @@ import org.sleuthkit.datamodel.TskData;
|
||||
"DeleteFileContentTagAction.deleteTag=Remove File Tag"
|
||||
})
|
||||
public class DeleteFileContentTagAction extends AbstractAction implements Presenter.Popup {
|
||||
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DeleteFileContentTagAction.class.getName());
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@ -81,6 +81,19 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
|
||||
return new TagMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the menu for removing tags from the specified collection of Files.
|
||||
*
|
||||
* @param selectedFiles The collection of AbstractFiles the menu actions
|
||||
* will be applied to.
|
||||
*
|
||||
* @return The menu which will allow users to remove tags from the specified
|
||||
* collection of Files.
|
||||
*/
|
||||
public JMenuItem getMenuForFiles(Collection<AbstractFile> selectedFiles) {
|
||||
return new TagMenu(selectedFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
}
|
||||
@ -102,19 +115,19 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
|
||||
tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Error untagging file. No open case found.", ex); //NON-NLS
|
||||
Platform.runLater(() ->
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show()
|
||||
Platform.runLater(()
|
||||
-> new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show()
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
logger.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), contentTag.getContent().getName()}); //NON-NLS
|
||||
tagsManager.deleteContentTag(contentTag);
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS
|
||||
Platform.runLater(() ->
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show()
|
||||
Platform.runLater(()
|
||||
-> new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@ -141,15 +154,24 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Construct an TagMenu object using the specified collection of files
|
||||
* as the files to remove a tag from.
|
||||
*/
|
||||
TagMenu() {
|
||||
this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an TagMenu object using the specified collection of files
|
||||
* as the files to remove a tag from.
|
||||
*/
|
||||
TagMenu(Collection<AbstractFile> selectedFiles) {
|
||||
super(getActionDisplayName());
|
||||
|
||||
final Collection<AbstractFile> selectedAbstractFilesList =
|
||||
new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
|
||||
if(!selectedAbstractFilesList.isEmpty()) {
|
||||
AbstractFile file = selectedAbstractFilesList.iterator().next();
|
||||
|
||||
|
||||
if (!selectedFiles.isEmpty()) {
|
||||
AbstractFile file = selectedFiles.iterator().next();
|
||||
|
||||
Map<String, TagName> tagNamesMap = null;
|
||||
List<String> standardTagNames = TagsManager.getStandardTagNames();
|
||||
List<JMenuItem> standardTagMenuitems = new ArrayList<>();
|
||||
@ -167,22 +189,22 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
|
||||
// a tag with the associated tag name.
|
||||
if (null != tagNamesMap && !tagNamesMap.isEmpty()) {
|
||||
try {
|
||||
List<ContentTag> existingTagsList =
|
||||
Case.getCurrentCaseThrows().getServices().getTagsManager()
|
||||
List<ContentTag> existingTagsList
|
||||
= Case.getCurrentCaseThrows().getServices().getTagsManager()
|
||||
.getContentTagsByContent(file);
|
||||
|
||||
for (Map.Entry<String, TagName> entry : tagNamesMap.entrySet()) {
|
||||
String tagDisplayName = entry.getKey();
|
||||
|
||||
TagName tagName = entry.getValue();
|
||||
for(ContentTag contentTag : existingTagsList) {
|
||||
if(tagDisplayName.equals(contentTag.getName().getDisplayName())) {
|
||||
for (ContentTag contentTag : existingTagsList) {
|
||||
if (tagDisplayName.equals(contentTag.getName().getDisplayName())) {
|
||||
String notableString = tagName.getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : "";
|
||||
JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString);
|
||||
tagNameItem.addActionListener((ActionEvent e) -> {
|
||||
deleteTag(tagName, contentTag, file.getId());
|
||||
});
|
||||
|
||||
|
||||
// Show custom tags before predefined tags in the menu
|
||||
if (standardTagNames.contains(tagDisplayName)) {
|
||||
standardTagMenuitems.add(tagNameItem);
|
||||
@ -198,14 +220,14 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
|
||||
}
|
||||
}
|
||||
|
||||
if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty() ){
|
||||
if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty()) {
|
||||
addSeparator();
|
||||
}
|
||||
standardTagMenuitems.forEach((menuItem) -> {
|
||||
add(menuItem);
|
||||
});
|
||||
|
||||
if(getItemCount() == 0) {
|
||||
|
||||
if (getItemCount() == 0) {
|
||||
setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -24,6 +24,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.DateFormat;
|
||||
@ -427,9 +428,10 @@ public final class CaseMetadata {
|
||||
transformer.transform(source, streamResult);
|
||||
|
||||
/*
|
||||
* Write the DOM to the metadata file.
|
||||
* Write the DOM to the metadata file. Add UTF-8 Characterset so it writes to the file
|
||||
* correctly for non-latin characters
|
||||
*/
|
||||
try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile())))) {
|
||||
try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile()), StandardCharsets.UTF_8))) {
|
||||
fileWriter.write(stringWriter.toString());
|
||||
fileWriter.flush();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -3298,10 +3298,10 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
try {
|
||||
minorVersion = Integer.parseInt(minorVersionStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new EamDbException(Bundle.AbstractSqlEamDb_badMinorSchema_message(minorVersionStr), ex);
|
||||
throw new EamDbException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt", Bundle.AbstractSqlEamDb_badMinorSchema_message(minorVersionStr), ex);
|
||||
}
|
||||
} else {
|
||||
throw new EamDbException(Bundle.AbstractSqlEamDb_failedToReadMinorVersion_message());
|
||||
throw new EamDbException("Failed to read schema minor version from db_info table", Bundle.AbstractSqlEamDb_failedToReadMinorVersion_message());
|
||||
}
|
||||
|
||||
int majorVersion = 0;
|
||||
@ -3312,10 +3312,10 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
try {
|
||||
majorVersion = Integer.parseInt(majorVersionStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new EamDbException(Bundle.AbstractSqlEamDb_badMajorSchema_message(majorVersionStr), ex);
|
||||
throw new EamDbException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt", Bundle.AbstractSqlEamDb_badMajorSchema_message(majorVersionStr), ex);
|
||||
}
|
||||
} else {
|
||||
throw new EamDbException(Bundle.AbstractSqlEamDb_failedToReadMajorVersion_message());
|
||||
throw new EamDbException("Failed to read schema major version from db_info table", Bundle.AbstractSqlEamDb_failedToReadMajorVersion_message());
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3393,7 +3393,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
addObjectIdIndexTemplate = SqliteEamDbSettings.getAddObjectIdIndexTemplate();
|
||||
break;
|
||||
default:
|
||||
throw new EamDbException(Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name()));
|
||||
throw new EamDbException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.", Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name()));
|
||||
}
|
||||
final String dataSourcesTableName = "data_sources";
|
||||
final String dataSourceObjectIdColumnName = "datasource_obj_id";
|
||||
@ -3553,7 +3553,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
statement.execute("DROP TABLE old_data_sources");
|
||||
break;
|
||||
default:
|
||||
throw new EamDbException(Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name()));
|
||||
throw new EamDbException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.", Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name()));
|
||||
}
|
||||
}
|
||||
updateSchemaVersion(conn);
|
||||
|
@ -39,6 +39,7 @@ EamDbUtil.centralRepoUpgradeFailed.message=Failed to upgrade Central Repository.
|
||||
EamDbUtil.exclusiveLockAquisitionFailure.message=Unable to acquire exclusive lock for Central Repository.
|
||||
PostgresEamDb.centralRepoDisabled.message=Central Repository module is not enabled.
|
||||
PostgresEamDb.connectionFailed.message=Error getting connection to database.
|
||||
PostgresEamDb.multiUserLockError.message=Error acquiring database lock
|
||||
SqliteEamDb.centralRepositoryDisabled.message=Central Repository module is not enabled.
|
||||
SqliteEamDb.connectionFailedMessage.message=Error getting connection to database.
|
||||
SqliteEamDb.databaseMissing.message=Central repository database missing
|
||||
|
@ -277,10 +277,10 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
* @param enabled Is this Type currently enabled.
|
||||
*/
|
||||
@Messages({"CorrelationAttributeInstance.nullName.message=Database name is null.",
|
||||
"CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."})
|
||||
"CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."})
|
||||
public Type(int typeId, String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException {
|
||||
if (dbTableName == null) {
|
||||
throw new EamDbException(Bundle.CorrelationAttributeInstance_nullName_message());
|
||||
throw new EamDbException("dbTableName is null", Bundle.CorrelationAttributeInstance_nullName_message());
|
||||
}
|
||||
this.typeId = typeId;
|
||||
this.displayName = displayName;
|
||||
@ -288,7 +288,7 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
this.supported = supported;
|
||||
this.enabled = enabled;
|
||||
if (!Pattern.matches(DB_NAMES_REGEX, dbTableName)) {
|
||||
throw new EamDbException(Bundle.CorrelationAttributeInstance_invalidName_message()); // NON-NLS
|
||||
throw new EamDbException("Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'.", Bundle.CorrelationAttributeInstance_invalidName_message()); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2015-2017 Basis Technology Corp.
|
||||
* Copyright 2015-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,10 +18,12 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.centralrepository.datamodel;
|
||||
|
||||
/*
|
||||
import org.sleuthkit.autopsy.exceptions.AutopsyException;
|
||||
|
||||
/**
|
||||
* An exception to be thrown by an artifact manager.
|
||||
*/
|
||||
public class EamDbException extends Exception {
|
||||
public class EamDbException extends AutopsyException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -34,6 +36,28 @@ public class EamDbException extends Exception {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception to be thrown by an artifact manager with a user exception.
|
||||
*
|
||||
* @param message Exception message.
|
||||
* @param userMessage the user friendly message to include in this exception
|
||||
*/
|
||||
public EamDbException(String message, String userMessage) {
|
||||
super(message, userMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception to be thrown by an artifact manager with a user
|
||||
* exception.
|
||||
*
|
||||
* @param message Exception message.
|
||||
* @param userMessage the user friendly message to include in this exception
|
||||
* @param cause Exception cause.
|
||||
*/
|
||||
public EamDbException(String message, String userMessage, Throwable cause) {
|
||||
super(message, userMessage, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception to be thrown by an artifact manager.
|
||||
*
|
||||
|
@ -181,32 +181,39 @@ public class EamDbUtil {
|
||||
}
|
||||
EamDb db = null;
|
||||
CoordinationService.Lock lock = null;
|
||||
String messageForDialog = "";
|
||||
|
||||
//get connection
|
||||
try {
|
||||
db = EamDb.getInstance();
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex);
|
||||
messageForDialog = Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message();
|
||||
}
|
||||
//get lock necessary for upgrade
|
||||
if (db != null) {
|
||||
try {
|
||||
// This may return null if locking isn't supported, which is fine. It will
|
||||
// throw an exception if locking is supported but we can't get the lock
|
||||
// (meaning the database is in use by another user)
|
||||
lock = db.getExclusiveMultiUserDbLock();
|
||||
//perform upgrade
|
||||
db = EamDb.getInstance();
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex);
|
||||
throw new EamDbException("Error updating central repository, unable to make connection", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
|
||||
}
|
||||
//get lock necessary for upgrade
|
||||
if (db != null) {
|
||||
try {
|
||||
// This may return null if locking isn't supported, which is fine. It will
|
||||
// throw an exception if locking is supported but we can't get the lock
|
||||
// (meaning the database is in use by another user)
|
||||
lock = db.getExclusiveMultiUserDbLock();
|
||||
//perform upgrade
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex);
|
||||
throw new EamDbException("Error updating central repository, unable to acquire exclusive lock", Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
|
||||
}
|
||||
|
||||
try {
|
||||
db.upgradeSchema();
|
||||
} catch (EamDbException | SQLException | IncompatibleCentralRepoException ex) {
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating central repository", ex);
|
||||
messageForDialog = Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message();
|
||||
if (ex instanceof IncompatibleCentralRepoException) {
|
||||
messageForDialog = ex.getMessage() + "\n\n" + messageForDialog;
|
||||
} else if (ex instanceof EamDbException) {
|
||||
messageForDialog = ex.getMessage() + Bundle.EamDbUtil_centralRepoDisabled_message();
|
||||
}
|
||||
throw new EamDbException("Error updating central repository", ex.getUserMessage() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
|
||||
} catch (SQLException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating central repository", ex);
|
||||
throw new EamDbException("Error updating central repository", Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
|
||||
} catch (IncompatibleCentralRepoException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating central repository", ex);
|
||||
throw new EamDbException("Error updating central repository", ex.getMessage() + "\n\n" + Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
|
||||
} finally {
|
||||
if (lock != null) {
|
||||
try {
|
||||
@ -216,16 +223,11 @@ public class EamDbUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex);
|
||||
messageForDialog = Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message();
|
||||
} else {
|
||||
throw new EamDbException("Unable to connect to database", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message());
|
||||
}
|
||||
|
||||
} else {
|
||||
messageForDialog = Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message();
|
||||
}
|
||||
// Disable the central repo and clear the current settings.
|
||||
if (!messageForDialog.isEmpty()) {
|
||||
} catch (EamDbException ex) {
|
||||
// Disable the central repo and clear the current settings.
|
||||
try {
|
||||
if (null != EamDb.getInstance()) {
|
||||
EamDb.getInstance().shutdownConnections();
|
||||
@ -235,8 +237,7 @@ public class EamDbUtil {
|
||||
}
|
||||
EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.DISABLED.name());
|
||||
EamDbPlatformEnum.saveSelectedPlatform();
|
||||
|
||||
throw new EamDbException(messageForDialog);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,7 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
protected Connection connect() throws EamDbException {
|
||||
synchronized (this) {
|
||||
if (!EamDb.isEnabled()) {
|
||||
throw new EamDbException(Bundle.PostgresEamDb_centralRepoDisabled_message()); // NON-NLS
|
||||
throw new EamDbException("Central Repository module is not enabled", Bundle.PostgresEamDb_centralRepoDisabled_message()); // NON-NLS
|
||||
}
|
||||
|
||||
if (connectionPool == null) {
|
||||
@ -200,7 +200,7 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
try {
|
||||
return connectionPool.getConnection();
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException(Bundle.PostgresEamDb_connectionFailed_message(), ex); // NON-NLS
|
||||
throw new EamDbException("Error getting connection from connection pool.", Bundle.PostgresEamDb_connectionFailed_message(), ex); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +221,7 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
* to get the lock
|
||||
*/
|
||||
@Override
|
||||
@Messages({"PostgresEamDb.multiUserLockError.message=Error acquiring database lock"})
|
||||
public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException {
|
||||
try {
|
||||
// First check if multi user mode is enabled - if not there's no point trying to get a lock
|
||||
@ -234,9 +235,9 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
if (lock != null) {
|
||||
return lock;
|
||||
}
|
||||
throw new EamDbException("Error acquiring database lock");
|
||||
throw new EamDbException("Error acquiring database lock", Bundle.PostgresEamDb_multiUserLockError_message());
|
||||
} catch (InterruptedException ex) {
|
||||
throw new EamDbException("Error acquiring database lock");
|
||||
throw new EamDbException("Error acquiring database lock", Bundle.PostgresEamDb_multiUserLockError_message(), ex);
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
// This likely just means the coordination service isn't running, which is ok
|
||||
return null;
|
||||
|
@ -158,7 +158,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
private void setupConnectionPool(boolean foreignKeysEnabled) throws EamDbException {
|
||||
|
||||
if (dbSettings.dbFileExists() == false) {
|
||||
throw new EamDbException(Bundle.SqliteEamDb_databaseMissing_message());
|
||||
throw new EamDbException("Central repository database missing", Bundle.SqliteEamDb_databaseMissing_message());
|
||||
}
|
||||
|
||||
connectionPool = new BasicDataSource();
|
||||
@ -194,7 +194,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
protected Connection connect(boolean foreignKeys) throws EamDbException {
|
||||
synchronized (this) {
|
||||
if (!EamDb.isEnabled()) {
|
||||
throw new EamDbException(Bundle.SqliteEamDb_centralRepositoryDisabled_message()); // NON-NLS
|
||||
throw new EamDbException("Central repository database missing", Bundle.SqliteEamDb_centralRepositoryDisabled_message()); // NON-NLS
|
||||
}
|
||||
if (connectionPool == null) {
|
||||
setupConnectionPool(foreignKeys);
|
||||
@ -202,7 +202,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
try {
|
||||
return connectionPool.getConnection();
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException(Bundle.SqliteEamDb_connectionFailedMessage_message(), ex); // NON-NLS
|
||||
throw new EamDbException("Error getting connection from connection pool.", Bundle.SqliteEamDb_connectionFailedMessage_message(), ex); // NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -64,7 +64,7 @@ public class Installer extends ModuleInstall {
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
WindowManager.getDefault().invokeWhenUIReady(() -> {
|
||||
JOptionPane.showMessageDialog(null,
|
||||
ex.getMessage(),
|
||||
ex.getUserMessage(),
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"Installer.centralRepoUpgradeFailed.title"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
|
@ -33,7 +33,7 @@ EamDbSettingsDialog.validation.finished=Click OK to save your database settings
|
||||
EamDbSettingsDialog.validation.incompleteFields=Fill in all values for the selected database.
|
||||
EamOptionsController.moduleErr=Error processing value changes.
|
||||
EamOptionsController.moduleErr.msg=Value change processing failed.
|
||||
GlobalSettingsPanel.updateFailed.title=Central repository upgrade failed
|
||||
GlobalSettingsPanel.updateFailed.title=Central repository disabled
|
||||
GlobalSettingsPanel.validationErrMsg.ingestRunning=You cannot change settings while ingest is running.
|
||||
GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module.
|
||||
ManageCasesDialog.title.text=Manage Cases
|
||||
|
@ -77,7 +77,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
ingestStateUpdated(Case.isCaseOpen());
|
||||
}
|
||||
|
||||
@Messages({"GlobalSettingsPanel.updateFailed.title=Central repository upgrade failed"})
|
||||
@Messages({"GlobalSettingsPanel.updateFailed.title=Central repository disabled"})
|
||||
private void updateDatabase() {
|
||||
|
||||
if (EamDbPlatformEnum.getSelectedPlatform().equals(DISABLED)) {
|
||||
@ -91,7 +91,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
} catch (EamDbException ex) {
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage(),
|
||||
ex.getUserMessage(),
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"GlobalSettingsPanel.updateFailed.title"),
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
|
@ -26,3 +26,4 @@ SummaryViewer.attachmentsLabel.text=Total Attachments:
|
||||
SummaryViewer.referencesLabel.text=Communication References:
|
||||
SummaryViewer.referencesDataLabel.text=<reference count>
|
||||
SummaryViewer.contactsLabel.text=Book Entries:
|
||||
SummaryViewer.accountCountry.text=<account country>
|
||||
|
@ -47,6 +47,7 @@ SummaryViewer_Account_Description=This account represents a device in the case.
|
||||
SummaryViewer_Account_Description_MuliSelect=Summary information is not available when multiple accounts are selected.
|
||||
SummaryViewer_CaseRefNameColumn_Title=Case Name
|
||||
SummaryViewer_CentralRepository_Message=<Enable Central Respository to see Other Occurrences>
|
||||
SummaryViewer_Country_Code=Country:
|
||||
SummaryViewer_Creation_Date_Title=Creation Date
|
||||
SummaryViewer_Device_Account_Description=This account was referenced by a device in the case.
|
||||
SummaryViewer_FileRef_Message=<Select a single account to see File References>
|
||||
@ -72,3 +73,4 @@ SummaryViewer.attachmentsLabel.text=Total Attachments:
|
||||
SummaryViewer.referencesLabel.text=Communication References:
|
||||
SummaryViewer.referencesDataLabel.text=<reference count>
|
||||
SummaryViewer.contactsLabel.text=Book Entries:
|
||||
SummaryViewer.accountCountry.text=<account country>
|
||||
|
@ -1,154 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<NonVisualComponents>
|
||||
<Container class="javax.swing.JPanel" name="summaryPanel">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="accountLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accountLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="0" insetsRight="9" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="accoutDescriptionLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accoutDescriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="15" insetsRight="9" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="countsPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="Communications">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.countsPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="messagesLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="callLogsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="thumbnailCntLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailCntLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="thumbnailsDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="messagesDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="callLogsDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="attachmentsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="attachmentDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</NonVisualComponents>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
@ -164,38 +16,169 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="fileReferencesPanel">
|
||||
<Container class="javax.swing.JPanel" name="summaryPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="accountLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accountLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="0" insetsRight="9" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="accountCountry">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accountCountry.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="9" insetsBottom="0" insetsRight="9" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="accoutDescriptionLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accoutDescriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="15" insetsRight="9" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="countsPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="File References in Current Case">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<TitledBorder title="Communications">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.countsPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="caseReferencesPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="Other Occurrences">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.caseReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="messagesLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="callLogsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="thumbnailCntLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailCntLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="thumbnailsDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="messagesDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="callLogsDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="attachmentsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="attachmentDataLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="contanctsPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
@ -208,7 +191,7 @@
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
@ -270,5 +253,37 @@
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="fileReferencesPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="File References in Current Case">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="caseReferencesPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="Other Occurrences">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.caseReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -29,6 +29,7 @@ import org.openide.util.Lookup;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.autopsy.coreutils.PhoneNumUtil;
|
||||
|
||||
/**
|
||||
* Account Summary View Panel. This panel shows a list of various counts related
|
||||
@ -49,7 +50,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
"SummaryViewer_FileRef_Message=<Select a single account to see File References>",
|
||||
"SummaryViewer_Device_Account_Description=This account was referenced by a device in the case.",
|
||||
"SummaryViewer_Account_Description=This account represents a device in the case.",
|
||||
"SummaryViewer_Account_Description_MuliSelect=Summary information is not available when multiple accounts are selected."
|
||||
"SummaryViewer_Account_Description_MuliSelect=Summary information is not available when multiple accounts are selected.",
|
||||
"SummaryViewer_Country_Code=Country: "
|
||||
})
|
||||
|
||||
/**
|
||||
@ -110,7 +112,16 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
Account[] accountArray = info.getAccounts().toArray(new Account[1]);
|
||||
Account account = accountArray[0];
|
||||
|
||||
accountLabel.setText(account.getTypeSpecificID());
|
||||
if (account.getAccountType().getTypeName().contains("PHONE")) {
|
||||
String countryCode = PhoneNumUtil.getCountryCode(account.getTypeSpecificID());
|
||||
accountLabel.setText(PhoneNumUtil.convertToInternational(account.getTypeSpecificID()));
|
||||
accountCountry.setText(Bundle.SummaryViewer_Country_Code() + countryCode);
|
||||
accountCountry.setEnabled(true);
|
||||
} else {
|
||||
accountLabel.setText(account.getTypeSpecificID());
|
||||
accountCountry.setText("");
|
||||
accountCountry.setEnabled(false);
|
||||
}
|
||||
|
||||
if (account.getAccountType().equals(Account.Type.DEVICE)) {
|
||||
accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description());
|
||||
@ -173,6 +184,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
accountLabel.setText("");
|
||||
accoutDescriptionLabel.setText("");
|
||||
referencesDataLabel.setText("");
|
||||
accountCountry.setText("");
|
||||
|
||||
fileReferencesPanel.setNode(new AbstractNode(Children.LEAF));
|
||||
caseReferencesPanel.setNode(new AbstractNode(Children.LEAF));
|
||||
@ -210,6 +222,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
|
||||
summaryPanel = new javax.swing.JPanel();
|
||||
accountLabel = new javax.swing.JLabel();
|
||||
accountCountry = new javax.swing.JLabel();
|
||||
accoutDescriptionLabel = new javax.swing.JLabel();
|
||||
countsPanel = new javax.swing.JPanel();
|
||||
messagesLabel = new javax.swing.JLabel();
|
||||
@ -220,13 +233,15 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
callLogsDataLabel = new javax.swing.JLabel();
|
||||
attachmentsLabel = new javax.swing.JLabel();
|
||||
attachmentDataLabel = new javax.swing.JLabel();
|
||||
fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
|
||||
caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
|
||||
contanctsPanel = new javax.swing.JPanel();
|
||||
contactsLabel = new javax.swing.JLabel();
|
||||
contactsDataLabel = new javax.swing.JLabel();
|
||||
referencesLabel = new javax.swing.JLabel();
|
||||
referencesDataLabel = new javax.swing.JLabel();
|
||||
fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
|
||||
caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
|
||||
|
||||
setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
summaryPanel.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
@ -238,15 +253,27 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
gridBagConstraints.insets = new java.awt.Insets(15, 9, 0, 9);
|
||||
summaryPanel.add(accountLabel, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(accoutDescriptionLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.accoutDescriptionLabel.text")); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(accountCountry, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.accountCountry.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 1;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.insets = new java.awt.Insets(0, 9, 0, 9);
|
||||
summaryPanel.add(accountCountry, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(accoutDescriptionLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.accoutDescriptionLabel.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 2;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(15, 9, 15, 9);
|
||||
summaryPanel.add(accoutDescriptionLabel, gridBagConstraints);
|
||||
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
add(summaryPanel, gridBagConstraints);
|
||||
|
||||
countsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.countsPanel.border.title"))); // NOI18N
|
||||
countsPanel.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
@ -315,31 +342,12 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
gridBagConstraints.insets = new java.awt.Insets(0, 0, 9, 15);
|
||||
countsPanel.add(attachmentDataLabel, gridBagConstraints);
|
||||
|
||||
summaryPanel.add(countsPanel, new java.awt.GridBagConstraints());
|
||||
|
||||
setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 3;
|
||||
gridBagConstraints.gridy = 1;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
|
||||
add(fileReferencesPanel, gridBagConstraints);
|
||||
|
||||
caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 4;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
|
||||
add(caseReferencesPanel, gridBagConstraints);
|
||||
add(countsPanel, gridBagConstraints);
|
||||
|
||||
contanctsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.contanctsPanel.border.title"))); // NOI18N
|
||||
contanctsPanel.setLayout(new java.awt.GridBagLayout());
|
||||
@ -381,14 +389,37 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
|
||||
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 1;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.gridy = 2;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
add(contanctsPanel, gridBagConstraints);
|
||||
|
||||
fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 3;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
|
||||
add(fileReferencesPanel, gridBagConstraints);
|
||||
|
||||
caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 4;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
|
||||
add(caseReferencesPanel, gridBagConstraints);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel accountCountry;
|
||||
private javax.swing.JLabel accountLabel;
|
||||
private javax.swing.JLabel accoutDescriptionLabel;
|
||||
private javax.swing.JLabel attachmentDataLabel;
|
||||
|
@ -84,9 +84,12 @@ MediaViewImagePanel.zoomTextField.text=
|
||||
MediaViewImagePanel.rotationTextField.text=
|
||||
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
||||
HtmlPanel.showImagesToggleButton.text=Download Images
|
||||
MediaPlayerPanel.audioSlider.toolTipText=
|
||||
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
|
||||
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
|
||||
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
|
||||
MediaPlayerPanel.audioSlider.toolTipText=
|
||||
MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7
|
||||
MediaPlayerPanel.fastForwardButton.text=\u2bc8\u2bc8
|
||||
MediaPlayerPanel.playButton.text=\u25ba
|
||||
MediaPlayerPanel.infoLabel.text=No Errors
|
||||
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
|
||||
MediaPlayerPanel.VolumeIcon.text=Volume
|
||||
MediaPlayerPanel.playBackSpeedLabel.text=Speed:
|
||||
|
@ -23,6 +23,7 @@ GstVideoPanel.noOpenCase.errMsg=No open case available.
|
||||
Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.
|
||||
HtmlPanel_showImagesToggleButton_hide=Hide Images
|
||||
HtmlPanel_showImagesToggleButton_show=Download Images
|
||||
HtmlViewer_encoding_error=This file has unsupported encoding
|
||||
HtmlViewer_file_error=This file is missing or unreadable.
|
||||
MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
||||
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
|
||||
@ -154,12 +155,15 @@ MediaViewImagePanel.zoomTextField.text=
|
||||
MediaViewImagePanel.rotationTextField.text=
|
||||
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
||||
HtmlPanel.showImagesToggleButton.text=Download Images
|
||||
MediaPlayerPanel.audioSlider.toolTipText=
|
||||
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
|
||||
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
|
||||
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
|
||||
MediaPlayerPanel.audioSlider.toolTipText=
|
||||
MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7
|
||||
MediaPlayerPanel.fastForwardButton.text=\u2bc8\u2bc8
|
||||
MediaPlayerPanel.playButton.text=\u25ba
|
||||
MediaPlayerPanel.infoLabel.text=No Errors
|
||||
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
|
||||
MediaPlayerPanel.VolumeIcon.text=Volume
|
||||
MediaPlayerPanel.playBackSpeedLabel.text=Speed:
|
||||
# {0} - tableName
|
||||
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
|
||||
# {0} - tableName
|
||||
|
@ -20,9 +20,12 @@ package org.sleuthkit.autopsy.contentviewers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.tika.parser.txt.CharsetDetector;
|
||||
import org.apache.tika.parser.txt.CharsetMatch;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -59,17 +62,27 @@ final class HtmlViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
* @return The text content of the file.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"HtmlViewer_file_error=This file is missing or unreadable.",})
|
||||
"HtmlViewer_file_error=This file is missing or unreadable.",
|
||||
"HtmlViewer_encoding_error=This file has unsupported encoding"})
|
||||
private String getHtmlText(AbstractFile abstractFile) {
|
||||
try {
|
||||
int fileSize = (int) abstractFile.getSize();
|
||||
byte[] buffer = new byte[fileSize];
|
||||
abstractFile.read(buffer, 0, fileSize);
|
||||
return new String(buffer);
|
||||
CharsetMatch match = new CharsetDetector().setText(buffer).detect();
|
||||
if (match != null) {
|
||||
return new String(buffer, match.getName());
|
||||
} else {
|
||||
return new String(buffer);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Unable to read from file '%s' (id=%d).",
|
||||
abstractFile.getName(), abstractFile.getId()), ex);
|
||||
return String.format("<p>%s</p>", Bundle.HtmlViewer_file_error());
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Unsupported encoding for file '%s' (id=%d).",
|
||||
abstractFile.getName(), abstractFile.getId()), ex);
|
||||
return String.format("<p>%s</p>", Bundle.HtmlViewer_encoding_error());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Form version="1.8" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
@ -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">
|
||||
@ -41,7 +41,7 @@
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="259" max="32767" attributes="0"/>
|
||||
<EmptySpace min="0" pref="131" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
@ -51,47 +51,42 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="playButton" min="-2" pref="64" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="progressSlider" pref="680" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="infoLabel" max="32767" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="VolumeIcon" min="-2" pref="64" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
|
||||
<Component id="audioSlider" min="-2" pref="229" max="-2" attributes="0"/>
|
||||
<Component id="infoLabel" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Component id="buttonPanel" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="progressSlider" pref="623" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="progressLabel" max="32767" attributes="0"/>
|
||||
<Component id="playBackPanel" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="progressLabel" max="32767" attributes="0"/>
|
||||
<Component id="progressSlider" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="playButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="progressLabel" max="32767" attributes="0"/>
|
||||
<Component id="progressSlider" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Component id="audioSlider" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="VolumeIcon" alignment="3" min="-2" pref="23" max="-2" attributes="0"/>
|
||||
<Component id="infoLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="buttonPanel" max="32767" attributes="0"/>
|
||||
<Component id="playBackPanel" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="14" max="-2" attributes="0"/>
|
||||
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -111,26 +106,9 @@
|
||||
<Dimension value="[200, 21]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="infoLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||
<Color id="Default Cursor"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="playButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playButtonActionPerformed"/>
|
||||
</Events>
|
||||
<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>
|
||||
@ -139,31 +117,174 @@
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="VolumeIcon">
|
||||
<Container class="javax.swing.JPanel" name="buttonPanel">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="playButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playButtonActionPerformed"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="21" ipadY="0" insetsTop="5" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="fastForwardButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.fastForwardButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fastForwardButtonActionPerformed"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="2" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="rewindButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.rewindButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="rewindButtonActionPerformed"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="0" insetsBottom="1" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="VolumeIcon">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.VolumeIcon.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="horizontalTextPosition" type="int" value="2"/>
|
||||
</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="14" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSlider" name="audioSlider">
|
||||
<Properties>
|
||||
<Property name="majorTickSpacing" type="int" value="10"/>
|
||||
<Property name="maximum" type="int" value="50"/>
|
||||
<Property name="minorTickSpacing" type="int" value="5"/>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.audioSlider.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="value" type="int" value="25"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 21]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<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"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="infoLabel">
|
||||
<Properties>
|
||||
<Property name="horizontalAlignment" type="int" value="2"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.VolumeIcon.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSlider" name="audioSlider">
|
||||
<Properties>
|
||||
<Property name="majorTickSpacing" type="int" value="10"/>
|
||||
<Property name="maximum" type="int" value="50"/>
|
||||
<Property name="minorTickSpacing" type="int" value="5"/>
|
||||
<Property name="paintTicks" type="boolean" value="true"/>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.audioSlider.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="value" type="int" value="25"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 21]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 21]"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||
<Color id="Default Cursor"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="playBackPanel">
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="playBackSpeedLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="playBackSpeedComboBox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="playBackSpeedComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="playBackSpeedLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JComboBox" name="playBackSpeedComboBox">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="8">
|
||||
<StringItem index="0" value="0.25x"/>
|
||||
<StringItem index="1" value="0.50x"/>
|
||||
<StringItem index="2" value="0.75x"/>
|
||||
<StringItem index="3" value="1x"/>
|
||||
<StringItem index="4" value="1.25x"/>
|
||||
<StringItem index="5" value="1.50x"/>
|
||||
<StringItem index="6" value="1.75x"/>
|
||||
<StringItem index="7" value="2x"/>
|
||||
</StringArray>
|
||||
</Property>
|
||||
<Property name="selectedIndex" type="int" value="3"/>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[53, 23]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[53, 23]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[53, 23]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playBackSpeedComboBoxActionPerformed"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="playBackSpeedLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playBackSpeedLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
|
@ -19,16 +19,26 @@
|
||||
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;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
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;
|
||||
@ -37,7 +47,6 @@ import javax.swing.SwingWorker;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import org.freedesktop.gstreamer.Bus;
|
||||
import org.freedesktop.gstreamer.ClockTime;
|
||||
import org.freedesktop.gstreamer.Gst;
|
||||
import org.freedesktop.gstreamer.GstObject;
|
||||
import org.freedesktop.gstreamer.State;
|
||||
@ -52,8 +61,17 @@ 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;
|
||||
import org.freedesktop.gstreamer.event.SeekFlags;
|
||||
import org.freedesktop.gstreamer.event.SeekType;
|
||||
|
||||
/**
|
||||
* This is a video player that is part of the Media View layered pane. It uses
|
||||
@ -177,17 +195,27 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
private Bus.EOS endOfStreamListener;
|
||||
|
||||
//Update progress bar and time label during video playback
|
||||
private final Timer timer = new Timer(75, new VideoPanelUpdater());
|
||||
//Updating every 16 MS = 62.5 FPS.
|
||||
private final Timer timer = new Timer(16, new VideoPanelUpdater());
|
||||
private static final int PROGRESS_SLIDER_SIZE = 2000;
|
||||
private static final int SKIP_IN_SECONDS = 30;
|
||||
|
||||
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() {
|
||||
@ -202,11 +230,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
if (progressSlider.getValueIsAdjusting()) {
|
||||
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
|
||||
double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE;
|
||||
long newPos = (long) (relativePosition * duration);
|
||||
gstPlayBin.seek(newPos, TimeUnit.NANOSECONDS);
|
||||
long newStartTime = (long) (relativePosition * duration);
|
||||
double playBackRate = getPlayBackRate();
|
||||
gstPlayBin.seek(playBackRate,
|
||||
Format.TIME,
|
||||
//FLUSH - flushes the pipeline
|
||||
//ACCURATE - video will seek exactly to the position requested
|
||||
EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
|
||||
//Set the start position to newTime
|
||||
SeekType.SET, newStartTime,
|
||||
//Do nothing for the end position
|
||||
SeekType.NONE, -1);
|
||||
//Keep constantly updating the time label so users have a sense of
|
||||
//where the slider they are dragging is in relation to the video time
|
||||
updateTimeLabel(newPos, duration);
|
||||
updateTimeLabel(newStartTime, duration);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -220,10 +257,12 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
errorListener = new Bus.ERROR() {
|
||||
@Override
|
||||
public void errorMessage(GstObject go, int i, String string) {
|
||||
enableComponents(false);
|
||||
infoLabel.setText(String.format(
|
||||
"<html><font color='red'>%s</font></html>",
|
||||
MEDIA_PLAYER_ERROR_STRING));
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
enableComponents(false);
|
||||
infoLabel.setText(String.format(
|
||||
"<html><font color='red'>%s</font></html>",
|
||||
MEDIA_PLAYER_ERROR_STRING));
|
||||
});
|
||||
timer.stop();
|
||||
}
|
||||
};
|
||||
@ -231,9 +270,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
@Override
|
||||
public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
|
||||
if (State.PLAYING.equals(currentState)) {
|
||||
playButton.setText("||");
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
playButton.setText("||");
|
||||
});
|
||||
} else {
|
||||
playButton.setText("►");
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
playButton.setText("►");
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -241,7 +284,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
@Override
|
||||
public void endOfStream(GstObject go) {
|
||||
gstPlayBin.seek(ClockTime.ZERO);
|
||||
progressSlider.setValue(0);
|
||||
/**
|
||||
* Keep the video from automatically playing
|
||||
*/
|
||||
@ -268,7 +310,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
try {
|
||||
//Pushing off initialization to the background
|
||||
extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
|
||||
extractMediaWorker.execute();
|
||||
extractMediaWorker.execute();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
|
||||
infoLabel.setText(String.format("<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
|
||||
@ -301,8 +343,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
if (gstPlayBin != null) {
|
||||
gstPlayBin.stop();
|
||||
gstPlayBin.getBus().disconnect(endOfStreamListener);
|
||||
gstPlayBin.getBus().disconnect(endOfStreamListener);
|
||||
gstPlayBin.getBus().disconnect(endOfStreamListener);
|
||||
gstPlayBin.getBus().disconnect(stateChangeListener);
|
||||
gstPlayBin.getBus().disconnect(errorListener);
|
||||
gstPlayBin.dispose();
|
||||
fxAppSink.clear();
|
||||
gstPlayBin = null;
|
||||
@ -317,6 +359,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
progressSlider.setEnabled(isEnabled);
|
||||
videoPanel.setEnabled(isEnabled);
|
||||
audioSlider.setEnabled(isEnabled);
|
||||
rewindButton.setEnabled(isEnabled);
|
||||
fastForwardButton.setEnabled(isEnabled);
|
||||
playBackSpeedComboBox.setEnabled(isEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -373,7 +418,18 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
* @param total
|
||||
*/
|
||||
private void updateTimeLabel(long start, long total) {
|
||||
progressLabel.setText(formatTime(start, false) + "/" + formatTime(total, true));
|
||||
progressLabel.setText(formatTime(start) + "/" + formatTime(total));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the current selected playback rate from the speed combo box.
|
||||
*
|
||||
* @return The selected rate.
|
||||
*/
|
||||
private double getPlayBackRate() {
|
||||
int selectIndex = playBackSpeedComboBox.getSelectedIndex();
|
||||
String selectText = playBackSpeedComboBox.getItemAt(selectIndex);
|
||||
return Double.valueOf(selectText.substring(0, selectText.length() - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -383,24 +439,18 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
"MediaPlayerPanel.unknownTime=Unknown",
|
||||
"MediaPlayerPanel.timeFormat=%02d:%02d:%02d"
|
||||
})
|
||||
private String formatTime(long ns, boolean ceiling) {
|
||||
private String formatTime(long ns) {
|
||||
if (ns == -1) {
|
||||
return Bundle.MediaPlayerPanel_unknownTime();
|
||||
}
|
||||
|
||||
double millis = ns / 1000000.0;
|
||||
double seconds;
|
||||
if (ceiling) {
|
||||
seconds = Math.ceil(millis / 1000);
|
||||
} else {
|
||||
seconds = millis / 1000;
|
||||
}
|
||||
double hours = seconds / 3600;
|
||||
seconds -= (int) hours * 3600;
|
||||
double minutes = seconds / 60;
|
||||
seconds -= (int) minutes * 60;
|
||||
long seconds = TimeUnit.SECONDS.convert(ns, TimeUnit.NANOSECONDS);
|
||||
long hours = TimeUnit.HOURS.convert(seconds, TimeUnit.SECONDS);
|
||||
seconds -= TimeUnit.SECONDS.convert(hours, TimeUnit.HOURS);
|
||||
long minutes = TimeUnit.MINUTES.convert(seconds, TimeUnit.SECONDS);
|
||||
seconds -= TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES);
|
||||
|
||||
return String.format(Bundle.MediaPlayerPanel_timeFormat(), (int) hours, (int) minutes, (int) seconds);
|
||||
return String.format(Bundle.MediaPlayerPanel_timeFormat(), hours, minutes, seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -422,7 +472,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
protected Void doInBackground() throws Exception {
|
||||
if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) {
|
||||
progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
|
||||
});
|
||||
|
||||
progress.start(100);
|
||||
try {
|
||||
Files.createParentDirs(tempFile);
|
||||
@ -443,8 +497,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
protected void done() {
|
||||
try {
|
||||
super.get();
|
||||
|
||||
if(this.isCancelled()) {
|
||||
|
||||
if (this.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -460,8 +514,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
playBinBus.connect(endOfStreamListener);
|
||||
playBinBus.connect(stateChangeListener);
|
||||
playBinBus.connect(errorListener);
|
||||
|
||||
if(this.isCancelled()) {
|
||||
|
||||
if (this.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -471,14 +525,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
videoPanel.add(fxPanel);
|
||||
fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel);
|
||||
gstPlayBin.setVideoSink(fxAppSink);
|
||||
|
||||
if(this.isCancelled()) {
|
||||
|
||||
if (this.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0);
|
||||
gstPlayBin.pause();
|
||||
|
||||
|
||||
timer.start();
|
||||
enableComponents(true);
|
||||
} catch (CancellationException ex) {
|
||||
@ -499,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);
|
||||
/**
|
||||
@ -506,12 +561,218 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
* pipeline. We start this updater when data-flow has just been
|
||||
* initiated so buffering may still be in progress.
|
||||
*/
|
||||
if (duration != -1) {
|
||||
if (duration >= 0 && position >= 0) {
|
||||
double relativePosition = (double) position / duration;
|
||||
progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE));
|
||||
}
|
||||
|
||||
updateTimeLabel(position, duration);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -524,15 +785,22 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
java.awt.GridBagConstraints gridBagConstraints;
|
||||
|
||||
videoPanel = new javax.swing.JPanel();
|
||||
controlPanel = new javax.swing.JPanel();
|
||||
progressSlider = new javax.swing.JSlider();
|
||||
infoLabel = new javax.swing.JLabel();
|
||||
playButton = new javax.swing.JButton();
|
||||
progressLabel = new javax.swing.JLabel();
|
||||
buttonPanel = new javax.swing.JPanel();
|
||||
playButton = new javax.swing.JButton();
|
||||
fastForwardButton = new javax.swing.JButton();
|
||||
rewindButton = new javax.swing.JButton();
|
||||
VolumeIcon = new javax.swing.JLabel();
|
||||
audioSlider = new javax.swing.JSlider();
|
||||
infoLabel = new javax.swing.JLabel();
|
||||
playBackPanel = new javax.swing.JPanel();
|
||||
playBackSpeedComboBox = new javax.swing.JComboBox<>();
|
||||
playBackSpeedLabel = new javax.swing.JLabel();
|
||||
|
||||
javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
|
||||
videoPanel.setLayout(videoPanelLayout);
|
||||
@ -542,7 +810,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
);
|
||||
videoPanelLayout.setVerticalGroup(
|
||||
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 259, Short.MAX_VALUE)
|
||||
.addGap(0, 131, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
progressSlider.setValue(0);
|
||||
@ -550,9 +818,11 @@ 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(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N
|
||||
infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
|
||||
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
|
||||
|
||||
buttonPanel.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N
|
||||
playButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
@ -560,64 +830,145 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
playButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 1;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.ipadx = 21;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
|
||||
buttonPanel.add(playButton, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N
|
||||
fastForwardButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
fastForwardButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 2;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
|
||||
buttonPanel.add(fastForwardButton, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N
|
||||
rewindButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
rewindButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.insets = new java.awt.Insets(5, 0, 1, 0);
|
||||
buttonPanel.add(rewindButton, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N
|
||||
VolumeIcon.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 3;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.ipadx = 8;
|
||||
gridBagConstraints.ipady = 7;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0);
|
||||
buttonPanel.add(VolumeIcon, gridBagConstraints);
|
||||
|
||||
audioSlider.setMajorTickSpacing(10);
|
||||
audioSlider.setMaximum(50);
|
||||
audioSlider.setMinorTickSpacing(5);
|
||||
audioSlider.setPaintTicks(true);
|
||||
audioSlider.setToolTipText(org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.audioSlider.toolTipText")); // NOI18N
|
||||
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;
|
||||
gridBagConstraints.ipadx = -116;
|
||||
gridBagConstraints.ipady = 7;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.insets = new java.awt.Insets(3, 1, 0, 10);
|
||||
buttonPanel.add(audioSlider, gridBagConstraints);
|
||||
|
||||
infoLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N
|
||||
infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
|
||||
|
||||
playBackSpeedComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "0.25x", "0.50x", "0.75x", "1x", "1.25x", "1.50x", "1.75x", "2x" }));
|
||||
playBackSpeedComboBox.setSelectedIndex(3);
|
||||
playBackSpeedComboBox.setMaximumSize(new java.awt.Dimension(53, 23));
|
||||
playBackSpeedComboBox.setMinimumSize(new java.awt.Dimension(53, 23));
|
||||
playBackSpeedComboBox.setPreferredSize(new java.awt.Dimension(53, 23));
|
||||
playBackSpeedComboBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
playBackSpeedComboBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(playBackSpeedLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playBackSpeedLabel.text")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout playBackPanelLayout = new javax.swing.GroupLayout(playBackPanel);
|
||||
playBackPanel.setLayout(playBackPanelLayout);
|
||||
playBackPanelLayout.setHorizontalGroup(
|
||||
playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(playBackPanelLayout.createSequentialGroup()
|
||||
.addComponent(playBackSpeedLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(13, 13, 13))
|
||||
);
|
||||
playBackPanelLayout.setVerticalGroup(
|
||||
playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(playBackPanelLayout.createSequentialGroup()
|
||||
.addGap(6, 6, 6)
|
||||
.addGroup(playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(playBackSpeedLabel))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
|
||||
controlPanel.setLayout(controlPanelLayout);
|
||||
controlPanelLayout.setHorizontalGroup(
|
||||
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addComponent(playButton, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 680, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(progressLabel))
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(2, 2, 2)
|
||||
.addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, 229, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addContainerGap())
|
||||
.addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 623, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
|
||||
.addGap(10, 10, 10)))
|
||||
.addGap(0, 0, 0))
|
||||
);
|
||||
controlPanelLayout.setVerticalGroup(
|
||||
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(playButton))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(infoLabel)))
|
||||
.addGap(13, 13, 13))
|
||||
.addGap(0, 0, 0)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGap(5, 5, 5)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
|
||||
.addGap(14, 14, 14)
|
||||
.addComponent(infoLabel))
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
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)
|
||||
@ -628,23 +979,96 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed
|
||||
long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
||||
//Skip 30 seconds.
|
||||
long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
|
||||
//Ensure new video position is within bounds
|
||||
long newTime = Math.max(currentTime - rewindDelta, 0);
|
||||
double playBackRate = getPlayBackRate();
|
||||
gstPlayBin.seek(playBackRate,
|
||||
Format.TIME,
|
||||
//FLUSH - flushes the pipeline
|
||||
//ACCURATE - video will seek exactly to the position requested
|
||||
EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
|
||||
//Set the start position to newTime
|
||||
SeekType.SET, newTime,
|
||||
//Do nothing for the end position
|
||||
SeekType.NONE, -1);
|
||||
}//GEN-LAST:event_rewindButtonActionPerformed
|
||||
|
||||
private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed
|
||||
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
|
||||
long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
||||
//Skip 30 seconds.
|
||||
long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
//Ignore fast forward requests if there are less than 30 seconds left.
|
||||
if (currentTime + fastForwardDelta >= duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
long newTime = currentTime + fastForwardDelta;
|
||||
double playBackRate = getPlayBackRate();
|
||||
gstPlayBin.seek(playBackRate,
|
||||
Format.TIME,
|
||||
//FLUSH - flushes the pipeline
|
||||
//ACCURATE - video will seek exactly to the position requested
|
||||
EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
|
||||
//Set the start position to newTime
|
||||
SeekType.SET, newTime,
|
||||
//Do nothing for the end position
|
||||
SeekType.NONE, -1);
|
||||
}//GEN-LAST:event_fastForwardButtonActionPerformed
|
||||
|
||||
private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed
|
||||
if (gstPlayBin.isPlaying()) {
|
||||
gstPlayBin.pause();
|
||||
} else {
|
||||
double playBackRate = getPlayBackRate();
|
||||
long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
||||
//Set playback rate before play.
|
||||
gstPlayBin.seek(playBackRate,
|
||||
Format.TIME,
|
||||
//FLUSH - flushes the pipeline
|
||||
//ACCURATE - video will seek exactly to the position requested
|
||||
EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
|
||||
//Set the start position to newTime
|
||||
SeekType.SET, currentTime,
|
||||
//Do nothing for the end position
|
||||
SeekType.NONE, -1);
|
||||
gstPlayBin.play();
|
||||
}
|
||||
}//GEN-LAST:event_playButtonActionPerformed
|
||||
|
||||
private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playBackSpeedComboBoxActionPerformed
|
||||
double playBackRate = getPlayBackRate();
|
||||
long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
|
||||
gstPlayBin.seek(playBackRate,
|
||||
Format.TIME,
|
||||
//FLUSH - flushes the pipeline
|
||||
//ACCURATE - video will seek exactly to the position requested
|
||||
EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
|
||||
//Set the position to the currentTime, we are only adjusting the
|
||||
//playback rate.
|
||||
SeekType.SET, currentTime,
|
||||
SeekType.NONE, 0);
|
||||
}//GEN-LAST:event_playBackSpeedComboBoxActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel VolumeIcon;
|
||||
private javax.swing.JSlider audioSlider;
|
||||
private javax.swing.JPanel buttonPanel;
|
||||
private javax.swing.JPanel controlPanel;
|
||||
private javax.swing.JButton fastForwardButton;
|
||||
private javax.swing.JLabel infoLabel;
|
||||
private javax.swing.JPanel playBackPanel;
|
||||
private javax.swing.JComboBox<String> playBackSpeedComboBox;
|
||||
private javax.swing.JLabel playBackSpeedLabel;
|
||||
private javax.swing.JButton playButton;
|
||||
private javax.swing.JLabel progressLabel;
|
||||
private javax.swing.JSlider progressSlider;
|
||||
private javax.swing.JButton rewindButton;
|
||||
private javax.swing.JPanel videoPanel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ OpenIDE-Module-Short-Description=Autopsy Core Module
|
||||
org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml
|
||||
Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy Update Center
|
||||
Installer.errorInitJavafx.msg=Error initializing JavaFX.
|
||||
Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have the right JRE installed (Oracle JRE > 1.7.10).
|
||||
Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have JavaFX installed (OpenJFX 8).
|
||||
ServicesMonitor.failedService.notify.title=Service Is Down
|
||||
ServicesMonitor.failedService.notify.msg=Connection to {0} is down
|
||||
ServicesMonitor.restoredService.notify.title=Service Is Up
|
||||
|
@ -15,7 +15,7 @@ OpenIDE-Module-Short-Description=Autopsy Core Module
|
||||
org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml
|
||||
Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy Update Center
|
||||
Installer.errorInitJavafx.msg=Error initializing JavaFX.
|
||||
Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have the right JRE installed (Oracle JRE > 1.7.10).
|
||||
Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have JavaFX installed (OpenJFX 8).
|
||||
ServicesMonitor.failedService.notify.title=Service Is Down
|
||||
ServicesMonitor.failedService.notify.msg=Connection to {0} is down
|
||||
ServicesMonitor.restoredService.notify.title=Service Is Up
|
||||
|
@ -1,31 +1,30 @@
|
||||
OpenIDE-Module-Display-Category=\u30A4\u30F3\u30D5\u30E9\u30B9\u30C8\u30E9\u30AF\u30C1\u30E3\u30FC
|
||||
OpenIDE-Module-Display-Category=\u30a4\u30f3\u30d5\u30e9\u30b9\u30c8\u30e9\u30af\u30c1\u30e3\u30fc
|
||||
OpenIDE-Module-Long-Description=\
|
||||
\u3053\u308C\u304CAutopsy\u306E\u30B3\u30A2\u30E2\u30B8\u30E5\u30FC\u30EB\u3067\u3059\u3002\n\n\
|
||||
\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u307F\u3067\u5B9F\u884C\u3059\u308B\u306E\u306B\u5FC5\u8981\u306A\u4E3B\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059\uFF1ARCP\u30D7\u30E9\u30C3\u30C8\u30D5\u30A9\u30FC\u30E0\u3001\u30A6\u30A3\u30F3\u30C9\u30A6\u30A4\u30F3\u30B0GUI\u3001Sleuth Kit\u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u3001\u30C7\u30FC\u30BF\u30E2\u30C7\u30EB\uFF0F\u30B9\u30C8\u30EC\u30FC\u30B8\u3001\u30A8\u30AF\u30B9\u30D7\u30ED\u30FC\u30E9\u3001\u7D50\u679C\u30D3\u30E5\u30FC\u30A2\u3001\u30B3\u30F3\u30C6\u30F3\u30C4\u30D3\u30E5\u30FC\u30A2\u3001\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u7528\u30D5\u30EC\u30FC\u30E0\u30EF\u30FC\u30AF\u3001\u30EC\u30DD\u30FC\u30C8\u751F\u6210\u3001\u30D5\u30A1\u30A4\u30EB\u691C\u7D22\u7B49\u306E\u4E3B\u8981\u30C4\u30FC\u30EB\u3002\n\n\
|
||||
\u30E2\u30B8\u30E5\u30FC\u30EB\u5185\u306E\u30D5\u30EC\u30FC\u30E0\u30EF\u30FC\u30AF\u306B\u306F\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u3001\u30D3\u30E5\u30FC\u30A2\u3001\u30EC\u30DD\u30FC\u30C8\u751F\u6210\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u958B\u767A\u7528\u306EAPI\u304C\u542B\u307E\u308C\u307E\u3059\u3002\
|
||||
\u30E2\u30B8\u30E5\u30FC\u30EB\u306FAutopsy\u30D7\u30E9\u30B0\u30A4\u30F3\u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u3092\u4F7F\u7528\u3057\u3001\u30D7\u30E9\u30B0\u30A4\u30F3\u3068\u3057\u3066\u5B9F\u88C5\u3067\u304D\u307E\u3059\u3002\n\
|
||||
\u3053\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u306F\u30A2\u30F3\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3066\u3044\u306A\u3051\u308C\u3070\u3001Autopsy\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\n\n\
|
||||
\u8A73\u7D30\u306F\u3053\u3061\u3089\u3067\u3054\u78BA\u8A8D\u304F\u3060\u3055\u3044\u3002http\://www.sleuthkit.org/autopsy/
|
||||
OpenIDE-Module-Name=Autopsy-\u30B3\u30A2
|
||||
OpenIDE-Module-Short-Description=Autopsy\u30B3\u30A2\u30E2\u30B8\u30E5\u30FC\u30EB
|
||||
\u3053\u308c\u304cAutopsy\u306e\u30b3\u30a2\u30e2\u30b8\u30e5\u30fc\u30eb\u3067\u3059\u3002\n\n\
|
||||
\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u307f\u3067\u5b9f\u884c\u3059\u308b\u306e\u306b\u5fc5\u8981\u306a\u4e3b\u8981\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059\uff1aRCP\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u3001\u30a6\u30a3\u30f3\u30c9\u30a6\u30a4\u30f3\u30b0GUI\u3001Sleuth Kit\u30d0\u30a4\u30f3\u30c7\u30a3\u30f3\u30b0\u3001\u30c7\u30fc\u30bf\u30e2\u30c7\u30eb\uff0f\u30b9\u30c8\u30ec\u30fc\u30b8\u3001\u30a8\u30af\u30b9\u30d7\u30ed\u30fc\u30e9\u3001\u7d50\u679c\u30d3\u30e5\u30fc\u30a2\u3001\u30b3\u30f3\u30c6\u30f3\u30c4\u30d3\u30e5\u30fc\u30a2\u3001\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u7528\u30d5\u30ec\u30fc\u30e0\u30ef\u30fc\u30af\u3001\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u3001\u30d5\u30a1\u30a4\u30eb\u691c\u7d22\u7b49\u306e\u4e3b\u8981\u30c4\u30fc\u30eb\u3002\n\n\
|
||||
\u30e2\u30b8\u30e5\u30fc\u30eb\u5185\u306e\u30d5\u30ec\u30fc\u30e0\u30ef\u30fc\u30af\u306b\u306f\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3001\u30d3\u30e5\u30fc\u30a2\u3001\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u958b\u767a\u7528\u306eAPI\u304c\u542b\u307e\u308c\u307e\u3059\u3002\
|
||||
\u30e2\u30b8\u30e5\u30fc\u30eb\u306fAutopsy\u30d7\u30e9\u30b0\u30a4\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30e9\u30fc\u3092\u4f7f\u7528\u3057\u3001\u30d7\u30e9\u30b0\u30a4\u30f3\u3068\u3057\u3066\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002\n\
|
||||
\u3053\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u306f\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u3066\u3044\u306a\u3051\u308c\u3070\u3001Autopsy\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002\n\n\
|
||||
\u8a73\u7d30\u306f\u3053\u3061\u3089\u3067\u3054\u78ba\u8a8d\u304f\u3060\u3055\u3044\u3002http\://www.sleuthkit.org/autopsy/
|
||||
OpenIDE-Module-Name=Autopsy-\u30b3\u30a2
|
||||
OpenIDE-Module-Short-Description=Autopsy\u30b3\u30a2\u30e2\u30b8\u30e5\u30fc\u30eb
|
||||
org_sleuthkit_autopsy_core_update_center=http\://sleuthkit.org/autopsy/updates_ja.xml
|
||||
Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u30BB\u30F3\u30BF\u30FC
|
||||
Installer.errorInitJavafx.msg=JavaFX\u521D\u671F\u5316\u30A8\u30E9\u30FC
|
||||
Installer.errorInitJavafx.details=\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u6B63\u3057\u3044JRE\u304C\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3066\u3044\u308B\u304B\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002\uFF08Oracle JRE > 1.7.10\uFF09
|
||||
ServicesMonitor.failedService.notify.title=\u30B5\u30FC\u30D3\u30B9\u304C\u505C\u6B62\u3057\u3066\u3044\u307E\u3059
|
||||
ServicesMonitor.failedService.notify.msg={0}\u3078\u306E\u63A5\u7D9A\u304C\u30C0\u30A6\u30F3\u3057\u3066\u3044\u307E\u3059
|
||||
ServicesMonitor.restoredService.notify.title=\u30B5\u30FC\u30D3\u30B9\u304C\u7A3C\u50CD\u4E2D\u3067\u3059
|
||||
ServicesMonitor.restoredService.notify.msg={0}\u3078\u306E\u63A5\u7D9A\u304C\u5229\u7528\u3067\u304D\u307E\u3059
|
||||
ServicesMonitor.statusChange.notify.title=\u30B5\u30FC\u30D3\u30B9\u30B9\u30C6\u30FC\u30BF\u30B9\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8
|
||||
ServicesMonitor.statusChange.notify.msg={0}\u306E\u30B9\u30C6\u30FC\u30BF\u30B9\u306F{1}
|
||||
ServicesMonitor.nullServiceName.excepton.txt=\u30EA\u30AF\u30A8\u30B9\u30C8\u3055\u308C\u305F\u30B5\u30FC\u30D3\u30B9\u306F\u30CC\u30EB\u3067\u3059
|
||||
ServicesMonitor.unknownServiceName.excepton.txt=\u30EA\u30AF\u30A8\u30B9\u30C8\u3055\u308C\u305F\u30B5\u30FC\u30D3\u30B9{0}\u306F\u4E0D\u660E\u3067\u3059
|
||||
TextConverter.convert.exception.txt=\u30C6\u30AD\u30B9\u30C8{0}\u3092hex\u30C6\u30AD\u30B9\u30C8\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F
|
||||
TextConverter.convertFromHex.exception.txt=hex\u30C6\u30AD\u30B9\u30C8\u3092\u30C6\u30AD\u30B9\u30C8\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F
|
||||
ServicesMonitor.KeywordSearchNull=\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u30B5\u30FC\u30D3\u30B9\u3092\u898B\u3064\u3051\u308C\u307E\u305B\u3093\u3067\u3057\u305F
|
||||
ServicesMonitor.InvalidPortNumber=\u7121\u52B9\u306A\u30DD\u30FC\u30C8\u756A\u53F7
|
||||
ServicesMonitor.remoteCaseDatabase.displayName.text=\u8907\u6570\u306E\u30E6\u30FC\u30B6\u30FC\u306E\u30B1\u30FC\u30B9\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u30B5\u30FC\u30D3\u30B9
|
||||
ServicesMonitor.remoteKeywordSearch.displayName.text=\u8907\u6570\u306E\u30E6\u30FC\u30B6\u30FC\u306E\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u30B5\u30FC\u30D3\u30B9
|
||||
ServicesMonitor.messaging.displayName.text=\u30E1\u30C3\u30BB\u30FC\u30B8\u30B5\u30FC\u30D3\u30B9
|
||||
ServicesMonitor.databaseConnectionInfo.error.msg=\u30B1\u30FC\u30B9\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u63A5\u7D9A\u60C5\u5831\u3092\u5165\u624B\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F
|
||||
ServicesMonitor.messagingService.connErr.text=\u30E1\u30C3\u30BB\u30FC\u30B8\u30B5\u30FC\u30D3\u30B9\u306E\u63A5\u7D9A\u60C5\u5831\u3092\u5165\u624B\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F
|
||||
Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30bb\u30f3\u30bf\u30fc
|
||||
Installer.errorInitJavafx.msg=JavaFX\u521d\u671f\u5316\u30a8\u30e9\u30fc
|
||||
ServicesMonitor.failedService.notify.title=\u30b5\u30fc\u30d3\u30b9\u304c\u505c\u6b62\u3057\u3066\u3044\u307e\u3059
|
||||
ServicesMonitor.failedService.notify.msg={0}\u3078\u306e\u63a5\u7d9a\u304c\u30c0\u30a6\u30f3\u3057\u3066\u3044\u307e\u3059
|
||||
ServicesMonitor.restoredService.notify.title=\u30b5\u30fc\u30d3\u30b9\u304c\u7a3c\u50cd\u4e2d\u3067\u3059
|
||||
ServicesMonitor.restoredService.notify.msg={0}\u3078\u306e\u63a5\u7d9a\u304c\u5229\u7528\u3067\u304d\u307e\u3059
|
||||
ServicesMonitor.statusChange.notify.title=\u30b5\u30fc\u30d3\u30b9\u30b9\u30c6\u30fc\u30bf\u30b9\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8
|
||||
ServicesMonitor.statusChange.notify.msg={0}\u306e\u30b9\u30c6\u30fc\u30bf\u30b9\u306f{1}
|
||||
ServicesMonitor.nullServiceName.excepton.txt=\u30ea\u30af\u30a8\u30b9\u30c8\u3055\u308c\u305f\u30b5\u30fc\u30d3\u30b9\u306f\u30cc\u30eb\u3067\u3059
|
||||
ServicesMonitor.unknownServiceName.excepton.txt=\u30ea\u30af\u30a8\u30b9\u30c8\u3055\u308c\u305f\u30b5\u30fc\u30d3\u30b9{0}\u306f\u4e0d\u660e\u3067\u3059
|
||||
TextConverter.convert.exception.txt=\u30c6\u30ad\u30b9\u30c8{0}\u3092hex\u30c6\u30ad\u30b9\u30c8\u306b\u5909\u63db\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
|
||||
TextConverter.convertFromHex.exception.txt=hex\u30c6\u30ad\u30b9\u30c8\u3092\u30c6\u30ad\u30b9\u30c8\u306b\u5909\u63db\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
|
||||
ServicesMonitor.KeywordSearchNull=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9\u3092\u898b\u3064\u3051\u308c\u307e\u305b\u3093\u3067\u3057\u305f
|
||||
ServicesMonitor.InvalidPortNumber=\u7121\u52b9\u306a\u30dd\u30fc\u30c8\u756a\u53f7
|
||||
ServicesMonitor.remoteCaseDatabase.displayName.text=\u8907\u6570\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30b1\u30fc\u30b9\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b5\u30fc\u30d3\u30b9
|
||||
ServicesMonitor.remoteKeywordSearch.displayName.text=\u8907\u6570\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9
|
||||
ServicesMonitor.messaging.displayName.text=\u30e1\u30c3\u30bb\u30fc\u30b8\u30b5\u30fc\u30d3\u30b9
|
||||
ServicesMonitor.databaseConnectionInfo.error.msg=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u624b\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
|
||||
ServicesMonitor.messagingService.connErr.text=\u30e1\u30c3\u30bb\u30fc\u30b8\u30b5\u30fc\u30d3\u30b9\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u624b\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
|
11
Core/src/org/sleuthkit/autopsy/core/geolocationWsmode.xml
Executable file
11
Core/src/org/sleuthkit/autopsy/core/geolocationWsmode.xml
Executable file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mode version="2.4">
|
||||
<name unique="geolocation"/>
|
||||
<kind type="editor"/>
|
||||
<state type="separated"/>
|
||||
<bounds x="76" y="68" width="500" height="500"/>
|
||||
<frame state="0"/>
|
||||
|
||||
<empty-behavior permanent="false"/>
|
||||
</mode>
|
||||
|
@ -448,6 +448,7 @@
|
||||
<file name="floatingLeftBottom.wsmode" url="floatingLeftBottomWsmode.xml"/>
|
||||
<file name="timeline.wsmode" url="timelineWsmode.xml"/>
|
||||
<file name="cvt.wsmode" url="cvtWsmode.xml"/>
|
||||
<file name="geolocation.wsmode" url="geolocationWsmode.xml"/>
|
||||
</folder>
|
||||
</folder>
|
||||
|
||||
|
@ -56,6 +56,10 @@ import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
import org.netbeans.swing.etable.ETable;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* Instances of this class display the BlackboardArtifacts associated with the
|
||||
@ -552,6 +556,16 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
||||
value = dateFormatter.format(new java.util.Date(epoch * 1000));
|
||||
}
|
||||
break;
|
||||
case JSON:
|
||||
// @TODO: 5726 - return a multilevel bulleted list instead of prettyprint JSON
|
||||
String jsonVal = attr.getValueString();
|
||||
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject json = parser.parse(jsonVal).getAsJsonObject();
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
value = gson.toJson(json);
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Attribute sources column.
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2018-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.
|
||||
@ -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.
|
||||
*
|
||||
* @param node Node passed into content viewer
|
||||
* @return highest priority content or null if there is no content
|
||||
* Gets a Content object from the Lookup of a display Node object,
|
||||
* preferring to return any Content object other than a BlackboardArtifact
|
||||
* object.
|
||||
*
|
||||
* This method was written with the needs of the hex and strings content
|
||||
* viewers in mind - the algorithm is exactly what those viewers require.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
else {
|
||||
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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012 Basis Technology Corp.
|
||||
* Copyright 2012-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.coreutils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
@ -35,43 +36,43 @@ import org.sleuthkit.autopsy.coreutils.LnkEnums.NetworkProviderType;
|
||||
*/
|
||||
public class JLNK {
|
||||
|
||||
private int header;
|
||||
private byte[] linkClassIdentifier;
|
||||
private List<LinkFlags> linkFlags;
|
||||
private List<FileAttributesFlags> fileAttributesFlags;
|
||||
private long crtime;
|
||||
private long atime;
|
||||
private long mtime;
|
||||
private int fileSize;
|
||||
private int iconIndex;
|
||||
private int showCommand;
|
||||
private short hotKey;
|
||||
private final int header;
|
||||
private final byte[] linkClassIdentifier;
|
||||
private final List<LinkFlags> linkFlags;
|
||||
private final List<FileAttributesFlags> fileAttributesFlags;
|
||||
private final long crtime;
|
||||
private final long atime;
|
||||
private final long mtime;
|
||||
private final int fileSize;
|
||||
private final int iconIndex;
|
||||
private final int showCommand;
|
||||
private final short hotKey;
|
||||
|
||||
private List<String> linkTargetIdList;
|
||||
private final List<String> linkTargetIdList;
|
||||
|
||||
private boolean hasUnicodeLocalBaseAndCommonSuffixOffset;
|
||||
private String localBasePath;
|
||||
private String commonPathSuffix;
|
||||
private String localBasePathUnicode;
|
||||
private String commonPathSuffixUnicode;
|
||||
private final boolean hasUnicodeLocalBaseAndCommonSuffixOffset;
|
||||
private final String localBasePath;
|
||||
private final String commonPathSuffix;
|
||||
private final String localBasePathUnicode;
|
||||
private final String commonPathSuffixUnicode;
|
||||
|
||||
private String name;
|
||||
private String relativePath;
|
||||
private String workingDir;
|
||||
private String arguments;
|
||||
private String iconLocation;
|
||||
private final String name;
|
||||
private final String relativePath;
|
||||
private final String workingDir;
|
||||
private final String arguments;
|
||||
private final String iconLocation;
|
||||
|
||||
private int driveSerialNumber;
|
||||
private DriveType driveType;
|
||||
private String volumeLabel;
|
||||
private final int driveSerialNumber;
|
||||
private final DriveType driveType;
|
||||
private final String volumeLabel;
|
||||
|
||||
private List<CommonNetworkRelativeLinkFlags> commonNetworkRelativeListFlags;
|
||||
private NetworkProviderType networkProviderType;
|
||||
private boolean unicodeNetAndDeviceName;
|
||||
private String netName;
|
||||
private String netNameUnicode;
|
||||
private String deviceName;
|
||||
private String deviceNameUnicode;
|
||||
private final List<CommonNetworkRelativeLinkFlags> commonNetworkRelativeListFlags;
|
||||
private final NetworkProviderType networkProviderType;
|
||||
private final boolean unicodeNetAndDeviceName;
|
||||
private final String netName;
|
||||
private final String netNameUnicode;
|
||||
private final String deviceName;
|
||||
private final String deviceNameUnicode;
|
||||
|
||||
public JLNK(int header, byte[] linkClassIdentifier, int linkFlags,
|
||||
int fileAttributesFlags, long crtime, long atime,
|
||||
@ -87,14 +88,14 @@ public class JLNK {
|
||||
String netName, String netNameUnicode, String deviceName,
|
||||
String deviceNameUnicode) {
|
||||
this.header = header;
|
||||
this.linkClassIdentifier = linkClassIdentifier;
|
||||
this.linkFlags = new ArrayList<LinkFlags>();
|
||||
this.linkClassIdentifier = linkClassIdentifier.clone();
|
||||
this.linkFlags = new ArrayList<>();
|
||||
for (LnkEnums.LinkFlags enumVal : LnkEnums.LinkFlags.values()) {
|
||||
if ((linkFlags & enumVal.getFlag()) == enumVal.getFlag()) {
|
||||
this.linkFlags.add(enumVal);
|
||||
}
|
||||
}
|
||||
this.fileAttributesFlags = new ArrayList<FileAttributesFlags>();
|
||||
this.fileAttributesFlags = new ArrayList<>();
|
||||
for (LnkEnums.FileAttributesFlags enumVal : LnkEnums.FileAttributesFlags.values()) {
|
||||
if ((fileAttributesFlags & enumVal.getFlag()) == enumVal.getFlag()) {
|
||||
this.fileAttributesFlags.add(enumVal);
|
||||
@ -121,7 +122,7 @@ public class JLNK {
|
||||
this.driveSerialNumber = driveSerialNumber;
|
||||
this.driveType = driveType;
|
||||
this.volumeLabel = volumeLabel;
|
||||
this.commonNetworkRelativeListFlags = new ArrayList<CommonNetworkRelativeLinkFlags>();
|
||||
this.commonNetworkRelativeListFlags = new ArrayList<>();
|
||||
for (LnkEnums.CommonNetworkRelativeLinkFlags enumVal : LnkEnums.CommonNetworkRelativeLinkFlags.values()) {
|
||||
if ((commonNetworkRelativeListFlags & enumVal.getFlag()) == enumVal.getFlag()) {
|
||||
this.commonNetworkRelativeListFlags.add(enumVal);
|
||||
@ -140,7 +141,7 @@ public class JLNK {
|
||||
}
|
||||
|
||||
public List<CommonNetworkRelativeLinkFlags> getCommonNetworkRelativeListFlags() {
|
||||
return commonNetworkRelativeListFlags;
|
||||
return Collections.unmodifiableList(commonNetworkRelativeListFlags);
|
||||
}
|
||||
|
||||
public String getCommonPathSuffix() {
|
||||
@ -176,7 +177,7 @@ public class JLNK {
|
||||
}
|
||||
|
||||
public List<FileAttributesFlags> getFileAttributesFlags() {
|
||||
return fileAttributesFlags;
|
||||
return Collections.unmodifiableList(fileAttributesFlags);
|
||||
}
|
||||
|
||||
public int getFileSize() {
|
||||
@ -196,7 +197,7 @@ public class JLNK {
|
||||
}
|
||||
|
||||
public List<String> getLinkTargetIdList() {
|
||||
return linkTargetIdList;
|
||||
return Collections.unmodifiableList(linkTargetIdList);
|
||||
}
|
||||
|
||||
public int getIconIndex() {
|
||||
@ -208,11 +209,11 @@ public class JLNK {
|
||||
}
|
||||
|
||||
public byte[] getLinkClassIdentifier() {
|
||||
return linkClassIdentifier;
|
||||
return linkClassIdentifier.clone();
|
||||
}
|
||||
|
||||
public List<LinkFlags> getLinkFlags() {
|
||||
return linkFlags;
|
||||
return Collections.unmodifiableList(linkFlags);
|
||||
}
|
||||
|
||||
public String getLocalBasePath() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012 Basis Technology Corp.
|
||||
* Copyright 2012-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -31,6 +31,10 @@ class LnkEnums {
|
||||
private static final byte[] IEFRAME = new byte[]{(byte) 0x80, 0x53, 0x1c, (byte) 0x87, (byte) 0xa0,
|
||||
0x42, 0x69, 0x10, (byte) 0xa2, (byte) 0xea, 0x08, 0x00, 0x2b, 0x30, 0x30, (byte) 0x9d};
|
||||
|
||||
private LnkEnums() {
|
||||
//private constructor for utility class
|
||||
}
|
||||
|
||||
public enum CommonCLSIDS {
|
||||
|
||||
CDrivesFolder(CDRIVES),
|
||||
@ -38,20 +42,24 @@ class LnkEnums {
|
||||
IEFrameDLL(IEFRAME),
|
||||
Unknown(new byte[16]);
|
||||
|
||||
private byte[] flag;
|
||||
private final byte[] flag;
|
||||
|
||||
private CommonCLSIDS(byte[] flag) {
|
||||
this.flag = flag;
|
||||
this.flag = flag.clone();
|
||||
}
|
||||
|
||||
static CommonCLSIDS valueOf(byte[] type) {
|
||||
for (CommonCLSIDS value : CommonCLSIDS.values()) {
|
||||
if (java.util.Arrays.equals(value.flag, type)) {
|
||||
if (java.util.Arrays.equals(value.getFlag(), type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
byte[] getFlag() {
|
||||
return flag.clone();
|
||||
}
|
||||
}
|
||||
|
||||
public enum LinkFlags {
|
||||
@ -84,7 +92,7 @@ class LnkEnums {
|
||||
PreferEnvironmentPath(0x02000000),
|
||||
KeepLocalIDListForUNCTarget(0x04000000);
|
||||
|
||||
private int flag;
|
||||
private final int flag;
|
||||
|
||||
private LinkFlags(int flag) {
|
||||
this.flag = flag;
|
||||
@ -105,7 +113,7 @@ class LnkEnums {
|
||||
DRIVE_CDROM(0x00000005),
|
||||
DRIVE_RAMDISK(0x00000006);
|
||||
|
||||
private int flag;
|
||||
private final int flag;
|
||||
|
||||
private DriveType(int flag) {
|
||||
this.flag = flag;
|
||||
@ -117,7 +125,7 @@ class LnkEnums {
|
||||
|
||||
static DriveType valueOf(int type) {
|
||||
for (DriveType value : DriveType.values()) {
|
||||
if (value.flag == type) {
|
||||
if (value.getFlag() == type) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -143,7 +151,7 @@ class LnkEnums {
|
||||
NOT_CONTENT_INDEXED(0x00002000),
|
||||
ENCRYPTED(0x00004000);
|
||||
|
||||
private int flag;
|
||||
private final int flag;
|
||||
|
||||
private FileAttributesFlags(int flag) {
|
||||
this.flag = flag;
|
||||
@ -159,7 +167,7 @@ class LnkEnums {
|
||||
VolumeIDAndLocalBasePath(0x00000001),
|
||||
CommonNetworkRelativeLinkAndPathSuffix(0x00000002);
|
||||
|
||||
private int flag;
|
||||
private final int flag;
|
||||
|
||||
private LinkInfoFlags(int flag) {
|
||||
this.flag = flag;
|
||||
@ -175,7 +183,7 @@ class LnkEnums {
|
||||
ValidDevice(0x00000001),
|
||||
ValidNetType(0x00000002);
|
||||
|
||||
private int flag;
|
||||
private final int flag;
|
||||
|
||||
private CommonNetworkRelativeLinkFlags(int flag) {
|
||||
this.flag = flag;
|
||||
@ -231,7 +239,7 @@ class LnkEnums {
|
||||
WNNC_NET_GOOGLE(0x00430000),
|
||||
WNNC_NET_UNKNOWN(0x00000000);
|
||||
|
||||
private int flag;
|
||||
private final int flag;
|
||||
|
||||
private NetworkProviderType(int flag) {
|
||||
this.flag = flag;
|
||||
@ -239,7 +247,7 @@ class LnkEnums {
|
||||
|
||||
static NetworkProviderType valueOf(int type) {
|
||||
for (NetworkProviderType value : NetworkProviderType.values()) {
|
||||
if (value.flag == type) {
|
||||
if (value.getFlag() == type) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -250,4 +258,5 @@ class LnkEnums {
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
109
Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java
Normal file
109
Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.coreutils;
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class to format and get information from a phone number
|
||||
*/
|
||||
public final class PhoneNumUtil {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PhoneNumUtil.class.getName());
|
||||
|
||||
private PhoneNumUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the country code from a phone number
|
||||
*
|
||||
* @param phoneNumber
|
||||
* @return country code if can determine otherwise it will return ""
|
||||
*/
|
||||
public static String getCountryCode(String phoneNumber) {
|
||||
String regionCode = null;
|
||||
try {
|
||||
PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
|
||||
Phonenumber.PhoneNumber phoneNum = phoneNumberUtil.parse(phoneNumber, "");
|
||||
regionCode = phoneNumberUtil.getRegionCodeForNumber(phoneNum);
|
||||
|
||||
if (regionCode == null) {
|
||||
return "";
|
||||
} else {
|
||||
return regionCode;
|
||||
}
|
||||
} catch (NumberParseException ex) {
|
||||
logger.log(Level.WARNING, "Error getting country code, for phone number: {0}", phoneNumber);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a phone number to the E164 format
|
||||
*
|
||||
* @param phoneNumber
|
||||
*
|
||||
* @return formated phone number if successful or original phone number if
|
||||
* unsuccessful
|
||||
*/
|
||||
public static String convertToE164(String phoneNumber) {
|
||||
PhoneNumberUtil phone_util = PhoneNumberUtil.getInstance();
|
||||
try {
|
||||
Phonenumber.PhoneNumber phoneProto = phone_util.parse(phoneNumber, getCountryCode(phoneNumber));
|
||||
if (phone_util.isValidNumber(phoneProto)) {
|
||||
return phone_util.format(phoneProto, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
} else {
|
||||
logger.log(Level.WARNING, "Invalid phone number: {0}", phoneNumber);
|
||||
return phoneNumber;
|
||||
}
|
||||
} catch (NumberParseException e) {
|
||||
logger.log(Level.WARNING, "Error parsing phone number: {0}", phoneNumber);
|
||||
return phoneNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a phone number to the International format
|
||||
*
|
||||
* @param phoneNumber
|
||||
*
|
||||
* @return formated phone number if successful or original phone number if
|
||||
* unsuccessful
|
||||
*/
|
||||
public static String convertToInternational(String phoneNumber) {
|
||||
PhoneNumberUtil phone_util = PhoneNumberUtil.getInstance();
|
||||
try {
|
||||
Phonenumber.PhoneNumber phoneProto = phone_util.parse(phoneNumber, getCountryCode(phoneNumber));
|
||||
if (phone_util.isValidNumber(phoneProto)) {
|
||||
return phone_util.format(phoneProto, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
|
||||
} else {
|
||||
logger.log(Level.WARNING, "Invalid phone number: {0}", phoneNumber);
|
||||
return phoneNumber;
|
||||
}
|
||||
} catch (NumberParseException e) {
|
||||
logger.log(Level.WARNING, "Error parsing phone number: {0}", phoneNumber);
|
||||
return phoneNumber;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -122,6 +122,7 @@ public class ArtifactStringContent implements StringContent {
|
||||
case LONG:
|
||||
case DOUBLE:
|
||||
case BYTE:
|
||||
case JSON:
|
||||
default:
|
||||
value = attr.getDisplayString();
|
||||
break;
|
||||
|
258
Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java
Executable file
258
Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java
Executable file
@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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.datasourceprocessors.xry;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.NoSuchElementException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
/**
|
||||
* Extracts XRY entities and determines the report type. An example of an XRY
|
||||
* entity would be:
|
||||
*
|
||||
* Calls # 1
|
||||
* Call Type: Missed
|
||||
* Time: 1/2/2019 1:23:45 PM (Device)
|
||||
* From
|
||||
* Tel: 12345678
|
||||
*/
|
||||
public final class XRYFileReader implements AutoCloseable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName());
|
||||
|
||||
//Assume UTF_16LE
|
||||
private static final Charset CHARSET = StandardCharsets.UTF_16LE;
|
||||
|
||||
//Assume TXT extension
|
||||
private static final String EXTENSION = "txt";
|
||||
|
||||
//Assume 0xFFFE is the BOM
|
||||
private static final int[] BOM = {0xFF, 0xFE};
|
||||
|
||||
//Assume all XRY reports have the type on the 3rd line.
|
||||
private static final int LINE_WITH_REPORT_TYPE = 3;
|
||||
|
||||
//Assume all headers are 5 lines in length.
|
||||
private static final int HEADER_LENGTH_IN_LINES = 5;
|
||||
|
||||
//Underlying reader for the xry file.
|
||||
private final BufferedReader reader;
|
||||
|
||||
private final StringBuilder xryEntity;
|
||||
|
||||
/**
|
||||
* Creates an XRYFileReader. As part of construction, the XRY file is opened
|
||||
* and the reader is advanced past the header. This leaves the reader
|
||||
* positioned at the start of the first XRY entity.
|
||||
*
|
||||
* The file is assumed to be encoded in UTF-16LE.
|
||||
*
|
||||
* @param xryFile XRY file to read. It is assumed that the caller has read
|
||||
* access to the path.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public XRYFileReader(Path xryFile) throws IOException {
|
||||
reader = Files.newBufferedReader(xryFile, CHARSET);
|
||||
|
||||
//Advance the reader to the start of the first XRY entity.
|
||||
for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) {
|
||||
reader.readLine();
|
||||
}
|
||||
|
||||
xryEntity = new StringBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the reader until a valid XRY entity is detected or EOF is
|
||||
* reached.
|
||||
*
|
||||
* @return Indication that there is another XRY entity to consume or that
|
||||
* the file has been exhausted.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public boolean hasNextEntity() throws IOException {
|
||||
//Entity has yet to be consumed.
|
||||
if (xryEntity.length() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (marksEndOfEntity(line)) {
|
||||
if (xryEntity.length() > 0) {
|
||||
//Found a non empty XRY entity.
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
xryEntity.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
//Check if EOF was hit before an entity delimiter was found.
|
||||
return xryEntity.length() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an XRY entity if there is one, otherwise an exception is thrown.
|
||||
*
|
||||
* @return A non-empty XRY entity.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @throws NoSuchElementException if there are no more XRY entities to
|
||||
* consume.
|
||||
*/
|
||||
public String nextEntity() throws IOException {
|
||||
if (hasNextEntity()) {
|
||||
String returnVal = xryEntity.toString();
|
||||
xryEntity.setLength(0);
|
||||
return returnVal;
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes any file handles this reader may have open.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
reader.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the line encountered during file reading signifies the end
|
||||
* of an XRY entity.
|
||||
*
|
||||
* @param line
|
||||
* @return
|
||||
*/
|
||||
private boolean marksEndOfEntity(String line) {
|
||||
return line.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Path is an XRY file. In order to be an XRY file, it must
|
||||
* have a txt extension, a 0xFFFE BOM (for UTF-16LE), and a non-empty report
|
||||
* type. The encoding is not verified any further than checking the BOM. To
|
||||
* get the report type, the file is read with a UTF-16LE decoder. If a
|
||||
* failure directly related to the decoding is encountered, it is logged and
|
||||
* the file is assumed not to be an XRY file. A direct consequence is that
|
||||
* there may be false positives.
|
||||
*
|
||||
* All other I/O exceptions are propagated up. If the Path represents a
|
||||
* symbolic link, this function will not follow it.
|
||||
*
|
||||
* @param file Path to test. It is assumed that the caller has read access
|
||||
* to the file.
|
||||
* @return Indicates whether the Path is a XRY file.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static boolean isXRYFile(Path file) throws IOException {
|
||||
String parsedExtension = FilenameUtils.getExtension(file.toString());
|
||||
|
||||
//A XRY file should have a txt extension.
|
||||
if (!EXTENSION.equals(parsedExtension)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BasicFileAttributes attr = Files.readAttributes(file,
|
||||
BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
||||
|
||||
//Do not follow symbolic links. XRY files cannot be a directory.
|
||||
if (attr.isSymbolicLink() || attr.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check 0xFFFE BOM
|
||||
if (!isXRYBOM(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Optional<String> reportType = getType(file);
|
||||
//All valid XRY reports should have a type.
|
||||
return reportType.isPresent();
|
||||
} catch (MalformedInputException ex) {
|
||||
logger.log(Level.WARNING, String.format("File at path [%s] had "
|
||||
+ "0xFFFE BOM but was not encoded in UTF-16LE.", file.toString()), ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the leading bytes of the Path to verify they match the expected
|
||||
* 0xFFFE BOM.
|
||||
*
|
||||
* @param file Path to check. It is assumed that the caller has read access
|
||||
* to the file.
|
||||
*
|
||||
* @return Indication if the leading bytes match.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
private static boolean isXRYBOM(Path file) throws IOException {
|
||||
try (InputStream in = Files.newInputStream(file, StandardOpenOption.READ)) {
|
||||
for (int bomByte : BOM) {
|
||||
if (in.read() != bomByte) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the report type from the Path. It is assumed that the Path will
|
||||
* have a UTF-16LE encoding. A MalformedInputException will be thrown if
|
||||
* there is a decoding error.
|
||||
*
|
||||
* @param file
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static Optional<String> getType(Path file) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(file, CHARSET)) {
|
||||
//Advance the reader to the line before the report type.
|
||||
for (int i = 0; i < LINE_WITH_REPORT_TYPE - 1; i++) {
|
||||
reader.readLine();
|
||||
}
|
||||
|
||||
String reportTypeLine = reader.readLine();
|
||||
if (reportTypeLine != null && !reportTypeLine.isEmpty()) {
|
||||
return Optional.of(reportTypeLine);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
77
Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java
Executable file
77
Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java
Executable file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.datasourceprocessors.xry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Extracts XRY files and (optionally) non-XRY files from a XRY (Report) folder.
|
||||
*/
|
||||
public final class XRYFolder {
|
||||
|
||||
//Depth that will contain XRY files. All XRY files will be immediate
|
||||
//children of their parent folder.
|
||||
private static final int XRY_FILES_DEPTH = 1;
|
||||
|
||||
/**
|
||||
* Searches for XRY files at the top level of a given folder. If at least
|
||||
* one file matches, the entire directory is assumed to be an XRY report.
|
||||
*
|
||||
* This function will not follow any symbolic links, the directory is tested
|
||||
* as is.
|
||||
*
|
||||
* @param folder Path to test. Assumes that caller has read access to the
|
||||
* folder and all of the top level files.
|
||||
* @return Indicates whether the Path is an XRY report.
|
||||
*
|
||||
* @throws IOException Error occurred during File I/O.
|
||||
* @throws SecurityException If the security manager denies access any of
|
||||
* the files.
|
||||
*/
|
||||
public static boolean isXRYFolder(Path folder) throws IOException {
|
||||
BasicFileAttributes attr = Files.readAttributes(folder,
|
||||
BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
||||
|
||||
if (!attr.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Files.walk by default will not follow symbolic links.
|
||||
try (Stream<Path> allFiles = Files.walk(folder, XRY_FILES_DEPTH)) {
|
||||
Iterator<Path> allFilesIterator = allFiles.iterator();
|
||||
while (allFilesIterator.hasNext()) {
|
||||
Path currentFile = allFilesIterator.next();
|
||||
if (XRYFileReader.isXRYFile(currentFile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (UncheckedIOException ex) {
|
||||
throw ex.getCause();
|
||||
}
|
||||
}
|
||||
}
|
@ -65,14 +65,6 @@ ExplorerNodeActionVisitor.volDetail.noVolMatchErr=Error: No Volume Matches.
|
||||
ExplorerNodeActionVisitor.imgDetail.noVolMatchesErr=Error: No Volume Matches.
|
||||
ExplorerNodeActionVisitor.exception.probGetParent.text=Problem getting parent from {0}: {1}
|
||||
ExtractAction.title.extractFiles.text=Extract File(s)
|
||||
ExtractAction.extractFiles.cantCreateFolderErr.msg=Could not create selected folder.
|
||||
ExtractAction.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite?
|
||||
ExtractAction.confDlg.destFileExist.title=File Exists
|
||||
ExtractAction.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0}
|
||||
ExtractAction.notifyDlg.noFileToExtr.msg=No file(s) to extract.
|
||||
ExtractAction.progress.extracting=Extracting
|
||||
ExtractAction.progress.cancellingExtraction={0} (Cancelling...)
|
||||
ExtractAction.done.notifyMsg.fileExtr.text=File(s) extracted.
|
||||
ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg=Unallocated Space is already being extracted on this Image. Please select a different Image.
|
||||
ExtractUnallocAction.msgDlg.folderDoesntExist.msg=Folder does not exist. Please choose a valid folder before continuing
|
||||
ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg=Select directory to save to
|
||||
|
@ -25,7 +25,6 @@ ExternalViewerAction.actionPerformed.failure.support.message=This platform (oper
|
||||
ExternalViewerAction.actionPerformed.failure.title=Open File Failure {0}
|
||||
ExternalViewerAction.actionPerformed.urlFailure.title=Open URL Failure
|
||||
ExternalViewerShortcutAction.title.text=Open in External Viewer Ctrl+E
|
||||
ExtractAction.noOpenCase.errMsg=No open case available.
|
||||
ExtractUnallocAction.imageError=Error extracting unallocated space from image
|
||||
ExtractUnallocAction.noFiles=No unallocated files found on volume
|
||||
ExtractUnallocAction.noOpenCase.errMsg=No open case available.
|
||||
@ -107,14 +106,6 @@ ExplorerNodeActionVisitor.volDetail.noVolMatchErr=Error: No Volume Matches.
|
||||
ExplorerNodeActionVisitor.imgDetail.noVolMatchesErr=Error: No Volume Matches.
|
||||
ExplorerNodeActionVisitor.exception.probGetParent.text=Problem getting parent from {0}: {1}
|
||||
ExtractAction.title.extractFiles.text=Extract File(s)
|
||||
ExtractAction.extractFiles.cantCreateFolderErr.msg=Could not create selected folder.
|
||||
ExtractAction.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite?
|
||||
ExtractAction.confDlg.destFileExist.title=File Exists
|
||||
ExtractAction.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0}
|
||||
ExtractAction.notifyDlg.noFileToExtr.msg=No file(s) to extract.
|
||||
ExtractAction.progress.extracting=Extracting
|
||||
ExtractAction.progress.cancellingExtraction={0} (Cancelling...)
|
||||
ExtractAction.done.notifyMsg.fileExtr.text=File(s) extracted.
|
||||
ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg=Unallocated Space is already being extracted on this Image. Please select a different Image.
|
||||
ExtractUnallocAction.msgDlg.folderDoesntExist.msg=Folder does not exist. Please choose a valid folder before continuing
|
||||
ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg=Select directory to save to
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,32 +18,13 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.directorytree;
|
||||
|
||||
import java.awt.Component;
|
||||
import org.sleuthkit.autopsy.directorytree.actionhelpers.ExtractActionHelper;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor;
|
||||
import org.openide.util.Lookup;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
/**
|
||||
@ -51,10 +32,6 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
*/
|
||||
public final class ExtractAction extends AbstractAction {
|
||||
|
||||
private Logger logger = Logger.getLogger(ExtractAction.class.getName());
|
||||
|
||||
private String userDefinedExportPath;
|
||||
|
||||
// This class is a singleton to support multi-selection of nodes, since
|
||||
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
|
||||
// node in the array returns a reference to the same action object from Node.getActions(boolean).
|
||||
@ -82,300 +59,10 @@ public final class ExtractAction extends AbstractAction {
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
|
||||
if (selectedFiles.size() > 1) {
|
||||
extractFiles(e, selectedFiles);
|
||||
} else if (selectedFiles.size() == 1) {
|
||||
AbstractFile source = selectedFiles.iterator().next();
|
||||
if (source.isDir()) {
|
||||
extractFiles(e, selectedFiles);
|
||||
} else {
|
||||
extractFile(e, selectedFiles.iterator().next());
|
||||
}
|
||||
}
|
||||
}
|
||||
Lookup lookup = Utilities.actionsGlobalContext();
|
||||
Collection<? extends AbstractFile> selectedFiles =lookup.lookupAll(AbstractFile.class);
|
||||
ExtractActionHelper extractor = new ExtractActionHelper();
|
||||
extractor.extract(e, selectedFiles);
|
||||
|
||||
/**
|
||||
* Called when user has selected a single file to extract
|
||||
*
|
||||
* @param event
|
||||
* @param selectedFile Selected file
|
||||
*/
|
||||
@NbBundle.Messages({"ExtractAction.noOpenCase.errMsg=No open case available."})
|
||||
private void extractFile(ActionEvent event, AbstractFile selectedFile) {
|
||||
Case openCase;
|
||||
try {
|
||||
openCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg());
|
||||
logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
|
||||
// If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
|
||||
fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName())));
|
||||
if (fileChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
|
||||
updateExportDirectory(fileChooser.getSelectedFile().getParent(), openCase);
|
||||
|
||||
ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
|
||||
fileExtractionTasks.add(new FileExtractionTask(selectedFile, fileChooser.getSelectedFile()));
|
||||
runExtractionTasks(event, fileExtractionTasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user has selected multiple files to extract
|
||||
*
|
||||
* @param event
|
||||
* @param selectedFiles Selected files
|
||||
*/
|
||||
private void extractFiles(ActionEvent event, Collection<? extends AbstractFile> selectedFiles) {
|
||||
Case openCase;
|
||||
try {
|
||||
openCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg());
|
||||
logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
JFileChooser folderChooser = new JFileChooser();
|
||||
folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
folderChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
|
||||
if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
|
||||
File destinationFolder = folderChooser.getSelectedFile();
|
||||
if (!destinationFolder.exists()) {
|
||||
try {
|
||||
destinationFolder.mkdirs();
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(),
|
||||
"ExtractAction.extractFiles.cantCreateFolderErr.msg"));
|
||||
logger.log(Level.INFO, "Unable to create folder(s) for user " + destinationFolder.getAbsolutePath(), ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateExportDirectory(destinationFolder.getPath(), openCase);
|
||||
|
||||
/*
|
||||
* get the unique set of files from the list. A user once reported
|
||||
* extraction taking days because it was extracting the same PST
|
||||
* file 20k times. They selected 20k email messages in the tree and
|
||||
* chose to extract them.
|
||||
*/
|
||||
Set<AbstractFile> uniqueFiles = new HashSet<>(selectedFiles);
|
||||
|
||||
// make a task for each file
|
||||
ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
|
||||
for (AbstractFile source : uniqueFiles) {
|
||||
// If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
|
||||
fileExtractionTasks.add(new FileExtractionTask(source, new File(destinationFolder, source.getId() + "-" + FileUtil.escapeFileName(source.getName()))));
|
||||
}
|
||||
runExtractionTasks(event, fileExtractionTasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the export directory path.
|
||||
*
|
||||
* @param openCase The current case.
|
||||
*
|
||||
* @return The export directory path.
|
||||
*/
|
||||
private String getExportDirectory(Case openCase) {
|
||||
String caseExportPath = openCase.getExportDirectory();
|
||||
|
||||
if (userDefinedExportPath == null) {
|
||||
return caseExportPath;
|
||||
}
|
||||
|
||||
File file = new File(userDefinedExportPath);
|
||||
if (file.exists() == false || file.isDirectory() == false) {
|
||||
return caseExportPath;
|
||||
}
|
||||
|
||||
return userDefinedExportPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the default export directory. If the directory path matches the
|
||||
* case export directory, then the directory used will always match the
|
||||
* export directory of any given case. Otherwise, the path last used will be
|
||||
* saved.
|
||||
*
|
||||
* @param exportPath The export path.
|
||||
* @param openCase The current case.
|
||||
*/
|
||||
private void updateExportDirectory(String exportPath, Case openCase) {
|
||||
if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
|
||||
userDefinedExportPath = null;
|
||||
} else {
|
||||
userDefinedExportPath = exportPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a series of file extraction tasks.
|
||||
*
|
||||
* @param event ActionEvent whose source will be used for
|
||||
* centering popup dialogs.
|
||||
* @param fileExtractionTasks List of file extraction tasks.
|
||||
*/
|
||||
private void runExtractionTasks(ActionEvent event, List<FileExtractionTask> fileExtractionTasks) {
|
||||
|
||||
// verify all of the sources and destinations are OK
|
||||
for (Iterator<FileExtractionTask> it = fileExtractionTasks.iterator(); it.hasNext();) {
|
||||
FileExtractionTask task = it.next();
|
||||
|
||||
if (ContentUtils.isDotDirectory(task.source)) {
|
||||
//JOptionPane.showMessageDialog((Component) e.getSource(), "Cannot extract virtual " + task.source.getName() + " directory.", "File is Virtual Directory", JOptionPane.WARNING_MESSAGE);
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* This code assumes that each destination is unique. We previously
|
||||
* satisfied that by adding the unique ID.
|
||||
*/
|
||||
if (task.destination.exists()) {
|
||||
if (JOptionPane.showConfirmDialog((Component) event.getSource(),
|
||||
NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.msg", task.destination.getAbsolutePath()),
|
||||
NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.title"),
|
||||
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
|
||||
if (!FileUtil.deleteFileDir(task.destination)) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(),
|
||||
NbBundle.getMessage(this.getClass(), "ExtractAction.msgDlg.cantOverwriteFile.msg", task.destination.getAbsolutePath()));
|
||||
it.remove();
|
||||
}
|
||||
} else {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// launch a thread to do the work
|
||||
if (!fileExtractionTasks.isEmpty()) {
|
||||
try {
|
||||
FileExtracter extracter = new FileExtracter(fileExtractionTasks);
|
||||
extracter.execute();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.WARNING, "Unable to start background file extraction thread", ex); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
MessageNotifyUtil.Message.info(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractAction.notifyDlg.noFileToExtr.msg"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores source and destination for file extraction.
|
||||
*/
|
||||
private class FileExtractionTask {
|
||||
|
||||
AbstractFile source;
|
||||
File destination;
|
||||
|
||||
/**
|
||||
* Create an instance of the FileExtractionTask.
|
||||
*
|
||||
* @param source The file to be extracted.
|
||||
* @param destination The destination for the extraction.
|
||||
*/
|
||||
FileExtractionTask(AbstractFile source, File destination) {
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread that does the actual extraction work
|
||||
*/
|
||||
private class FileExtracter extends SwingWorker<Object, Void> {
|
||||
|
||||
private final Logger logger = Logger.getLogger(FileExtracter.class.getName());
|
||||
private ProgressHandle progress;
|
||||
private final List<FileExtractionTask> extractionTasks;
|
||||
|
||||
/**
|
||||
* Create an instance of the FileExtracter.
|
||||
*
|
||||
* @param extractionTasks List of file extraction tasks.
|
||||
*/
|
||||
FileExtracter(List<FileExtractionTask> extractionTasks) {
|
||||
this.extractionTasks = extractionTasks;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
if (extractionTasks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup progress bar.
|
||||
final String displayName = NbBundle.getMessage(this.getClass(), "ExtractAction.progress.extracting");
|
||||
progress = ProgressHandle.createHandle(displayName, new Cancellable() {
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
if (progress != null) {
|
||||
progress.setDisplayName(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractAction.progress.cancellingExtraction", displayName));
|
||||
}
|
||||
return ExtractAction.FileExtracter.this.cancel(true);
|
||||
}
|
||||
});
|
||||
progress.start();
|
||||
progress.switchToIndeterminate();
|
||||
|
||||
/*
|
||||
* @@@ Add back in -> Causes exceptions int workUnits = 0; for
|
||||
* (FileExtractionTask task : extractionTasks) { workUnits +=
|
||||
* calculateProgressBarWorkUnits(task.source); }
|
||||
* progress.switchToDeterminate(workUnits);
|
||||
*/
|
||||
// Do the extraction tasks.
|
||||
for (FileExtractionTask task : this.extractionTasks) {
|
||||
// @@@ Note, we are no longer passing in progress
|
||||
ExtractFscContentVisitor.extract(task.source, task.destination, null, this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
boolean msgDisplayed = false;
|
||||
try {
|
||||
super.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.info(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractAction.done.notifyMsg.extractErr", ex.getMessage()));
|
||||
msgDisplayed = true;
|
||||
} finally {
|
||||
progress.finish();
|
||||
if (!this.isCancelled() && !msgDisplayed) {
|
||||
MessageNotifyUtil.Message.info(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractAction.done.notifyMsg.fileExtr.text"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of work units for the progress bar.
|
||||
*
|
||||
* @param file File whose children will be reviewed to get the number of
|
||||
* work units.
|
||||
*
|
||||
* @return The number of work units.
|
||||
*/
|
||||
/*
|
||||
* private int calculateProgressBarWorkUnits(AbstractFile file) { int
|
||||
* workUnits = 0; if (file.isFile()) { workUnits += file.getSize(); }
|
||||
* else { try { for (Content child : file.getChildren()) { if (child
|
||||
* instanceof AbstractFile) { workUnits +=
|
||||
* calculateProgressBarWorkUnits((AbstractFile) child); } } } catch
|
||||
* (TskCoreException ex) { logger.log(Level.SEVERE, "Could not get
|
||||
* children of content", ex); //NON-NLS } } return workUnits;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ final class ExtractUnallocAction extends AbstractAction {
|
||||
try {
|
||||
openCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
MessageNotifyUtil.Message.info(Bundle.ExtractAction_noOpenCase_errMsg());
|
||||
MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg());
|
||||
return;
|
||||
}
|
||||
List<OutputFileData> copyList = new ArrayList<OutputFileData>() {
|
||||
|
@ -0,0 +1,8 @@
|
||||
ExtractActionHelper.extractFiles.cantCreateFolderErr.msg=Could not create selected folder.
|
||||
ExtractActionHelper.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite?
|
||||
ExtractActionHelper.confDlg.destFileExist.title=File Exists
|
||||
ExtractActionHelper.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0}
|
||||
ExtractActionHelper.notifyDlg.noFileToExtr.msg=No file(s) to extract.
|
||||
ExtractActionHelper.progress.extracting=Extracting
|
||||
ExtractActionHelper.progress.cancellingExtraction={0} (Cancelling...)
|
||||
ExtractActionHelper.done.notifyMsg.fileExtr.text=File(s) extracted.
|
@ -0,0 +1,9 @@
|
||||
ExtractActionHelper.extractFiles.cantCreateFolderErr.msg=Could not create selected folder.
|
||||
ExtractActionHelper.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite?
|
||||
ExtractActionHelper.confDlg.destFileExist.title=File Exists
|
||||
ExtractActionHelper.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0}
|
||||
ExtractActionHelper.noOpenCase.errMsg=No open case available.
|
||||
ExtractActionHelper.notifyDlg.noFileToExtr.msg=No file(s) to extract.
|
||||
ExtractActionHelper.progress.extracting=Extracting
|
||||
ExtractActionHelper.progress.cancellingExtraction={0} (Cancelling...)
|
||||
ExtractActionHelper.done.notifyMsg.fileExtr.text=File(s) extracted.
|
@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-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 content except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.directorytree.actionhelpers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
/**
|
||||
* Helper class for methods needed by actions which extract files.
|
||||
*/
|
||||
public class ExtractActionHelper {
|
||||
|
||||
private final Logger logger = Logger.getLogger(ExtractActionHelper.class.getName());
|
||||
private String userDefinedExportPath;
|
||||
|
||||
/**
|
||||
* Extract the specified collection of files with an event specified for
|
||||
* context.
|
||||
*
|
||||
* @param event The event that caused the extract method to be
|
||||
* called.
|
||||
* @param selectedFiles The files to be extracted from the current case.
|
||||
*/
|
||||
public void extract(ActionEvent event, Collection<? extends AbstractFile> selectedFiles) {
|
||||
if (selectedFiles.size() > 1) {
|
||||
extractFiles(event, selectedFiles);
|
||||
} else if (selectedFiles.size() == 1) {
|
||||
AbstractFile source = selectedFiles.iterator().next();
|
||||
if (source.isDir()) {
|
||||
extractFiles(event, selectedFiles);
|
||||
} else {
|
||||
extractFile(event, selectedFiles.iterator().next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user has selected a single file to extract
|
||||
*
|
||||
* @param event
|
||||
* @param selectedFile Selected file
|
||||
*/
|
||||
@NbBundle.Messages({"ExtractActionHelper.noOpenCase.errMsg=No open case available."})
|
||||
private void extractFile(ActionEvent event, AbstractFile selectedFile) {
|
||||
Case openCase;
|
||||
try {
|
||||
openCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractActionHelper_noOpenCase_errMsg());
|
||||
logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
|
||||
// If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
|
||||
fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName())));
|
||||
if (fileChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
|
||||
updateExportDirectory(fileChooser.getSelectedFile().getParent(), openCase);
|
||||
|
||||
ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
|
||||
fileExtractionTasks.add(new FileExtractionTask(selectedFile, fileChooser.getSelectedFile()));
|
||||
runExtractionTasks(event, fileExtractionTasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user has selected multiple files to extract
|
||||
*
|
||||
* @param event
|
||||
* @param selectedFiles Selected files
|
||||
*/
|
||||
private void extractFiles(ActionEvent event, Collection<? extends AbstractFile> selectedFiles) {
|
||||
Case openCase;
|
||||
try {
|
||||
openCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractActionHelper_noOpenCase_errMsg());
|
||||
logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
JFileChooser folderChooser = new JFileChooser();
|
||||
folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
folderChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
|
||||
if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
|
||||
File destinationFolder = folderChooser.getSelectedFile();
|
||||
if (!destinationFolder.exists()) {
|
||||
try {
|
||||
destinationFolder.mkdirs();
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(),
|
||||
"ExtractAction.extractFiles.cantCreateFolderErr.msg"));
|
||||
logger.log(Level.INFO, "Unable to create folder(s) for user " + destinationFolder.getAbsolutePath(), ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateExportDirectory(destinationFolder.getPath(), openCase);
|
||||
|
||||
/*
|
||||
* get the unique set of files from the list. A user once reported
|
||||
* extraction taking days because it was extracting the same PST
|
||||
* file 20k times. They selected 20k email messages in the tree and
|
||||
* chose to extract them.
|
||||
*/
|
||||
Set<AbstractFile> uniqueFiles = new HashSet<>(selectedFiles);
|
||||
|
||||
// make a task for each file
|
||||
ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
|
||||
for (AbstractFile source : uniqueFiles) {
|
||||
// If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
|
||||
fileExtractionTasks.add(new FileExtractionTask(source, new File(destinationFolder, source.getId() + "-" + FileUtil.escapeFileName(source.getName()))));
|
||||
}
|
||||
runExtractionTasks(event, fileExtractionTasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the export directory path.
|
||||
*
|
||||
* @param openCase The current case.
|
||||
*
|
||||
* @return The export directory path.
|
||||
*/
|
||||
private String getExportDirectory(Case openCase) {
|
||||
String caseExportPath = openCase.getExportDirectory();
|
||||
|
||||
if (userDefinedExportPath == null) {
|
||||
return caseExportPath;
|
||||
}
|
||||
|
||||
File file = new File(userDefinedExportPath);
|
||||
if (file.exists() == false || file.isDirectory() == false) {
|
||||
return caseExportPath;
|
||||
}
|
||||
|
||||
return userDefinedExportPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the default export directory. If the directory path matches the
|
||||
* case export directory, then the directory used will always match the
|
||||
* export directory of any given case. Otherwise, the path last used will be
|
||||
* saved.
|
||||
*
|
||||
* @param exportPath The export path.
|
||||
* @param openCase The current case.
|
||||
*/
|
||||
private void updateExportDirectory(String exportPath, Case openCase) {
|
||||
if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
|
||||
userDefinedExportPath = null;
|
||||
} else {
|
||||
userDefinedExportPath = exportPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a series of file extraction tasks.
|
||||
*
|
||||
* @param event ActionEvent whose source will be used for
|
||||
* centering popup dialogs.
|
||||
* @param fileExtractionTasks List of file extraction tasks.
|
||||
*/
|
||||
private void runExtractionTasks(ActionEvent event, List<FileExtractionTask> fileExtractionTasks) {
|
||||
|
||||
// verify all of the sources and destinations are OK
|
||||
for (Iterator<FileExtractionTask> it = fileExtractionTasks.iterator(); it.hasNext();) {
|
||||
FileExtractionTask task = it.next();
|
||||
|
||||
if (ContentUtils.isDotDirectory(task.source)) {
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* This code assumes that each destination is unique. We previously
|
||||
* satisfied that by adding the unique ID.
|
||||
*/
|
||||
if (task.destination.exists()) {
|
||||
if (JOptionPane.showConfirmDialog((Component) event.getSource(),
|
||||
NbBundle.getMessage(this.getClass(), "ExtractActionHelper.confDlg.destFileExist.msg", task.destination.getAbsolutePath()),
|
||||
NbBundle.getMessage(this.getClass(), "ExtractActionHelper.confDlg.destFileExist.title"),
|
||||
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
|
||||
if (!FileUtil.deleteFileDir(task.destination)) {
|
||||
JOptionPane.showMessageDialog((Component) event.getSource(),
|
||||
NbBundle.getMessage(this.getClass(), "ExtractActionHelper.msgDlg.cantOverwriteFile.msg", task.destination.getAbsolutePath()));
|
||||
it.remove();
|
||||
}
|
||||
} else {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// launch a thread to do the work
|
||||
if (!fileExtractionTasks.isEmpty()) {
|
||||
try {
|
||||
FileExtracter extracter = new FileExtracter(fileExtractionTasks);
|
||||
extracter.execute();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.WARNING, "Unable to start background file extraction thread", ex); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
MessageNotifyUtil.Message.info(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractActionHelper.notifyDlg.noFileToExtr.msg"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores source and destination for file extraction.
|
||||
*/
|
||||
private class FileExtractionTask {
|
||||
|
||||
AbstractFile source;
|
||||
File destination;
|
||||
|
||||
/**
|
||||
* Create an instance of the FileExtractionTask.
|
||||
*
|
||||
* @param source The file to be extracted.
|
||||
* @param destination The destination for the extraction.
|
||||
*/
|
||||
FileExtractionTask(AbstractFile source, File destination) {
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread that does the actual extraction work
|
||||
*/
|
||||
private class FileExtracter extends SwingWorker<Object, Void> {
|
||||
|
||||
private final Logger logger = Logger.getLogger(FileExtracter.class.getName());
|
||||
private ProgressHandle progress;
|
||||
private final List<FileExtractionTask> extractionTasks;
|
||||
|
||||
/**
|
||||
* Create an instance of the FileExtracter.
|
||||
*
|
||||
* @param extractionTasks List of file extraction tasks.
|
||||
*/
|
||||
FileExtracter(List<FileExtractionTask> extractionTasks) {
|
||||
this.extractionTasks = extractionTasks;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
if (extractionTasks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup progress bar.
|
||||
final String displayName = NbBundle.getMessage(this.getClass(), "ExtractActionHelper.progress.extracting");
|
||||
progress = ProgressHandle.createHandle(displayName, new Cancellable() {
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
if (progress != null) {
|
||||
progress.setDisplayName(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractActionHelper.progress.cancellingExtraction", displayName));
|
||||
}
|
||||
return ExtractActionHelper.FileExtracter.this.cancel(true);
|
||||
}
|
||||
});
|
||||
progress.start();
|
||||
progress.switchToIndeterminate();
|
||||
|
||||
/*
|
||||
* @@@ Add back in -> Causes exceptions int workUnits = 0; for
|
||||
* (FileExtractionTask task : extractionTasks) { workUnits +=
|
||||
* calculateProgressBarWorkUnits(task.source); }
|
||||
* progress.switchToDeterminate(workUnits);
|
||||
*/
|
||||
// Do the extraction tasks.
|
||||
for (FileExtractionTask task : this.extractionTasks) {
|
||||
// @@@ Note, we are no longer passing in progress
|
||||
ContentUtils.ExtractFscContentVisitor.extract(task.source, task.destination, null, this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
boolean msgDisplayed = false;
|
||||
try {
|
||||
super.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.info(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractActionHelper.done.notifyMsg.extractErr", ex.getMessage()));
|
||||
msgDisplayed = true;
|
||||
} finally {
|
||||
progress.finish();
|
||||
if (!this.isCancelled() && !msgDisplayed) {
|
||||
MessageNotifyUtil.Message.info(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractActionHelper.done.notifyMsg.fileExtr.text"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of work units for the progress bar.
|
||||
*
|
||||
* @param file File whose children will be reviewed to get the number of
|
||||
* work units.
|
||||
*
|
||||
* @return The number of work units.
|
||||
*/
|
||||
/*
|
||||
* private int calculateProgressBarWorkUnits(AbstractFile file) { int
|
||||
* workUnits = 0; if (file.isFile()) { workUnits += file.getSize(); }
|
||||
* else { try { for (Content child : file.getChildren()) { if (child
|
||||
* instanceof AbstractFile) { workUnits +=
|
||||
* calculateProgressBarWorkUnits((AbstractFile) child); } } } catch
|
||||
* (TskCoreException ex) { logger.log(Level.SEVERE, "Could not get
|
||||
* children of content", ex); //NON-NLS } } return workUnits; }
|
||||
*/
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Autopsy
|
||||
*
|
||||
* 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.exceptions;
|
||||
|
||||
/**
|
||||
* An exception to be thrown which can contain a user-friendly message.
|
||||
*/
|
||||
public abstract class AutopsyException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String userMessage;
|
||||
|
||||
/**
|
||||
* Constructs an AutopsyException with identical exception and user-friendly
|
||||
* messages.
|
||||
*
|
||||
* @param message Exception message.
|
||||
*/
|
||||
public AutopsyException(String message) {
|
||||
super(message);
|
||||
this.userMessage = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an AutopsyException with an exception message and user-friendly message.
|
||||
*
|
||||
* @param message Exception message.
|
||||
* @param userMessage The user-friendly message to include in this
|
||||
* exception.
|
||||
*/
|
||||
public AutopsyException(String message, String userMessage) {
|
||||
super(message);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an AutopsyException with identical exception and user-friendly
|
||||
* messages.
|
||||
*
|
||||
* @param message Exception message.
|
||||
* @param cause Exception cause.
|
||||
*/
|
||||
public AutopsyException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.userMessage = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an AutopsyException with an exception message, a user-friendly messages, and a cause.
|
||||
*
|
||||
* @param message Exception message.
|
||||
* @param userMessage The user-friendly message to include in this
|
||||
* exception.
|
||||
* @param cause Exception cause.
|
||||
*/
|
||||
public AutopsyException(String message, String userMessage, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user-friendly message if one exists.
|
||||
*
|
||||
* @return The user-friendly message if one was explicitly set, otherwise
|
||||
* returns the exception message.
|
||||
*/
|
||||
public String getUserMessage() {
|
||||
return userMessage;
|
||||
}
|
||||
|
||||
}
|
6
Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties
Executable file
6
Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties
Executable file
@ -0,0 +1,6 @@
|
||||
CTL_OpenGeolocation=Geolocation
|
||||
CTL_GeolocationTopComponentAction=GeolocationTopComponent
|
||||
CTL_GeolocationTopComponent=Geolocation
|
||||
RefreshPanel.refreshLabel.text=The geolocation data has been updated, the visualization may be out of date.
|
||||
RefreshPanel.refreshButton.text=Refresh View
|
||||
RefreshPanel.closeButton.text=
|
10
Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED
Executable file
10
Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED
Executable file
@ -0,0 +1,10 @@
|
||||
CTL_OpenGeolocation=Geolocation
|
||||
CTL_GeolocationTopComponentAction=GeolocationTopComponent
|
||||
CTL_GeolocationTopComponent=Geolocation
|
||||
GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete.
|
||||
GLTopComponent_name=Geolocation
|
||||
OpenGeolocationAction_displayName=Geolocation
|
||||
OpenGeolocationAction_name=Geolocation
|
||||
RefreshPanel.refreshLabel.text=The geolocation data has been updated, the visualization may be out of date.
|
||||
RefreshPanel.refreshButton.text=Refresh View
|
||||
RefreshPanel.closeButton.text=
|
28
Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form
Executable file
28
Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form
Executable file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="org.sleuthkit.autopsy.geolocation.MapPanel" name="mapPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||
<BorderConstraints direction="Center"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
217
Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java
Executable file
217
Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java
Executable file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.jxmapviewer.viewer.Waypoint;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.RetainLocation;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
|
||||
/**
|
||||
* Top component which displays the Geolocation Tool.
|
||||
*
|
||||
*/
|
||||
@TopComponent.Description(preferredID = "GeolocationTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER)
|
||||
@TopComponent.Registration(mode = "geolocation", openAtStartup = false)
|
||||
@RetainLocation("geolocation")
|
||||
@SuppressWarnings("PMD.SingularField")
|
||||
public final class GeolocationTopComponent extends TopComponent {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GeolocationTopComponent.class.getName());
|
||||
|
||||
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED);
|
||||
|
||||
private final PropertyChangeListener ingestListener;
|
||||
|
||||
final RefreshPanel refreshPanel = new RefreshPanel();
|
||||
|
||||
@Messages({
|
||||
"GLTopComponent_name=Geolocation",
|
||||
"GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete."
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates new form GeoLocationTopComponent
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public GeolocationTopComponent() {
|
||||
initComponents();
|
||||
initWaypoints();
|
||||
setName(Bundle.GLTopComponent_name());
|
||||
|
||||
this.ingestListener = pce -> {
|
||||
String eventType = pce.getPropertyName();
|
||||
if (eventType.equals(DATA_ADDED.toString())) {
|
||||
// Indicate that a refresh may be needed for GPS data.
|
||||
ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue();
|
||||
if (null != eventData
|
||||
&& (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID())) {
|
||||
|
||||
showRefreshPanel(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
refreshPanel.addCloseActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showRefreshPanel(false);
|
||||
}
|
||||
});
|
||||
|
||||
refreshPanel.addRefreshActionListner(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
mapPanel.clearWaypoints();
|
||||
initWaypoints();
|
||||
showRefreshPanel(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify() {
|
||||
super.addNotify();
|
||||
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, ingestListener);
|
||||
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
|
||||
mapPanel.clearWaypoints();
|
||||
if (evt.getNewValue() != null) {
|
||||
initWaypoints();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentOpened() {
|
||||
super.componentOpened();
|
||||
WindowManager.getDefault().setTopComponentFloating(this, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of the refresh panel at the top of the mapPanel.
|
||||
*
|
||||
* @param show Whether to show or hide the panel.
|
||||
*/
|
||||
private void showRefreshPanel(boolean show) {
|
||||
if (show) {
|
||||
mapPanel.add(refreshPanel, BorderLayout.NORTH);
|
||||
} else {
|
||||
mapPanel.remove(refreshPanel);
|
||||
}
|
||||
mapPanel.revalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a SwingWorker thread to find all of the artifacts that have GPS
|
||||
* coordinates.
|
||||
*
|
||||
*/
|
||||
private void initWaypoints() {
|
||||
SwingWorker<List<Waypoint>, Waypoint> worker = new SwingWorker<List<Waypoint>, Waypoint>() {
|
||||
@Override
|
||||
protected List<Waypoint> doInBackground() throws Exception {
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
|
||||
return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
if (isDone() && !isCancelled()) {
|
||||
try {
|
||||
List<Waypoint> waypoints = get();
|
||||
if (waypoints == null || waypoints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Waypoint point : waypoints) {
|
||||
mapPanel.addWaypoint(point);
|
||||
}
|
||||
|
||||
// There might be a better way to decide how to center
|
||||
// but for now just use the first way point.
|
||||
mapPanel.setCenterLocation(waypoints.get(0));
|
||||
|
||||
} catch (ExecutionException ex) {
|
||||
logger.log(Level.WARNING, "An exception occured while initializing waypoints for geolocation window.", ex);
|
||||
MessageNotifyUtil.Message.error(Bundle.GLTopComponent_initilzation_error());
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.WARNING, "The initializing thread for geolocation window was interrupted.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
worker.execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel();
|
||||
|
||||
setLayout(new java.awt.BorderLayout());
|
||||
add(mapPanel, java.awt.BorderLayout.CENTER);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
89
Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form
Executable file
89
Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form
Executable file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="focusable" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-42,0,0,0,58"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="org.jxmapviewer.JXMapViewer" name="mapViewer">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||
<BorderConstraints direction="Center"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="zoomPanel">
|
||||
<Properties>
|
||||
<Property name="focusable" type="boolean" value="false"/>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
<Property name="requestFocusEnabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="4" insetsLeft="4" insetsBottom="4" insetsRight="4" anchor="16" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
<Component id="zoomSlider" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="zoomSlider" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JSlider" name="zoomSlider">
|
||||
<Properties>
|
||||
<Property name="maximum" type="int" value="15"/>
|
||||
<Property name="minimum" type="int" value="10"/>
|
||||
<Property name="minorTickSpacing" type="int" value="1"/>
|
||||
<Property name="orientation" type="int" value="1"/>
|
||||
<Property name="paintTicks" type="boolean" value="true"/>
|
||||
<Property name="snapToTicks" type="boolean" value="true"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[35, 100]"/>
|
||||
</Property>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[35, 190]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="zoomSliderStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
238
Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java
Executable file
238
Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java
Executable file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
import javax.swing.event.MouseInputListener;
|
||||
import org.jxmapviewer.OSMTileFactoryInfo;
|
||||
import org.jxmapviewer.input.CenterMapListener;
|
||||
import org.jxmapviewer.input.PanMouseInputListener;
|
||||
import org.jxmapviewer.input.ZoomMouseWheelListenerCursor;
|
||||
import org.jxmapviewer.viewer.DefaultTileFactory;
|
||||
import org.jxmapviewer.viewer.GeoPosition;
|
||||
import org.jxmapviewer.viewer.TileFactoryInfo;
|
||||
import org.jxmapviewer.viewer.Waypoint;
|
||||
import org.jxmapviewer.viewer.WaypointPainter;
|
||||
|
||||
/**
|
||||
* Main panel with the JJXMapViewer object and its basic controls.
|
||||
*/
|
||||
final class MapPanel extends javax.swing.JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private boolean zoomChanging;
|
||||
|
||||
// Using a DefaultListModel to store the way points because we get
|
||||
// a lot of functionality for free, like listeners.
|
||||
private final DefaultListModel<Waypoint> waypointListModel;
|
||||
|
||||
/**
|
||||
* Creates new form MapPanel
|
||||
*/
|
||||
MapPanel() {
|
||||
waypointListModel = new DefaultListModel<>();
|
||||
zoomChanging = false;
|
||||
|
||||
initComponents();
|
||||
initMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the map.
|
||||
*/
|
||||
private void initMap() {
|
||||
|
||||
TileFactoryInfo info = new OSMTileFactoryInfo();
|
||||
DefaultTileFactory tileFactory = new DefaultTileFactory(info);
|
||||
mapViewer.setTileFactory(tileFactory);
|
||||
|
||||
// Add Mouse interactions
|
||||
MouseInputListener mia = new PanMouseInputListener(mapViewer);
|
||||
mapViewer.addMouseListener(mia);
|
||||
mapViewer.addMouseMotionListener(mia);
|
||||
|
||||
mapViewer.addMouseListener(new CenterMapListener(mapViewer));
|
||||
mapViewer.addMouseWheelListener(new ZoomMouseWheelListenerCursor(mapViewer));
|
||||
|
||||
// Listen to the map for a change in zoom so that we can update the slider.
|
||||
mapViewer.addPropertyChangeListener("zoom", new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
zoomSlider.setValue(mapViewer.getZoom());
|
||||
}
|
||||
});
|
||||
|
||||
zoomSlider.setMinimum(tileFactory.getInfo().getMinimumZoomLevel());
|
||||
zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel());
|
||||
|
||||
|
||||
setZoom(tileFactory.getInfo().getMaximumZoomLevel()- 1);
|
||||
mapViewer.setAddressLocation(new GeoPosition(0, 0));
|
||||
|
||||
// Listener for new way points being added to the map.
|
||||
waypointListModel.addListDataListener(new ListDataListener() {
|
||||
@Override
|
||||
public void intervalAdded(ListDataEvent e) {
|
||||
mapViewer.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void intervalRemoved(ListDataEvent e) {
|
||||
mapViewer.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contentsChanged(ListDataEvent e) {
|
||||
mapViewer.repaint();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Basic painters for the way points.
|
||||
WaypointPainter<Waypoint> waypointPainter = new WaypointPainter<Waypoint>() {
|
||||
@Override
|
||||
public Set<Waypoint> getWaypoints() {
|
||||
Set<Waypoint> set = new HashSet<>();
|
||||
for (int index = 0; index < waypointListModel.getSize(); index++) {
|
||||
set.add(waypointListModel.get(index));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
};
|
||||
|
||||
mapViewer.setOverlayPainter(waypointPainter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a way point to the map.
|
||||
*
|
||||
* @param waypoint
|
||||
*/
|
||||
void addWaypoint(Waypoint waypoint) {
|
||||
waypointListModel.addElement(waypoint);
|
||||
}
|
||||
|
||||
void setCenterLocation(Waypoint waypoint) {
|
||||
mapViewer.setCenterPosition(waypoint.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current zoom level.
|
||||
*
|
||||
* @param zoom
|
||||
*/
|
||||
void setZoom(int zoom) {
|
||||
zoomChanging = true;
|
||||
mapViewer.setZoom(zoom);
|
||||
zoomSlider.setValue((zoomSlider.getMaximum() + zoomSlider.getMinimum())- zoom);
|
||||
zoomChanging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all of the way points from the map.
|
||||
*/
|
||||
void clearWaypoints() {
|
||||
waypointListModel.removeAllElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
java.awt.GridBagConstraints gridBagConstraints;
|
||||
|
||||
mapViewer = new org.jxmapviewer.JXMapViewer();
|
||||
zoomPanel = new javax.swing.JPanel();
|
||||
zoomSlider = new javax.swing.JSlider();
|
||||
|
||||
setFocusable(false);
|
||||
setLayout(new java.awt.BorderLayout());
|
||||
|
||||
mapViewer.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
zoomPanel.setFocusable(false);
|
||||
zoomPanel.setOpaque(false);
|
||||
zoomPanel.setRequestFocusEnabled(false);
|
||||
|
||||
zoomSlider.setMaximum(15);
|
||||
zoomSlider.setMinimum(10);
|
||||
zoomSlider.setMinorTickSpacing(1);
|
||||
zoomSlider.setOrientation(javax.swing.JSlider.VERTICAL);
|
||||
zoomSlider.setPaintTicks(true);
|
||||
zoomSlider.setSnapToTicks(true);
|
||||
zoomSlider.setMinimumSize(new java.awt.Dimension(35, 100));
|
||||
zoomSlider.setOpaque(false);
|
||||
zoomSlider.setPreferredSize(new java.awt.Dimension(35, 190));
|
||||
zoomSlider.addChangeListener(new javax.swing.event.ChangeListener() {
|
||||
public void stateChanged(javax.swing.event.ChangeEvent evt) {
|
||||
zoomSliderStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout zoomPanelLayout = new javax.swing.GroupLayout(zoomPanel);
|
||||
zoomPanel.setLayout(zoomPanelLayout);
|
||||
zoomPanelLayout.setHorizontalGroup(
|
||||
zoomPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(zoomPanelLayout.createSequentialGroup()
|
||||
.addGap(0, 0, 0)
|
||||
.addComponent(zoomSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
zoomPanelLayout.setVerticalGroup(
|
||||
zoomPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, zoomPanelLayout.createSequentialGroup()
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(zoomSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(0, 0, 0))
|
||||
);
|
||||
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
|
||||
mapViewer.add(zoomPanel, gridBagConstraints);
|
||||
|
||||
add(mapViewer, java.awt.BorderLayout.CENTER);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void zoomSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_zoomSliderStateChanged
|
||||
if (!zoomChanging) {
|
||||
setZoom(zoomSlider.getValue());
|
||||
}
|
||||
}//GEN-LAST:event_zoomSliderStateChanged
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private org.jxmapviewer.JXMapViewer mapViewer;
|
||||
private javax.swing.JPanel zoomPanel;
|
||||
private javax.swing.JSlider zoomSlider;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
86
Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java
Executable file
86
Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java
Executable file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jxmapviewer.viewer.GeoPosition;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
||||
|
||||
/**
|
||||
* A Wrapper for the datamodel Waypoint class that implements the jxmapviewer
|
||||
* Waypoint interfact for use in the map.
|
||||
*
|
||||
*/
|
||||
final class MapWaypoint implements org.jxmapviewer.viewer.Waypoint{
|
||||
|
||||
private final Waypoint dataModelWaypoint;
|
||||
private final GeoPosition position;
|
||||
|
||||
/**
|
||||
* Private constructor for MapWaypoint
|
||||
*
|
||||
* @param dataModelWaypoint The datamodel waypoint to wrap
|
||||
*/
|
||||
private MapWaypoint(Waypoint dataModelWaypoint) {
|
||||
this.dataModelWaypoint = dataModelWaypoint;
|
||||
position = new GeoPosition(dataModelWaypoint.getLatitude(), dataModelWaypoint.getLongitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of jxmapviewer waypoints from the current case.
|
||||
*
|
||||
* @param skCase Current case
|
||||
*
|
||||
* @return List of jxmapviewer waypoints
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
static List<org.jxmapviewer.viewer.Waypoint> getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException{
|
||||
List<Waypoint> points = Waypoint.getAllWaypoints(skCase);
|
||||
|
||||
List<Route> routes = Route.getRoutes(skCase);
|
||||
for(Route route: routes) {
|
||||
points.addAll(route.getRoute());
|
||||
}
|
||||
|
||||
List<org.jxmapviewer.viewer.Waypoint> mapPoints = new ArrayList<>();
|
||||
|
||||
for(Waypoint point: points) {
|
||||
mapPoints.add(new MapWaypoint(point));
|
||||
}
|
||||
|
||||
return mapPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public GeoPosition getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
String getLabel() {
|
||||
return dataModelWaypoint.getLabel();
|
||||
}
|
||||
}
|
95
Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java
Executable file
95
Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java
Executable file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import org.openide.awt.ActionID;
|
||||
import org.openide.awt.ActionReference;
|
||||
import org.openide.awt.ActionReferences;
|
||||
import org.openide.awt.ActionRegistration;
|
||||
import org.openide.util.HelpCtx;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.util.actions.CallableSystemAction;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.core.RuntimeProperties;
|
||||
|
||||
/**
|
||||
* Action that opens the Geolocation window. Available through the Tools menu.
|
||||
*
|
||||
*/
|
||||
@ActionID(category = "Tools",
|
||||
id = "org.sleuthkit.autopsy.geolocation.OpenGeolocationAction")
|
||||
@ActionRegistration(displayName = "#CTL_OpenGeolocation", lazy = false)
|
||||
@ActionReferences(value = {
|
||||
@ActionReference(path = "Menu/Tools", position = 102)})
|
||||
public class OpenGeolocationAction extends CallableSystemAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Messages({
|
||||
"OpenGeolocationAction_name=Geolocation",
|
||||
"OpenGeolocationAction_displayName=Geolocation"
|
||||
})
|
||||
|
||||
/**
|
||||
* Constructs the new action of opening the Geolocation window.
|
||||
*/
|
||||
public OpenGeolocationAction() {
|
||||
setEnabled(false); //disabled by default. Will be enabled in Case.java when a case is opened.
|
||||
|
||||
PropertyChangeListener caseChangeListener = (PropertyChangeEvent evt) -> {
|
||||
if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) {
|
||||
setEnabled(RuntimeProperties.runningWithGUI() && evt.getNewValue() != null);
|
||||
}
|
||||
};
|
||||
|
||||
Case.addPropertyChangeListener(caseChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performAction() {
|
||||
final TopComponent topComponent = WindowManager.getDefault().findTopComponent("GeolocationTopComponent");
|
||||
if (topComponent != null) {
|
||||
if (topComponent.isOpened() == false) {
|
||||
topComponent.open();
|
||||
}
|
||||
topComponent.toFront();
|
||||
topComponent.requestActive();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return Bundle.OpenGeolocationAction_displayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HelpCtx getHelpCtx() {
|
||||
return HelpCtx.DEFAULT_HELP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean asynchronous() {
|
||||
return false; // run on edt
|
||||
}
|
||||
|
||||
}
|
86
Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form
Executable file
86
Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form
Executable file
@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="33" green="33" red="0" type="rgb"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,3,15"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="refreshLabel">
|
||||
<Properties>
|
||||
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="ff" green="ff" red="ff" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/images/warning16.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.refreshLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="10" insetsBottom="15" insetsRight="10" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="refreshButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.refreshButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[2, 5, 2, 5]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="closeButton">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="0" green="0" red="0" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/geolocation/images/cross-script.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[0, 0, 0, 0]"/>
|
||||
</Property>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="closeButtonActionPerformed"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="0" insetsRight="5" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
115
Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java
Executable file
115
Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java
Executable file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* Message panel to info user that the MapPanel may need to be refreshed
|
||||
* due to new artifacts.
|
||||
*
|
||||
*/
|
||||
final class RefreshPanel extends JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates new form RefreshPanel
|
||||
*/
|
||||
RefreshPanel() {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
void addCloseActionListener(ActionListener listener) {
|
||||
closeButton.addActionListener(listener);
|
||||
}
|
||||
|
||||
void addRefreshActionListner(ActionListener listener) {
|
||||
refreshButton.addActionListener(listener);
|
||||
}
|
||||
|
||||
void removeCloseActionListner(ActionListener listener) {
|
||||
closeButton.removeActionListener(listener);
|
||||
}
|
||||
|
||||
void removeRefreshActionListner(ActionListener listener) {
|
||||
refreshButton.removeActionListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
java.awt.GridBagConstraints gridBagConstraints;
|
||||
|
||||
refreshLabel = new javax.swing.JLabel();
|
||||
refreshButton = new javax.swing.JButton();
|
||||
closeButton = new javax.swing.JButton();
|
||||
|
||||
setBackground(new java.awt.Color(0, 51, 51));
|
||||
setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
refreshLabel.setForeground(new java.awt.Color(255, 255, 255));
|
||||
refreshLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/warning16.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(refreshLabel, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.refreshLabel.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(15, 10, 15, 10);
|
||||
add(refreshLabel, gridBagConstraints);
|
||||
|
||||
refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.refreshButton.text")); // NOI18N
|
||||
refreshButton.setMargin(new java.awt.Insets(2, 5, 2, 5));
|
||||
add(refreshButton, new java.awt.GridBagConstraints());
|
||||
|
||||
closeButton.setBackground(new java.awt.Color(0, 0, 0));
|
||||
closeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/cross-script.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.closeButton.text")); // NOI18N
|
||||
closeButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
|
||||
closeButton.setOpaque(false);
|
||||
closeButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
closeButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
|
||||
add(closeButton, gridBagConstraints);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
|
||||
|
||||
}//GEN-LAST:event_closeButtonActionPerformed
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton closeButton;
|
||||
private javax.swing.JButton refreshButton;
|
||||
private javax.swing.JLabel refreshLabel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
@ -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
|
85
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java
Executable file
85
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java
Executable file
@ -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 Waypoint {
|
||||
|
||||
/**
|
||||
* 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, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image from the given artifact.
|
||||
*
|
||||
* @param artifact BlackboardArtifact for waypoint
|
||||
*
|
||||
* @return AbstractFile image for this waypoint or null if one is not
|
||||
* available
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
private static AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException {
|
||||
AbstractFile abstractFile = null;
|
||||
BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID());
|
||||
if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) {
|
||||
try {
|
||||
abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
|
||||
} catch (TskCoreException ex) {
|
||||
throw new GeoLocationDataException(String.format("Unable to getAbstractFileByID for artifactID: %d", artifact.getArtifactID()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
return abstractFile;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.geolocation.datamodel;
|
||||
|
||||
/**
|
||||
* An exception class for the Geolocation dateModel;
|
||||
*
|
||||
*/
|
||||
public class GeoLocationDataException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Create exception containing the error message
|
||||
*
|
||||
* @param msg the message
|
||||
*/
|
||||
public GeoLocationDataException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create exception containing the error message and cause exception
|
||||
*
|
||||
* @param msg the message
|
||||
* @param ex cause exception
|
||||
*/
|
||||
public GeoLocationDataException(String msg, Exception ex) {
|
||||
super(msg, ex);
|
||||
}
|
||||
}
|
82
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java
Executable file
82
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java
Executable file
@ -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 Waypoint {
|
||||
|
||||
/**
|
||||
* 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, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
190
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java
Executable file
190
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java
Executable file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
*
|
||||
* 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 routes may have
|
||||
* more that two points.
|
||||
*
|
||||
*/
|
||||
public final class Route {
|
||||
private final List<Waypoint> points;
|
||||
private final Long timestamp;
|
||||
|
||||
// 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 = Waypoint.getAttributesFromArtifactAsMap(artifact);
|
||||
points.add(getRouteStartPoint(artifact, attributeMap));
|
||||
points.add(getRouteEndPoint(artifact, attributeMap));
|
||||
|
||||
BlackboardAttribute 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 "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();
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route start point.
|
||||
*
|
||||
* @param attributeMap Map of artifact attributes for this waypoint.
|
||||
*
|
||||
* An exception will be thrown if longitude or latitude is null.
|
||||
*
|
||||
* @return Start waypoint
|
||||
*
|
||||
* @throws GeoLocationDataException.
|
||||
*/
|
||||
@Messages({
|
||||
"Route_Start_Label=Start"
|
||||
})
|
||||
private Waypoint getRouteStartPoint(BlackboardArtifact artifact, 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);
|
||||
BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
|
||||
BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
|
||||
|
||||
if (latitude != null && longitude != null) {
|
||||
return new Waypoint(artifact,
|
||||
Bundle.Route_Start_Label(),
|
||||
pointTimestamp != null ? pointTimestamp.getValueLong() : null,
|
||||
latitude.getValueDouble(),
|
||||
longitude.getValueDouble(),
|
||||
altitude != null ? altitude.getValueDouble() : null,
|
||||
null, attributeMap, this);
|
||||
} else {
|
||||
throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route End point.
|
||||
*
|
||||
* An exception will be thrown if longitude or latitude is null.
|
||||
*
|
||||
* @param attributeMap Map of artifact attributes for this waypoint
|
||||
*
|
||||
* @return The end waypoint
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
@Messages({
|
||||
"Route_End_Label=End"
|
||||
})
|
||||
private Waypoint getRouteEndPoint(BlackboardArtifact artifact, 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);
|
||||
BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
|
||||
BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
|
||||
|
||||
if (latitude != null && longitude != null) {
|
||||
return new Waypoint(artifact,
|
||||
Bundle.Route_End_Label(),
|
||||
pointTimestamp != null ? pointTimestamp.getValueLong() : null,
|
||||
latitude.getValueDouble(),
|
||||
longitude.getValueDouble(),
|
||||
altitude != null ? altitude.getValueDouble() : null,
|
||||
null, attributeMap, this);
|
||||
} else {
|
||||
throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude");
|
||||
}
|
||||
}
|
||||
}
|
77
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java
Executable file
77
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java
Executable file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 Waypoint.
|
||||
*/
|
||||
final class SearchWaypoint extends Waypoint {
|
||||
|
||||
/**
|
||||
* 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, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java
Executable file
82
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java
Executable file
@ -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 Waypoint {
|
||||
/**
|
||||
* 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, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
514
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java
Executable file
514
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java
Executable file
@ -0,0 +1,514 @@
|
||||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Representation of a Waypoint created from a BlackboardArtifact.
|
||||
*
|
||||
*/
|
||||
public class 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;
|
||||
final private Route route;
|
||||
|
||||
// This list is not expected to change after construction. The
|
||||
// constructor will take care of making an unmodifiable List
|
||||
final private List<Waypoint.Property> immutablePropertiesList;
|
||||
|
||||
/**
|
||||
* This is a list of attributes that are already being handled by the
|
||||
* by getter functions.
|
||||
*/
|
||||
static private 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,};
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Waypoint.class.getName());
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
Waypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
|
||||
this(artifact,
|
||||
getAttributesFromArtifactAsMap(artifact));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that initializes 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.
|
||||
*/
|
||||
Waypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap, Route route) 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;
|
||||
this.route = null;
|
||||
|
||||
immutablePropertiesList = Collections.unmodifiableList(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 Waypoint(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, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BlackboardArtifact that this waypoint represents.
|
||||
*
|
||||
* @return BlackboardArtifact for this waypoint.
|
||||
*/
|
||||
public BlackboardArtifact getArtifact() {
|
||||
return artifact;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label for this point object.
|
||||
*
|
||||
* @return String label for the point or null if none was set
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latitude for this point.
|
||||
*
|
||||
* @return Returns the latitude for the point
|
||||
*/
|
||||
public Double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longitude for this point.
|
||||
*
|
||||
* @return Returns the longitude for the point
|
||||
*/
|
||||
public Double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the altitude for this point.
|
||||
*
|
||||
* @return Returns the altitude for the point or null if none was set
|
||||
*/
|
||||
public Double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image for this waypoint.
|
||||
*
|
||||
* @return AbstractFile image or null if one was not set
|
||||
*/
|
||||
public AbstractFile getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public List<Waypoint.Property> getOtherProperties() {
|
||||
return immutablePropertiesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the route that this waypoint is apart of .
|
||||
*
|
||||
* @return The waypoint route or null if the waypoint is not apart of a route.
|
||||
*/
|
||||
public Route getRoute() {
|
||||
return route;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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 {
|
||||
Waypoint 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 public 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 {
|
||||
Waypoint 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
|
||||
*/
|
||||
public 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 {
|
||||
Waypoint 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
|
||||
*/
|
||||
public 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 {
|
||||
Waypoint 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
|
||||
*/
|
||||
public 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 {
|
||||
Waypoint point = new Waypoint(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 public List<Waypoint.Property> createGeolocationProperties(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
|
||||
List<Waypoint.Property> list = new ArrayList<>();
|
||||
|
||||
Set<BlackboardAttribute.ATTRIBUTE_TYPE> keys = new HashSet<>(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.
|
||||
*/
|
||||
public static final 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.
|
||||
*/
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
BIN
Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png
Executable file
BIN
Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 864 B |
BIN
Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png
Executable file
BIN
Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 623 B |
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.modules.hashdatabase;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.AbstractAction;
|
||||
@ -42,7 +43,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
/**
|
||||
* Instances of this Action allow users to content to a hash database.
|
||||
*/
|
||||
final class AddContentToHashDbAction extends AbstractAction implements Presenter.Popup {
|
||||
public final class AddContentToHashDbAction extends AbstractAction implements Presenter.Popup {
|
||||
|
||||
private static AddContentToHashDbAction instance;
|
||||
|
||||
@ -66,12 +67,16 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
"AddContentToHashDbAction.singleSelectionNameNoMD5");
|
||||
private final static String MULTI_SELECTION_NAME_NO_MD5 = NbBundle.getMessage(AddContentToHashDbAction.class,
|
||||
"AddContentToHashDbAction.multipleSelectionNameNoMD5");
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* AddContentToHashDbAction is a singleton to support multi-selection of
|
||||
* nodes, since org.openide.nodes.NodeOp.findActions(Node[] nodes) will only
|
||||
* pick up an Action from a node if every node in the nodes array returns a
|
||||
* reference to the same action object from Node.getActions(boolean).
|
||||
*
|
||||
* @return The AddContentToHashDbAction instance which is used to provide
|
||||
* the menu for adding content to a HashDb.
|
||||
*/
|
||||
public static synchronized AddContentToHashDbAction getInstance() {
|
||||
if (null == instance) {
|
||||
@ -83,6 +88,19 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
private AddContentToHashDbAction() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the menu for adding the specified collection of Files to a HashDb.
|
||||
*
|
||||
* @param selectedFiles The collection of AbstractFiles the menu actions
|
||||
* will be applied to.
|
||||
*
|
||||
* @return The menu which will allow users to add the specified files to a
|
||||
* HashDb.
|
||||
*/
|
||||
public JMenuItem getMenuForFiles(Collection<AbstractFile> selectedFiles) {
|
||||
return new AddContentToHashDbMenu(selectedFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JMenuItem getPopupPresenter() {
|
||||
return new AddContentToHashDbMenu();
|
||||
@ -96,10 +114,14 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
// action.
|
||||
private final class AddContentToHashDbMenu extends JMenu {
|
||||
|
||||
AddContentToHashDbMenu() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Construct an AddContentToHashDbMenu object using the specified
|
||||
* collection of files as the files to be added to a HashDb.
|
||||
*/
|
||||
AddContentToHashDbMenu(Collection<AbstractFile> selectedFiles) {
|
||||
super(SINGLE_SELECTION_NAME);
|
||||
// Get any AbstractFile objects from the lookup of the currently focused top component.
|
||||
final Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
|
||||
int numberOfFilesSelected = selectedFiles.size();
|
||||
|
||||
// Disable the menu if file ingest is in progress.
|
||||
@ -109,16 +131,13 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
SINGLE_SELECTION_NAME_DURING_INGEST,
|
||||
MULTI_SELECTION_NAME_DURING_INGEST);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFiles.isEmpty()) {
|
||||
} else if (numberOfFilesSelected == 0) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
} else {
|
||||
setTextBasedOnNumberOfSelections(numberOfFilesSelected,
|
||||
SINGLE_SELECTION_NAME,
|
||||
MULTI_SELECTION_NAME);
|
||||
}
|
||||
setTextBasedOnNumberOfSelections(numberOfFilesSelected,
|
||||
SINGLE_SELECTION_NAME,
|
||||
MULTI_SELECTION_NAME);
|
||||
|
||||
// Disable the menu if md5 have not been computed or if the file size
|
||||
// is empty. Display the appropriate reason to the user.
|
||||
@ -137,7 +156,26 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
return;
|
||||
}
|
||||
}
|
||||
addExistingHashDatabases(selectedFiles);
|
||||
// Add a "New Hash Set..." menu item. Selecting this item invokes a
|
||||
// a hash database creation dialog and adds the selected files to the
|
||||
// the new database.
|
||||
addSeparator();
|
||||
JMenuItem newHashSetItem = new JMenuItem(NbBundle.getMessage(this.getClass(),
|
||||
"AddContentToHashDbAction.ContentMenu.createDbItem"));
|
||||
newHashSetItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
HashDb hashDb = new HashDbCreateDatabaseDialog().getHashDatabase();
|
||||
if (null != hashDb) {
|
||||
addFilesToHashSet(selectedFiles, hashDb);
|
||||
}
|
||||
}
|
||||
});
|
||||
add(newHashSetItem);
|
||||
}
|
||||
|
||||
private void addExistingHashDatabases(Collection<AbstractFile> selectedFiles) {
|
||||
// Get the current set of updateable hash databases and add each
|
||||
// one to the menu as a separate menu item. Selecting a hash database
|
||||
// adds the selected files to the selected database.
|
||||
@ -159,23 +197,16 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
empty.setEnabled(false);
|
||||
add(empty);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddContentToHashDbMenu object using the currently
|
||||
* selected files as the files to be added to a HashDb.
|
||||
*/
|
||||
AddContentToHashDbMenu() {
|
||||
// Get any AbstractFile objects from the lookup of the currently focused top component.
|
||||
this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)));
|
||||
|
||||
// Add a "New Hash Set..." menu item. Selecting this item invokes a
|
||||
// a hash database creation dialog and adds the selected files to the
|
||||
// the new database.
|
||||
addSeparator();
|
||||
JMenuItem newHashSetItem = new JMenuItem(NbBundle.getMessage(this.getClass(),
|
||||
"AddContentToHashDbAction.ContentMenu.createDbItem"));
|
||||
newHashSetItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
HashDb hashDb = new HashDbCreateDatabaseDialog().getHashDatabase();
|
||||
if (null != hashDb) {
|
||||
addFilesToHashSet(selectedFiles, hashDb);
|
||||
}
|
||||
}
|
||||
});
|
||||
add(newHashSetItem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -25,6 +25,7 @@ import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.openide.DialogDescriptor;
|
||||
import org.openide.DialogDisplayer;
|
||||
import org.openide.util.HelpCtx;
|
||||
@ -65,7 +66,7 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
|
||||
this.title = title;
|
||||
progressPanel = new ProgressPanel();
|
||||
progressPanel.setIndeterminate(true);
|
||||
this.buttonLabels = buttonLabels;
|
||||
this.buttonLabels = SerializationUtils.clone(buttonLabels);
|
||||
this.focusedButtonLabel = focusedButtonLabel;
|
||||
this.buttonListener = buttonListener;
|
||||
}
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -768,6 +768,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
oldAttr.getValueLong()));
|
||||
break;
|
||||
case STRING:
|
||||
case JSON:
|
||||
newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
|
||||
oldAttr.getValueString()));
|
||||
break;
|
||||
|
@ -50,6 +50,8 @@ final class CustomArtifactType {
|
||||
private static final String BYTES_ATTR_DISPLAY_NAME = "Custom Bytes";
|
||||
private static final String STRING_ATTR_TYPE_NAME = "CUSTOM_STRING_ATTRIBUTE";
|
||||
private static final String STRING_ATTR_DISPLAY_NAME = "Custom String";
|
||||
private static final String JSON_ATTR_TYPE_NAME = "CUSTOM_JSON_ATTRIBUTE";
|
||||
private static final String JSON_ATTR_DISPLAY_NAME = "Custom Json";
|
||||
private static BlackboardArtifact.Type artifactType;
|
||||
private static BlackboardAttribute.Type intAttrType;
|
||||
private static BlackboardAttribute.Type doubleAttrType;
|
||||
@ -57,6 +59,7 @@ final class CustomArtifactType {
|
||||
private static BlackboardAttribute.Type dateTimeAttrType;
|
||||
private static BlackboardAttribute.Type bytesAttrType;
|
||||
private static BlackboardAttribute.Type stringAttrType;
|
||||
private static BlackboardAttribute.Type jsonAttrType;
|
||||
|
||||
/**
|
||||
* Adds the custom artifact type, with its associated custom attribute
|
||||
@ -73,6 +76,7 @@ final class CustomArtifactType {
|
||||
dateTimeAttrType = blackboard.getOrAddAttributeType(DATETIME_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME, DATETIME_ATTR_DISPLAY_NAME);
|
||||
bytesAttrType = blackboard.getOrAddAttributeType(BYTES_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE, BYTES_ATTR_DISPLAY_NAME);
|
||||
stringAttrType = blackboard.getOrAddAttributeType(STRING_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, STRING_ATTR_DISPLAY_NAME);
|
||||
jsonAttrType = blackboard.getOrAddAttributeType(JSON_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON, JSON_ATTR_DISPLAY_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,6 +97,7 @@ final class CustomArtifactType {
|
||||
attributes.add(new BlackboardAttribute(dateTimeAttrType, MODULE_NAME, 60L));
|
||||
attributes.add(new BlackboardAttribute(bytesAttrType, MODULE_NAME, DatatypeConverter.parseHexBinary("ABCD")));
|
||||
attributes.add(new BlackboardAttribute(stringAttrType, MODULE_NAME, "Zero"));
|
||||
attributes.add(new BlackboardAttribute(jsonAttrType, MODULE_NAME, "{\"fruit\": \"Apple\",\"size\": \"Large\",\"color\": \"Red\"}"));
|
||||
artifact.addAttributes(attributes);
|
||||
|
||||
/*
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 Basis Technology Corp.
|
||||
* Copyright 2018-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -44,7 +44,7 @@ final class SqliteTextExtractor implements TextExtractor {
|
||||
private static final Logger logger = Logger.getLogger(SqliteTextExtractor.class.getName());
|
||||
private final AbstractFile file;
|
||||
|
||||
public SqliteTextExtractor(AbstractFile file) {
|
||||
SqliteTextExtractor(AbstractFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
/**
|
||||
@ -101,7 +101,7 @@ final class SqliteTextExtractor implements TextExtractor {
|
||||
*
|
||||
* @param file Sqlite file
|
||||
*/
|
||||
public SQLiteStreamReader(AbstractFile file) {
|
||||
SQLiteStreamReader(AbstractFile file) {
|
||||
this.file = file;
|
||||
reader = new SQLiteTableReader.Builder(file)
|
||||
.forAllColumnNames(getColumnNameStrategy())
|
||||
@ -140,7 +140,7 @@ final class SqliteTextExtractor implements TextExtractor {
|
||||
}
|
||||
|
||||
fillBuffer(objectStr);
|
||||
columnIndex = columnIndex % totalColumns;
|
||||
columnIndex %= totalColumns;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -171,7 +171,7 @@ final class SqliteTextExtractor implements TextExtractor {
|
||||
fillBuffer(columnName + ((columnIndex == totalColumns) ? "\n" : " "));
|
||||
|
||||
//Reset the columnCount to 0 for next table read
|
||||
columnIndex = columnIndex % totalColumns;
|
||||
columnIndex %= totalColumns;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -204,7 +204,7 @@ final class SqliteTextExtractor implements TextExtractor {
|
||||
*/
|
||||
@Override
|
||||
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||
buf = cbuf;
|
||||
buf = cbuf; //needs to be the same memory address and not a copy of the contents since we are filling it in
|
||||
|
||||
bufIndex = off;
|
||||
|
||||
@ -277,12 +277,12 @@ final class SqliteTextExtractor implements TextExtractor {
|
||||
private final String entity;
|
||||
private Integer pointer;
|
||||
|
||||
public ExcessBytes(String entity, Integer pointer) {
|
||||
ExcessBytes(String entity, Integer pointer) {
|
||||
this.entity = entity;
|
||||
this.pointer = pointer;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
boolean isFinished() {
|
||||
return entity.length() == pointer;
|
||||
}
|
||||
|
||||
@ -296,7 +296,7 @@ final class SqliteTextExtractor implements TextExtractor {
|
||||
*
|
||||
* @return number of characters read into the buffer
|
||||
*/
|
||||
public int read(char[] buf, int off, int len) {
|
||||
int read(char[] buf, int off, int len) {
|
||||
for (int i = off; i < len; i++) {
|
||||
if (isFinished()) {
|
||||
return i - off;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-2018 Basis Technology Corp.
|
||||
* Copyright 2015-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -178,6 +178,20 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
|
||||
this.artifactConditions = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the file size conditions of this rule.
|
||||
*/
|
||||
void sortFileSizeConditions() {
|
||||
this.fileSizeConditions.sort(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the artifact conditions of this rule.
|
||||
*/
|
||||
void sortArtifactConditions() {
|
||||
this.artifactConditions.sort(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the rule.
|
||||
*
|
||||
@ -336,13 +350,13 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
|
||||
if (!Objects.equals(this.fileTypeCondition, that.getFileMIMETypeCondition())) {
|
||||
return false;
|
||||
}
|
||||
this.fileSizeConditions.sort(null);
|
||||
that.fileSizeConditions.sort(null);
|
||||
this.sortFileSizeConditions();
|
||||
that.sortFileSizeConditions();
|
||||
if (!this.fileSizeConditions.equals(that.getFileSizeConditions())) {
|
||||
return false;
|
||||
}
|
||||
this.artifactConditions.sort(null);
|
||||
that.artifactConditions.sort(null);
|
||||
this.sortArtifactConditions();
|
||||
that.sortArtifactConditions();
|
||||
return this.artifactConditions.equals(that.getArtifactConditions());
|
||||
}
|
||||
|
||||
@ -376,8 +390,8 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
|
||||
List<Long> evaluate(long dataSourceId) throws ExportRulesException {
|
||||
try {
|
||||
SleuthkitCase db = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId))) {
|
||||
ResultSet resultSet = queryResult.getResultSet();
|
||||
try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId));
|
||||
ResultSet resultSet = queryResult.getResultSet();) {
|
||||
List<Long> fileIds = new ArrayList<>();
|
||||
while (resultSet.next()) {
|
||||
fileIds.add(resultSet.getLong("obj_id"));
|
||||
@ -889,7 +903,7 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
|
||||
* @return The value, may be null.
|
||||
*/
|
||||
byte[] getByteValue() {
|
||||
return this.byteValue;
|
||||
return this.byteValue.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -908,7 +922,7 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
|
||||
* @return The value, may be null.
|
||||
*/
|
||||
String getStringRepresentationOfValue() {
|
||||
String valueText = "";
|
||||
String valueText;
|
||||
switch (this.attributeValueType) {
|
||||
case BYTE:
|
||||
valueText = new String(Hex.encodeHex(getByteValue()));
|
||||
@ -1069,20 +1083,20 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
|
||||
}
|
||||
SleuthkitCase caseDb = currentCase.getSleuthkitCase();
|
||||
BlackboardArtifact.Type artifactType;
|
||||
BlackboardAttribute.Type attributeType;
|
||||
try {
|
||||
artifactType = caseDb.getArtifactType(artifactTypeName);
|
||||
} catch (TskCoreException ex) {
|
||||
throw new ExportRulesException(String.format("The specified %s artifact type does not exist in case database for %s", artifactTypeName, currentCase.getCaseDirectory()), ex);
|
||||
}
|
||||
BlackboardAttribute.Type attributeType;
|
||||
try {
|
||||
attributeType = caseDb.getAttributeType(attributeTypeName);
|
||||
} catch (TskCoreException ex) {
|
||||
throw new ExportRulesException(String.format("The specified %s attribute type does not exist in case database for %s", attributeTypeName, currentCase.getCaseDirectory()), ex);
|
||||
}
|
||||
|
||||
|
||||
String clause = String.format("files.obj_id = arts%d.obj_id AND arts%d.artifact_type_id = %d AND attrs%d.artifact_id = arts%d.artifact_id AND attrs%d.attribute_type_id = %d AND ",
|
||||
index, index, artifactType.getTypeID(), index, index, index, attributeType.getTypeID());
|
||||
index, index, artifactType.getTypeID(), index, index, index, attributeType.getTypeID());
|
||||
switch (this.attributeValueType) {
|
||||
case INTEGER:
|
||||
clause += String.format("attrs%d.value_int32 %s %d", index, this.op.getSymbol(), this.intValue);
|
||||
@ -1100,7 +1114,9 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
|
||||
clause += String.format("attrs%d.value_byte %s decode('%s', 'hex')", index, this.op.getSymbol(), new String(Hex.encodeHex(getByteValue())));
|
||||
break;
|
||||
case DATETIME:
|
||||
clause += String.format("attrs%d.value_int64 %s '%s'", index, this.op.getSymbol(), this.dateTimeValue.getMillis()/1000);
|
||||
clause += String.format("attrs%d.value_int64 %s '%s'", index, this.op.getSymbol(), this.dateTimeValue.getMillis() / 1000);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return clause;
|
||||
|
@ -244,6 +244,8 @@ public final class FileExporterSettingsPanel extends JPanel {
|
||||
comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING.getLabel());
|
||||
comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME.getLabel());
|
||||
comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE.getLabel());
|
||||
comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON.getLabel());
|
||||
|
||||
comboBoxValueType.addItem(UNSET);
|
||||
|
||||
load();
|
||||
|
@ -45,6 +45,7 @@ from org.sleuthkit.datamodel import Account
|
||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CallMediaType
|
||||
|
||||
import json
|
||||
import traceback
|
||||
@ -181,15 +182,242 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
||||
contactsDb.close()
|
||||
|
||||
|
||||
|
||||
## Adds a recipient to given list
|
||||
def addRecipientToList(self, user_key, recipientList):
|
||||
## Extracts recipeint id from 'user_key' column and adds recipient to given list,
|
||||
## if the recipeint id is not the same as sender id
|
||||
def addRecipientToList(self, user_key, senderId, recipientList):
|
||||
if user_key is not None:
|
||||
recipientId = user_key.replace('FACEBOOK:', '')
|
||||
recipientList.append(recipientId)
|
||||
recipientId = user_key.replace('FACEBOOK:', '')
|
||||
if recipientId != senderId:
|
||||
recipientList.append(recipientId)
|
||||
|
||||
|
||||
## Extracts sender id from the json in 'sender' column.
|
||||
def getSenderIdFromJson(self, senderJsonStr):
|
||||
senderId = None;
|
||||
if senderJsonStr is not None:
|
||||
sender_dict = json.loads(senderJsonStr)
|
||||
senderId = sender_dict['user_key']
|
||||
senderId = senderId.replace('FACEBOOK:', '')
|
||||
|
||||
## Analyze messages
|
||||
def analyzeMessages(self, dataSource, fileManager, context):
|
||||
return senderId
|
||||
|
||||
## determines communication direction by comparing senderId with selfAccountId
|
||||
def deduceDirectionFromSenderId(self, senderId):
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
if senderId is not None:
|
||||
if senderId == self.selfAccountId:
|
||||
direction = CommunicationDirection.OUTGOING
|
||||
else:
|
||||
direction = CommunicationDirection.INCOMING
|
||||
return direction
|
||||
|
||||
## Analyzes messages
|
||||
def analyzeMessages(self, threadsDb, threadsDBHelper):
|
||||
try:
|
||||
|
||||
## Messages are found in the messages table.
|
||||
## This query filters messages by msg_type to only get actual user created conversation messages (msg_type 0).
|
||||
## The participant ids can be found in the thread_participants table.
|
||||
## Participant names are found in thread_users table.
|
||||
## Joining these tables produces multiple rows per message, one row for each recipient.
|
||||
## The result set is processed to collect the multiple recipients for a given message.
|
||||
sqlString = """
|
||||
SELECT msg_id, text, sender, timestamp_ms, msg_type, messages.thread_key as thread_key,
|
||||
snippet, thread_participants.user_key as user_key, thread_users.name as name
|
||||
FROM messages
|
||||
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
||||
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
||||
WHERE msg_type = 0
|
||||
ORDER BY msg_id
|
||||
"""
|
||||
|
||||
messagesResultSet = threadsDb.runQuery(sqlString)
|
||||
if messagesResultSet is not None:
|
||||
oldMsgId = None
|
||||
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
fromId = None
|
||||
recipientIdsList = None
|
||||
timeStamp = -1
|
||||
msgText = ""
|
||||
threadId = ""
|
||||
|
||||
while messagesResultSet.next():
|
||||
msgId = messagesResultSet.getString("msg_id")
|
||||
|
||||
# new msg begins when msgId changes
|
||||
if msgId != oldMsgId:
|
||||
# Create message artifact with collected attributes
|
||||
if oldMsgId is not None:
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
|
||||
oldMsgId = msgId
|
||||
|
||||
# New message - collect all attributes
|
||||
recipientIdsList = []
|
||||
|
||||
## get sender id by parsing JSON in sender column
|
||||
fromId = self.getSenderIdFromJson(messagesResultSet.getString("sender"))
|
||||
direction = self.deduceDirectionFromSenderId(fromId)
|
||||
|
||||
# Get recipient and add to list
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), fromId,
|
||||
recipientIdsList)
|
||||
|
||||
timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000
|
||||
|
||||
# Get msg text
|
||||
# Sometimes there may not be an explict msg text,
|
||||
# but an app generated snippet instead
|
||||
msgText = messagesResultSet.getString("text")
|
||||
if not msgText:
|
||||
msgText = messagesResultSet.getString("snippet")
|
||||
|
||||
# TBD: get attachment
|
||||
|
||||
threadId = messagesResultSet.getString("thread_key")
|
||||
|
||||
else: # same msgId as last, just collect recipient from current row
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), fromId,
|
||||
recipientIdsList)
|
||||
|
||||
|
||||
# at the end of the loop, add last message
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for FB Messenger messages.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
self._logger.log(Level.SEVERE, "Failed to add FB Messenger message artifacts.", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
self._logger.log(Level.WARNING, "Failed to post artifacts.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
## Analyzes call logs
|
||||
def analyzeCallLogs(self, threadsDb, threadsDBHelper):
|
||||
try:
|
||||
|
||||
## Call logs are found in the messages table.
|
||||
## msg_type indicates type of call:
|
||||
## 9: one to one calls
|
||||
## 203: group call
|
||||
## 1-to-1 calls only have a call_ended record.
|
||||
## group calls have a call_started_record as well as call_ended recorded, with *different* message ids.
|
||||
## all the data we need can be found in the call_ended record.
|
||||
|
||||
sqlString = """
|
||||
SELECT msg_id, text, sender, timestamp_ms, msg_type, admin_text_thread_rtc_event,
|
||||
generic_admin_message_extensible_data,
|
||||
messages.thread_key as thread_key,
|
||||
thread_participants.user_key as user_key,
|
||||
thread_users.name as name
|
||||
FROM messages
|
||||
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
||||
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
||||
WHERE msg_type = 9 OR (msg_type = 203 AND admin_text_thread_rtc_event = 'group_call_ended')
|
||||
ORDER BY msg_id
|
||||
"""
|
||||
|
||||
messagesResultSet = threadsDb.runQuery(sqlString)
|
||||
if messagesResultSet is not None:
|
||||
oldMsgId = None
|
||||
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
callerId = None
|
||||
calleeIdsList = None
|
||||
startTimeStamp = -1
|
||||
endTimeStamp = -1
|
||||
duration = 0
|
||||
mediaType = CallMediaType.AUDIO
|
||||
|
||||
while messagesResultSet.next():
|
||||
msgId = messagesResultSet.getString("msg_id")
|
||||
|
||||
# new call begins when msgId changes
|
||||
if msgId != oldMsgId:
|
||||
# Create call log artifact with collected attributes
|
||||
if oldMsgId is not None:
|
||||
messageArtifact = threadsDBHelper.addCalllog(
|
||||
direction,
|
||||
callerId,
|
||||
calleeIdsList,
|
||||
startTimeStamp,
|
||||
endTimeStamp,
|
||||
mediaType )
|
||||
|
||||
oldMsgId = msgId
|
||||
|
||||
# New message - collect all attributes
|
||||
calleeIdsList = []
|
||||
|
||||
## get caller id by parsing JSON in sender column
|
||||
callerId = self.getSenderIdFromJson(messagesResultSet.getString("sender"))
|
||||
direction = self.deduceDirectionFromSenderId(callerId)
|
||||
|
||||
# Get recipient and add to list
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), callerId,
|
||||
calleeIdsList)
|
||||
|
||||
# the timestamp from call ended msg is used as end timestamp
|
||||
endTimeStamp = messagesResultSet.getLong("timestamp_ms") / 1000
|
||||
|
||||
# parse the generic_admin_message_extensible_data JSON to extract the duration and video fields
|
||||
adminDataJsonStr = messagesResultSet.getString("generic_admin_message_extensible_data")
|
||||
if adminDataJsonStr is not None:
|
||||
adminData_dict = json.loads(adminDataJsonStr)
|
||||
duration = adminData_dict['call_duration'] # call duration in seconds
|
||||
isVideo = adminData_dict['video']
|
||||
if isVideo:
|
||||
mediaType = CallMediaType.VIDEO
|
||||
|
||||
startTimeStamp = endTimeStamp - duration
|
||||
|
||||
else: # same msgId as last, just collect callee from current row
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), callerId,
|
||||
calleeIdsList)
|
||||
|
||||
# at the end of the loop, add last message
|
||||
messageArtifact = threadsDBHelper.addCalllog(
|
||||
direction,
|
||||
callerId,
|
||||
calleeIdsList,
|
||||
startTimeStamp,
|
||||
endTimeStamp,
|
||||
mediaType )
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for FB Messenger call logs.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
self._logger.log(Level.SEVERE, "Failed to add FB Messenger call log artifacts.", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
self._logger.log(Level.WARNING, "Failed to post FB Messenger call log artifacts.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
|
||||
## Analyze messages and call log threads
|
||||
def analyzeMessagesAndCallLogs(self, dataSource, fileManager, context):
|
||||
threadsDbs = AppSQLiteDB.findAppDatabases(dataSource, "threads_db2", True, self._FB_MESSENGER_PACKAGE_NAME)
|
||||
for threadsDb in threadsDbs:
|
||||
try:
|
||||
@ -202,113 +430,12 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
||||
self._MODULE_NAME, threadsDb.getDBFile(),
|
||||
Account.Type.FACEBOOK)
|
||||
|
||||
## Messages are found in the messages table.
|
||||
## This query filters messages by msg_type to only get actual user created conversation messages (msg_type 0).
|
||||
## The participant ids can be found in the thread_participants table.
|
||||
## Participant names are found in thread_users table.
|
||||
## Joining these tables produces multiple rows per message, one row for each recipient.
|
||||
## The result set is processed to collect the multiple recipients for a given message.
|
||||
sqlString = """
|
||||
SELECT msg_id, text, sender, timestamp_ms, msg_type, messages.thread_key as thread_key,
|
||||
snippet, thread_participants.user_key as user_key, thread_users.name as name
|
||||
FROM messages
|
||||
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
||||
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
||||
WHERE msg_type = 0
|
||||
ORDER BY msg_id
|
||||
"""
|
||||
|
||||
messagesResultSet = threadsDb.runQuery(sqlString)
|
||||
if messagesResultSet is not None:
|
||||
oldMsgId = None
|
||||
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
fromId = None
|
||||
recipientIdsList = None
|
||||
timeStamp = -1
|
||||
msgText = ""
|
||||
threadId = ""
|
||||
|
||||
while messagesResultSet.next():
|
||||
msgId = messagesResultSet.getString("msg_id")
|
||||
|
||||
# new msg begins when msgId changes
|
||||
if msgId != oldMsgId:
|
||||
# Create message artifact with collected attributes
|
||||
if oldMsgId is not None:
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
|
||||
oldMsgId = msgId
|
||||
|
||||
# New message - collect all attributes
|
||||
recipientIdsList = []
|
||||
|
||||
## get sender id by parsing JSON in sender column
|
||||
senderJsonStr = messagesResultSet.getString("sender")
|
||||
if senderJsonStr is not None:
|
||||
sender_dict = json.loads(senderJsonStr)
|
||||
senderId = sender_dict['user_key']
|
||||
senderId = senderId.replace('FACEBOOK:', '')
|
||||
senderName = sender_dict['name']
|
||||
fromId = senderId
|
||||
if senderId == self.selfAccountId:
|
||||
direction = CommunicationDirection.OUTGOING
|
||||
else:
|
||||
direction = CommunicationDirection.INCOMING
|
||||
|
||||
|
||||
# Get recipient and add to list
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"),
|
||||
recipientIdsList)
|
||||
|
||||
timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000
|
||||
|
||||
# Get msg text
|
||||
# Sometimes there may not be an explict msg text,
|
||||
# but an app generated snippet instead
|
||||
msgText = messagesResultSet.getString("text")
|
||||
if not msgText:
|
||||
msgText = messagesResultSet.getString("snippet")
|
||||
|
||||
# TBD: get attachment
|
||||
|
||||
threadId = messagesResultSet.getString("thread_key")
|
||||
|
||||
else: # same msgId as last, just collect recipient from current row
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"),
|
||||
recipientIdsList)
|
||||
|
||||
|
||||
# at the end of the loop, add last message
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
self.analyzeMessages(threadsDb, threadsDBHelper)
|
||||
self.analyzeCallLogs(threadsDb, threadsDBHelper)
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for FB Messenger messages.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
self._logger.log(Level.SEVERE, "Failed to add FB Messenger message artifacts.", ex)
|
||||
self._logger.log(Level.SEVERE, "Failed to to create CommunicationArtifactsHelper for FB Messenger.", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
self._logger.log(Level.WARNING, "Failed to post artifacts.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
finally:
|
||||
threadsDb.close()
|
||||
|
||||
@ -321,6 +448,6 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
||||
return
|
||||
|
||||
self.analyzeContacts(dataSource, fileManager, context)
|
||||
self.analyzeMessages(dataSource, fileManager, context)
|
||||
self.analyzeMessagesAndCallLogs(dataSource, fileManager, context)
|
||||
|
||||
|
||||
|
@ -43,10 +43,14 @@ from org.sleuthkit.datamodel import Content
|
||||
from org.sleuthkit.datamodel import TskCoreException
|
||||
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||
from org.sleuthkit.datamodel import Account
|
||||
from org.sleuthkit.datamodel.blackboardutils import FileAttachment
|
||||
from org.sleuthkit.datamodel.blackboardutils import URLAttachment
|
||||
from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
|
||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||
|
||||
import json
|
||||
import traceback
|
||||
import general
|
||||
|
||||
@ -66,6 +70,8 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer):
|
||||
-- A messages table which stores the message details
|
||||
--- sender/receiver buid, timestamp, message_type (1: incoming, 0: outgoing), message_read...
|
||||
--- 'imdata' column stores a json structure with all the message details, including attachments
|
||||
---- attachment file path may be specified in local_path or original_path. Original path, if available is a better candidate.
|
||||
---- For sent files, files seem to get uploaded to IMO Servers. There is no URL available in the imdata though.
|
||||
|
||||
"""
|
||||
|
||||
@ -156,7 +162,7 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer):
|
||||
msgReadStatus = MessageReadStatus.UNKNOWN
|
||||
|
||||
timeStamp = messagesResultSet.getLong("timestamp") / 1000000000
|
||||
|
||||
msgBody = messagesResultSet.getString("last_message")
|
||||
|
||||
messageArtifact = friendsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
@ -166,12 +172,34 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer):
|
||||
timeStamp,
|
||||
msgReadStatus,
|
||||
"", # subject
|
||||
messagesResultSet.getString("last_message"),
|
||||
msgBody,
|
||||
"") # thread id
|
||||
|
||||
# TBD: parse the imdata JSON structure to figure out if there is an attachment.
|
||||
# If one exists, add the attachment as a derived file and a child of the message artifact.
|
||||
|
||||
|
||||
# Parse the imdata JSON structure to check if there is an attachment.
|
||||
# If one exists, create an attachment and add to the message.
|
||||
fileAttachments = ArrayList()
|
||||
urlAttachments = ArrayList()
|
||||
|
||||
imdataJsonStr = messagesResultSet.getString("imdata")
|
||||
if imdataJsonStr is not None:
|
||||
imdata_dict = json.loads(imdataJsonStr)
|
||||
|
||||
# set to none if the key doesn't exist in the dict
|
||||
attachmentOriginalPath = imdata_dict.get('original_path', None)
|
||||
attachmentLocalPath = imdata_dict.get('local_path', None)
|
||||
if attachmentOriginalPath:
|
||||
attachmentPath = attachmentOriginalPath
|
||||
else:
|
||||
attachmentPath = attachmentLocalPath
|
||||
|
||||
if attachmentPath:
|
||||
# Create a file attachment with given path
|
||||
fileAttachment = FileAttachment(current_case.getSleuthkitCase(), friendsDb.getDBFile().getDataSource(), attachmentPath)
|
||||
fileAttachments.add(fileAttachment)
|
||||
|
||||
msgAttachments = MessageAttachments(fileAttachments, [])
|
||||
attachmentArtifact = friendsDBHelper.addAttachments(messageArtifact, msgAttachments)
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for IMO friends", ex)
|
||||
|
@ -236,28 +236,23 @@ class LineCallLogsParser(TskCallLogsParser):
|
||||
def __init__(self, calllog_db):
|
||||
super(LineCallLogsParser, self).__init__(calllog_db.runQuery(
|
||||
"""
|
||||
SELECT Substr(CH.call_type, -1) AS direction,
|
||||
CH.start_time AS start_time,
|
||||
CH.end_time AS end_time,
|
||||
contacts_list_with_groups.members AS group_members,
|
||||
contacts_list_with_groups.member_names AS names,
|
||||
CH.caller_mid,
|
||||
CH.voip_type AS call_type,
|
||||
CH.voip_gc_media_type AS group_call_type
|
||||
SELECT Substr(calls.call_type, -1) AS direction,
|
||||
calls.start_time AS start_time,
|
||||
calls.end_time AS end_time,
|
||||
contact_book_w_groups.members AS group_members,
|
||||
calls.caller_mid,
|
||||
calls.voip_type AS call_type,
|
||||
calls.voip_gc_media_type AS group_call_type
|
||||
FROM (SELECT id,
|
||||
Group_concat(M.m_id) AS members,
|
||||
Group_concat(Replace(C.server_name, ",", "")) AS member_names
|
||||
Group_concat(M.m_id) AS members
|
||||
FROM membership AS M
|
||||
JOIN naver.contacts AS C
|
||||
ON M.m_id = C.m_id
|
||||
GROUP BY id
|
||||
UNION
|
||||
SELECT m_id,
|
||||
NULL,
|
||||
server_name
|
||||
FROM naver.contacts) AS contacts_list_with_groups
|
||||
JOIN call_history AS CH
|
||||
ON CH.caller_mid = contacts_list_with_groups.id
|
||||
NULL
|
||||
FROM naver.contacts) AS contact_book_w_groups
|
||||
JOIN call_history AS calls
|
||||
ON calls.caller_mid = contact_book_w_groups.id
|
||||
"""
|
||||
)
|
||||
)
|
||||
@ -355,43 +350,25 @@ class LineMessagesParser(TskMessagesParser):
|
||||
def __init__(self, message_db):
|
||||
super(LineMessagesParser, self).__init__(message_db.runQuery(
|
||||
"""
|
||||
SELECT contact_list_with_groups.name,
|
||||
contact_list_with_groups.id,
|
||||
contact_list_with_groups.members,
|
||||
contact_list_with_groups.member_names,
|
||||
CH.from_mid,
|
||||
C.server_name AS from_name,
|
||||
CH.content,
|
||||
CH.created_time,
|
||||
CH.attachement_type,
|
||||
CH.attachement_local_uri,
|
||||
CH.status
|
||||
FROM (SELECT G.name,
|
||||
group_members.id,
|
||||
group_members.members,
|
||||
group_members.member_names
|
||||
FROM (SELECT id,
|
||||
group_concat(M.m_id) AS members,
|
||||
group_concat(replace(C.server_name,
|
||||
",",
|
||||
"")) as member_names
|
||||
FROM membership AS M
|
||||
JOIN contacts as C
|
||||
ON M.m_id = C.m_id
|
||||
GROUP BY id) AS group_members
|
||||
JOIN groups AS G
|
||||
ON G.id = group_members.id
|
||||
UNION
|
||||
SELECT server_name,
|
||||
m_id,
|
||||
NULL,
|
||||
NULL
|
||||
FROM contacts) AS contact_list_with_groups
|
||||
JOIN chat_history AS CH
|
||||
ON CH.chat_id = contact_list_with_groups.id
|
||||
LEFT JOIN contacts as C
|
||||
ON C.m_id = CH.from_mid
|
||||
WHERE attachement_type != 6
|
||||
SELECT contact_book_w_groups.id,
|
||||
contact_book_w_groups.members,
|
||||
messages.from_mid,
|
||||
messages.content,
|
||||
messages.created_time,
|
||||
messages.attachement_type,
|
||||
messages.attachement_local_uri,
|
||||
messages.status
|
||||
FROM (SELECT id,
|
||||
Group_concat(M.m_id) AS members
|
||||
FROM membership AS M
|
||||
GROUP BY id
|
||||
UNION
|
||||
SELECT m_id,
|
||||
NULL
|
||||
FROM contacts) AS contact_book_w_groups
|
||||
JOIN chat_history AS messages
|
||||
ON messages.chat_id = contact_book_w_groups.id
|
||||
WHERE attachement_type != 6
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
@ -76,11 +76,8 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer):
|
||||
as they would be excluded in the join. Since the chatItem table stores both the
|
||||
group id or skype_id in one column, an implementation decision was made to union
|
||||
the person and particiapnt table together so that all rows are matched in one join
|
||||
with chatItem. This result is consistently labeled contact_list_with_groups in the
|
||||
with chatItem. This result is consistently labeled contact_book_w_groups in the
|
||||
following queries.
|
||||
- In order to keep the formatting of the name consistent throughout each query,
|
||||
a _format_user_name() function was created to encapsulate the CASE statement
|
||||
that was being shared across them. Refer to the method for more details.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@ -93,7 +90,12 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer):
|
||||
account_query_result = skype_db.runQuery(
|
||||
"""
|
||||
SELECT entry_id,
|
||||
"""+_format_user_name()+""" AS name
|
||||
CASE
|
||||
WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id
|
||||
WHEN first_name is NULL THEN replace(last_name, ",", "")
|
||||
WHEN last_name is NULL THEN replace(first_name, ",", "")
|
||||
ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "")
|
||||
END AS name
|
||||
FROM user
|
||||
"""
|
||||
)
|
||||
@ -251,14 +253,6 @@ class SkypeCallLogsParser(TskCallLogsParser):
|
||||
|
||||
def __init__(self, calllog_db):
|
||||
"""
|
||||
Big picture:
|
||||
The query below creates a contacts_list_with_groups table, which
|
||||
represents the recipient info. A chatItem record holds ids for
|
||||
both the recipient and sender. The first join onto chatItem fills
|
||||
in the blanks for the recipients. The second join back onto person
|
||||
handles the sender info. The result is a table with all of the
|
||||
communication details.
|
||||
|
||||
Implementation details:
|
||||
- message_type w/ value 3 appeared to be the call type, regardless
|
||||
of if it was audio or video.
|
||||
@ -266,37 +260,23 @@ class SkypeCallLogsParser(TskCallLogsParser):
|
||||
"""
|
||||
super(SkypeCallLogsParser, self).__init__(calllog_db.runQuery(
|
||||
"""
|
||||
SELECT contacts_list_with_groups.conversation_id,
|
||||
contacts_list_with_groups.participant_ids,
|
||||
contacts_list_with_groups.participants,
|
||||
time,
|
||||
duration,
|
||||
is_sender_me,
|
||||
person_id as sender_id,
|
||||
sender_name.name as sender_name
|
||||
SELECT contact_book_w_groups.conversation_id,
|
||||
contact_book_w_groups.participant_ids,
|
||||
messages.time,
|
||||
messages.duration,
|
||||
messages.is_sender_me,
|
||||
messages.person_id AS sender_id
|
||||
FROM (SELECT conversation_id,
|
||||
Group_concat(person_id) AS participant_ids,
|
||||
Group_concat("""+_format_user_name()+""") AS participants
|
||||
FROM particiapnt AS PART
|
||||
JOIN person AS P
|
||||
ON PART.person_id = P.entry_id
|
||||
Group_concat(person_id) AS participant_ids
|
||||
FROM particiapnt
|
||||
GROUP BY conversation_id
|
||||
UNION
|
||||
SELECT entry_id,
|
||||
NULL,
|
||||
"""+_format_user_name()+""" AS participant
|
||||
FROM person) AS contacts_list_with_groups
|
||||
JOIN chatitem AS C
|
||||
ON C.conversation_link = contacts_list_with_groups.conversation_id
|
||||
JOIN (SELECT entry_id as id,
|
||||
"""+_format_user_name()+""" AS name
|
||||
FROM person
|
||||
UNION
|
||||
SELECT entry_id as id,
|
||||
"""+_format_user_name()+""" AS name
|
||||
FROM user) AS sender_name
|
||||
ON sender_name.id = C.person_id
|
||||
WHERE message_type == 3
|
||||
SELECT entry_id AS conversation_id,
|
||||
NULL
|
||||
FROM person) AS contact_book_w_groups
|
||||
join chatitem AS messages
|
||||
ON messages.conversation_link = contact_book_w_groups.conversation_id
|
||||
WHERE message_type == 3
|
||||
"""
|
||||
)
|
||||
)
|
||||
@ -347,7 +327,12 @@ class SkypeContactsParser(TskContactsParser):
|
||||
super(SkypeContactsParser, self).__init__(contact_db.runQuery(
|
||||
"""
|
||||
SELECT entry_id,
|
||||
"""+_format_user_name()+""" AS name
|
||||
CASE
|
||||
WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id
|
||||
WHEN first_name is NULL THEN replace(last_name, ",", "")
|
||||
WHEN last_name is NULL THEN replace(first_name, ",", "")
|
||||
ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "")
|
||||
END AS name
|
||||
FROM person
|
||||
"""
|
||||
)
|
||||
@ -379,39 +364,25 @@ class SkypeMessagesParser(TskMessagesParser):
|
||||
"""
|
||||
super(SkypeMessagesParser, self).__init__(message_db.runQuery(
|
||||
"""
|
||||
SELECT contacts_list_with_groups.conversation_id,
|
||||
contacts_list_with_groups.participant_ids,
|
||||
contacts_list_with_groups.participants,
|
||||
time,
|
||||
content,
|
||||
device_gallery_path,
|
||||
is_sender_me,
|
||||
person_id as sender_id,
|
||||
sender_name.name AS sender_name
|
||||
FROM (SELECT conversation_id,
|
||||
Group_concat(person_id) AS participant_ids,
|
||||
Group_concat("""+_format_user_name()+""") AS participants
|
||||
FROM particiapnt AS PART
|
||||
JOIN person AS P
|
||||
ON PART.person_id = P.entry_id
|
||||
GROUP BY conversation_id
|
||||
UNION
|
||||
SELECT entry_id as conversation_id,
|
||||
NULL,
|
||||
"""+_format_user_name()+""" AS participant
|
||||
FROM person) AS contacts_list_with_groups
|
||||
JOIN chatitem AS C
|
||||
ON C.conversation_link = contacts_list_with_groups.conversation_id
|
||||
JOIN (SELECT entry_id as id,
|
||||
"""+_format_user_name()+""" AS name
|
||||
FROM person
|
||||
UNION
|
||||
SELECT entry_id as id,
|
||||
"""+_format_user_name()+""" AS name
|
||||
FROM user) AS sender_name
|
||||
ON sender_name.id = C.person_id
|
||||
SELECT contact_book_w_groups.conversation_id,
|
||||
contact_book_w_groups.participant_ids,
|
||||
messages.time,
|
||||
messages.content,
|
||||
messages.device_gallery_path,
|
||||
messages.is_sender_me,
|
||||
messages.person_id as sender_id
|
||||
FROM (SELECT conversation_id,
|
||||
Group_concat(person_id) AS participant_ids
|
||||
FROM particiapnt
|
||||
GROUP BY conversation_id
|
||||
UNION
|
||||
SELECT entry_id as conversation_id,
|
||||
NULL
|
||||
FROM person) AS contact_book_w_groups
|
||||
JOIN chatitem AS messages
|
||||
ON messages.conversation_link = contact_book_w_groups.conversation_id
|
||||
WHERE message_type != 3
|
||||
"""
|
||||
"""
|
||||
)
|
||||
)
|
||||
self._SKYPE_MESSAGE_TYPE = "Skype Message"
|
||||
@ -469,25 +440,3 @@ class SkypeMessagesParser(TskMessagesParser):
|
||||
if group_ids is not None:
|
||||
return self.result_set.getString("conversation_id")
|
||||
return super(SkypeMessagesParser, self).get_thread_id()
|
||||
|
||||
def _format_user_name():
|
||||
"""
|
||||
This CASE SQL statement is used in many queries to
|
||||
format the names of users. For a user, there is a first_name
|
||||
column and a last_name column. Some of these columns can be null
|
||||
and our goal is to produce the cleanest data possible. In the event
|
||||
that both the first and last name columns are null, we return the skype_id
|
||||
which is stored in the database as 'entry_id'. Commas are removed from the name
|
||||
so that we can concatenate names into a comma seperate list for group chats.
|
||||
"""
|
||||
|
||||
return """
|
||||
CASE
|
||||
WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id
|
||||
WHEN first_name is NULL THEN replace(last_name, ",", "")
|
||||
WHEN last_name is NULL THEN replace(first_name, ",", "")
|
||||
ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "")
|
||||
END
|
||||
"""
|
||||
|
||||
|
||||
|
@ -290,53 +290,50 @@ class TextNowMessagesParser(TskMessagesParser):
|
||||
"""
|
||||
super(TextNowMessagesParser, self).__init__(message_db.runQuery(
|
||||
"""
|
||||
|
||||
SELECT CASE
|
||||
WHEN message_direction == 2 THEN ""
|
||||
WHEN to_addresses IS NULL THEN M.contact_value
|
||||
ELSE contact_name
|
||||
end from_address,
|
||||
CASE
|
||||
WHEN message_direction == 1 THEN ""
|
||||
WHEN to_addresses IS NULL THEN M.contact_value
|
||||
ELSE to_addresses
|
||||
end to_address,
|
||||
message_direction,
|
||||
message_text,
|
||||
M.READ,
|
||||
M.date,
|
||||
M.attach,
|
||||
thread_id
|
||||
FROM (SELECT group_info.contact_value,
|
||||
group_info.to_addresses,
|
||||
G.contact_value AS thread_id
|
||||
FROM (SELECT GM.contact_value,
|
||||
Group_concat(GM.member_contact_value) AS to_addresses
|
||||
FROM group_members AS GM
|
||||
GROUP BY GM.contact_value) AS group_info
|
||||
JOIN groups AS G
|
||||
ON G.contact_value = group_info.contact_value
|
||||
UNION
|
||||
SELECT c.contact_value,
|
||||
NULL,
|
||||
"-1"
|
||||
FROM contacts AS c) AS to_from_map
|
||||
JOIN messages AS M
|
||||
ON M.contact_value = to_from_map.contact_value
|
||||
WHERE message_type NOT IN ( 102, 100 )
|
||||
SELECT CASE
|
||||
WHEN messages.message_direction == 2 THEN NULL
|
||||
WHEN contact_book_w_groups.to_addresses IS NULL THEN
|
||||
messages.contact_value
|
||||
END from_address,
|
||||
CASE
|
||||
WHEN messages.message_direction == 1 THEN NULL
|
||||
WHEN contact_book_w_groups.to_addresses IS NULL THEN
|
||||
messages.contact_value
|
||||
ELSE contact_book_w_groups.to_addresses
|
||||
END to_address,
|
||||
messages.message_direction,
|
||||
messages.message_text,
|
||||
messages.READ,
|
||||
messages.DATE,
|
||||
messages.attach,
|
||||
thread_id
|
||||
FROM (SELECT GM.contact_value,
|
||||
Group_concat(GM.member_contact_value) AS to_addresses,
|
||||
G.contact_value AS thread_id
|
||||
FROM group_members AS GM
|
||||
join GROUPS AS G
|
||||
ON G.contact_value = GM.contact_value
|
||||
GROUP BY GM.contact_value
|
||||
UNION
|
||||
SELECT contact_value,
|
||||
NULL,
|
||||
NULL
|
||||
FROM contacts) AS contact_book_w_groups
|
||||
join messages
|
||||
ON messages.contact_value = contact_book_w_groups.contact_value
|
||||
WHERE message_type NOT IN ( 102, 100 )
|
||||
"""
|
||||
)
|
||||
)
|
||||
self._TEXTNOW_MESSAGE_TYPE = "TextNow Message"
|
||||
self._INCOMING_MESSAGE_TYPE = 1
|
||||
self._OUTGOING_MESSAGE_TYPE = 2
|
||||
self._UNKNOWN_THREAD_ID = "-1"
|
||||
|
||||
def get_message_type(self):
|
||||
return self._TEXTNOW_MESSAGE_TYPE
|
||||
|
||||
def get_phone_number_from(self):
|
||||
if self.result_set.getString("from_address") == "":
|
||||
if self.result_set.getString("from_address") is None:
|
||||
return super(TextNowMessagesParser, self).get_phone_number_from()
|
||||
return self.result_set.getString("from_address")
|
||||
|
||||
@ -347,10 +344,9 @@ class TextNowMessagesParser(TskMessagesParser):
|
||||
return self.OUTGOING
|
||||
|
||||
def get_phone_number_to(self):
|
||||
if self.result_set.getString("to_address") == "":
|
||||
if self.result_set.getString("to_address") is None:
|
||||
return super(TextNowMessagesParser, self).get_phone_number_to()
|
||||
recipients = self.result_set.getString("to_address").split(",")
|
||||
return recipients
|
||||
return self.result_set.getString("to_address").split(",")
|
||||
|
||||
def get_message_date_time(self):
|
||||
#convert ms to s
|
||||
@ -359,7 +355,7 @@ class TextNowMessagesParser(TskMessagesParser):
|
||||
def get_message_read_status(self):
|
||||
read = self.result_set.getBoolean("read")
|
||||
if self.get_message_direction() == self.INCOMING:
|
||||
if read == True:
|
||||
if read:
|
||||
return self.READ
|
||||
return self.UNREAD
|
||||
|
||||
@ -375,6 +371,6 @@ class TextNowMessagesParser(TskMessagesParser):
|
||||
|
||||
def get_thread_id(self):
|
||||
thread_id = self.result_set.getString("thread_id")
|
||||
if thread_id == self._UNKNOWN_THREAD_ID:
|
||||
if thread_id is None:
|
||||
return super(TextNowMessagesParser, self).get_thread_id()
|
||||
return thread_id
|
||||
|
@ -433,31 +433,28 @@ class WhatsAppMessagesParser(TskMessagesParser):
|
||||
def __init__(self, message_db):
|
||||
super(WhatsAppMessagesParser, self).__init__(message_db.runQuery(
|
||||
"""
|
||||
SELECT M.key_remote_jid AS id,
|
||||
contact_info.recipients,
|
||||
key_from_me AS direction,
|
||||
CASE
|
||||
WHEN M.data IS NULL THEN ""
|
||||
ELSE M.data
|
||||
END AS content,
|
||||
M.timestamp AS send_timestamp,
|
||||
M.received_timestamp,
|
||||
M.remote_resource AS group_sender,
|
||||
M.media_url As attachment
|
||||
FROM (SELECT jid,
|
||||
recipients
|
||||
FROM wadb.wa_contacts AS WC
|
||||
LEFT JOIN (SELECT gjid,
|
||||
group_concat(CASE
|
||||
WHEN jid == "" THEN NULL
|
||||
ELSE jid
|
||||
END) AS recipients
|
||||
FROM group_participants
|
||||
GROUP BY gjid) AS group_map
|
||||
ON WC.jid = group_map.gjid
|
||||
GROUP BY jid) AS contact_info
|
||||
JOIN messages AS M
|
||||
ON M.key_remote_jid = contact_info.jid
|
||||
SELECT messages.key_remote_jid AS id,
|
||||
contact_book_w_groups.recipients,
|
||||
key_from_me AS direction,
|
||||
messages.data AS content,
|
||||
messages.timestamp AS send_timestamp,
|
||||
messages.received_timestamp,
|
||||
messages.remote_resource AS group_sender,
|
||||
messages.media_url AS attachment
|
||||
FROM (SELECT jid,
|
||||
recipients
|
||||
FROM wadb.wa_contacts AS contacts
|
||||
left join (SELECT gjid,
|
||||
Group_concat(CASE
|
||||
WHEN jid == "" THEN NULL
|
||||
ELSE jid
|
||||
END) AS recipients
|
||||
FROM group_participants
|
||||
GROUP BY gjid) AS groups
|
||||
ON contacts.jid = groups.gjid
|
||||
GROUP BY jid) AS contact_book_w_groups
|
||||
join messages
|
||||
ON messages.key_remote_jid = contact_book_w_groups.jid
|
||||
"""
|
||||
)
|
||||
)
|
||||
@ -503,6 +500,8 @@ class WhatsAppMessagesParser(TskMessagesParser):
|
||||
|
||||
def get_message_text(self):
|
||||
message = self.result_set.getString("content")
|
||||
if message is None:
|
||||
message = super(WhatsAppMessagesParser, self).get_message_text()
|
||||
attachment = self.result_set.getString("attachment")
|
||||
if attachment is not None:
|
||||
return general.appendAttachmentList(message, [attachment])
|
||||
|
@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie;
|
||||
*/
|
||||
final class BinaryCookieReader implements Iterable<Cookie> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(BinaryCookieReader.class.getName());
|
||||
private static final int MAGIC_SIZE = 4;
|
||||
private static final int SIZEOF_INT_BYTES = 4;
|
||||
private static final int PAGE_HEADER_VALUE = 256;
|
||||
@ -53,8 +54,6 @@ final class BinaryCookieReader implements Iterable<Cookie> {
|
||||
private final int[] pageSizeArray;
|
||||
private final File cookieFile;
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(BinaryCookieReader.class.getName());
|
||||
|
||||
/**
|
||||
* The binary cookie reader encapsulates all the knowledge of how to read
|
||||
* the mac .binarycookie files into one class.
|
||||
@ -62,7 +61,7 @@ final class BinaryCookieReader implements Iterable<Cookie> {
|
||||
*/
|
||||
private BinaryCookieReader(File cookieFile, int[] pageSizeArray) {
|
||||
this.cookieFile = cookieFile;
|
||||
this.pageSizeArray = pageSizeArray;
|
||||
this.pageSizeArray = pageSizeArray.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +70,9 @@ final class BinaryCookieReader implements Iterable<Cookie> {
|
||||
* open.
|
||||
*
|
||||
* @param cookieFile binarycookie file
|
||||
*
|
||||
* @return An instance of the reader
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
@ -229,6 +230,7 @@ final class BinaryCookieReader implements Iterable<Cookie> {
|
||||
* correct format by checking for the header value of 0x0100.
|
||||
*
|
||||
* @param page byte array representing a cookie page
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
CookiePage(byte[] page) throws IOException {
|
||||
@ -414,7 +416,8 @@ final class BinaryCookieReader implements Iterable<Cookie> {
|
||||
* offset ending at the first null terminator found.
|
||||
*
|
||||
* @param byteArray Array of bytes
|
||||
* @param offset starting offset in the array
|
||||
* @param offset starting offset in the array
|
||||
*
|
||||
* @return String with bytes converted to ascii
|
||||
*/
|
||||
private String decodeString(byte[] byteArray, int offset) {
|
||||
|
@ -14,21 +14,21 @@ The following need to be done at least once. They do not need to be repeated for
|
||||
1. Download a 64 bit Java 8 JRE for your specific platform from https://www.azul.com/downloads/zulu-community
|
||||
2. Install the JRE. e.g. % sudo apt install ./zulu8.40.0.25-ca-jre8.0.222-linux_amd64.deb
|
||||
3. Download a 64 bit Java 8 JavaFX for your specific platform from the same location.
|
||||
- Note that you may need to select "Older Zulu versions" for FX to become available in the "Java Package" dropdown.
|
||||
4. Extract the contents of the JavaFX archive into the folder where the JRE was installed.
|
||||
e.g. % sudo tar xzf ~/Downloads/zulu8.40.0.25-ca-fx-jre8.0.222-linux_x64.tar.gz -C /usr/lib/jvm/zre-8-amd64 --strip-components=1
|
||||
|
||||
NOTE: You may need to log out and back in again after setting JAVA_HOME before the Autopsy
|
||||
unix_setup.sh script can see the value.
|
||||
|
||||
-- OS X: Any Java 8 version of OpenJDK/OpenJFX distribution should suffice.
|
||||
-- OS X: Any Java 8 version of OpenJDK/OpenJFX distribution should suffice. The following instructions use the AdoptOpenJDK distribution.
|
||||
1. Install a 64 bit Java 8 JRE.
|
||||
% brew tap adoptopenjdk/openjdk
|
||||
% brew cask install adoptopenjdk8
|
||||
2. Download a 64 bit Java 8 JavaFX for macOS from https://www.azul.com/downloads/zulu-community
|
||||
3. Extract the contents of the JavaFX archive into the folder where the JRE was installed.
|
||||
e.g. % sudo tar xf ~/Downloads/zulu8.40.0.25-ca-fx-jre8.0.222-macosx_x64.tar.gz -C /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home--strip-components=1
|
||||
4. Confirm Java 8 is being found by running 'java -version'
|
||||
5. Set JAVA_HOME environment variable to location of JRE installation.
|
||||
2. Set JAVA_HOME environment variable to location of JRE installation.
|
||||
e.g. add the following to ~/.bashrc
|
||||
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
|
||||
3. Confirm your version of Java by running
|
||||
% java -version
|
||||
|
||||
* Install The Sleuth Kit Java Bindings *
|
||||
|
||||
|
@ -6,6 +6,8 @@ Overview
|
||||
|
||||
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/"> developer's guide</a> will help you develop your own Autopsy modules.
|
||||
|
||||
Note: For those users running Autopsy on Mac devices, the functionality available through the "Tools" -> "Options" dialog as described in this documentation can be accessed through the system menu bar under "Preferences" or through the Cmd + , (command-comma) shortcut.
|
||||
|
||||
Help Topics
|
||||
-------
|
||||
The following topics are available here:
|
||||
|
@ -772,6 +772,9 @@ INPUT = main.dox \
|
||||
regressionTesting.dox \
|
||||
native_libs.dox \
|
||||
modDevPython.dox \
|
||||
modFileIngestTutorial.dox \
|
||||
modDSIngestTutorial.dox \
|
||||
modReportModuleTutorial.dox \
|
||||
debugTsk.dox \
|
||||
../../Core/src \
|
||||
../../CoreLibs/src \
|
||||
@ -867,7 +870,7 @@ EXAMPLE_RECURSIVE = NO
|
||||
# that contain images that are to be included in the documentation (see the
|
||||
# \image command).
|
||||
|
||||
IMAGE_PATH = .
|
||||
IMAGE_PATH = images/
|
||||
|
||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||
|
@ -1,5 +1,5 @@
|
||||
<hr/>
|
||||
<p><i>Copyright © 2012-2018 Basis Technology. Generated on: $date<br/>
|
||||
<p><i>Copyright © 2012-2019 Basis Technology. Generated on: $date<br/>
|
||||
This work is licensed under a
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/">Creative Commons Attribution-Share Alike 3.0 United States License</a>.
|
||||
</i></p>
|
||||
|
BIN
docs/doxygen/images/bigAndRoundFiles.png
Normal file
BIN
docs/doxygen/images/bigAndRoundFiles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
docs/doxygen/images/demoScript_folder.png
Normal file
BIN
docs/doxygen/images/demoScript_folder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user