diff --git a/.gitignore b/.gitignore
index b89812646f..9ae14c5cac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,7 +65,6 @@ genfiles.properties
!/Testing/nbproject/project.properties
*~
/netbeans-plat
-/docs/doxygen/doxygen_docs
/docs/doxygen-user/user-docs
/jdiff-javadocs/*
/jdiff-logs/*
diff --git a/BUILDING.txt b/BUILDING.txt
index 7361735adf..c08806503c 100644
--- a/BUILDING.txt
+++ b/BUILDING.txt
@@ -1,8 +1,8 @@
- Last Updated: Sept 17, 2014
+ Last Updated: 27 April 2015
This file outlines what it takes to build Autopsy from source.
-Note that it currently only works out of the box on Windows. We
+Note that it currently only works out-of-the-box on Windows. We
are working on getting the process working under non-Windows systems.
It generally works, but needs some custom mangling to find the
correct C libraries.
@@ -11,7 +11,10 @@ correct C libraries.
STEPS:
1) Get Java Setup
-1a) Download and install JDK version 1.8. For the current version of JavaFX that we use, you'll need 1.8.0_40 or greater. You can now use 32-bit or 64-bit, but special work is needed to get The Sleuth Kit to compile as 64-bit. So, 32-bit is easier.
+1a) Download and install JDK version 1.8. For the current version of JavaFX
+that we use, you'll need 1.8.0_40 or greater. You can now use 32-bit or 64-bit,
+but special work is needed to get The Sleuth Kit to compile as 64-bit. So,
+32-bit is easier, but if you intend to use PostgreSQL, choose 64-bit.
Autopsy has been used and tested with Oracle JavaSE and the included JavaFX support
(http://www.oracle.com/technetwork/java/javase/downloads/index.html).
@@ -28,33 +31,25 @@ but it is a recommended IDE to use for development of Autopsy modules.
need to set JRE_HOME_32 to the root 32-bit JRE directory and/or JRE_HOME_64
to the root 64-bit JRE directory.
-1e) (optional) For some Autopsy features to be functional, you need to add java executable to the system PATH.
-
+1e) (optional) For some Autopsy features to be functional, you need to add the
+ java executable to the system PATH.
2) Get Sleuth Kit Setup
-2a) Download and build the release version of Libewf2 (20130119 or
-later). All you need is the dll file. Note that you will get a
-launching error if you use libewf 1.
-- http://sourceforge.net/projects/libewf/
-If you want to build the 64-bit version of The Sleuth Kit, download
-our 64-bit version of libewf:
-- https://github.com/sleuthkit/libewf_64bit
-
-2b) Set LIBEWF_HOME environment variable to root directory of LIBEWF
-
-2c) Download and build a Release version of Sleuth Kit (TSK) 4.0. You
-need to build the tsk_jni project. You can use a released version or
-download the latest from github:
+2a) Download and build a Release version of Sleuth Kit (TSK) 4.0. See
+ win32\BUILDING.txt in the TSK package for more information. You need to
+ build the tsk_jni project. Select the Release_PostgreSQL x64 target. You can
+ use a released version or download the latest from github:
- git://github.com/sleuthkit/sleuthkit.git
-2d) Build the TSK JAR file by typing 'ant' in bindings/java in the
-TSK source code folder from a command line. You can also add the
-code to a NetBeans project and build it from there.
+2b) Build the TSK JAR file by typing 'ant PostgreSQL' in bindings/java in the
+ TSK source code folder from a command line. Note it is case sensitive. You
+ can also add the code to a NetBeans project and build it from there,
+ selecting the PostgreSQL target.
-2e) Set TSK_HOME environment variable to the root directory of TSK
+2c) Set TSK_HOME environment variable to the root directory of TSK
-2f) On Non-Windows systems, you will need to do a 'make install'
+2d) On Non-Windows systems, you will need to do a 'make install'
from the TSK root directory to install the libraries and such in
the needed places (i.e. '/usr/local').
@@ -85,12 +80,12 @@ and by submitting pull requests to the main Autopsy repository.
5) Compile Autopsy
5a) using Netbeans IDE:
- Start NetBeans IDE and open the Autopsy project.
-- Choose to build the Autopsy project / module. It is the highest
-level project that will then cause the other modules to be compiled.
+- Choose to build the Autopsy project / module. It is the highest level project
+ that will cause the other modules to be compiled.
5b) without Netbeans IDE (requires JDK and ant >= 1.7.1):
- from root directory of Autopsy source execute:
-ant build
+ant
(to build Autopsy)
ant run
(to run Autopsy)
@@ -101,14 +96,13 @@ Here are some notes to shed some light on what is going on during
the build process.
- The Sleuth Kit Java datamodel JAR file has native JNI libraries
-that are copied into it. These JNI libraries have dependencies on
-libewf and zlib. On non-Windows platforms, the JNI library also has
-a dependency on libtsk (on Windows, it is compiled into libtsk_jni).
+that are copied into it. These JNI libraries have dependencies on
+libewf, zlib, libpq, libintl-8, libeay32, and ssleay32 DLL files. On non-Windows
+platforms, the JNI library also has a dependency on libtsk (on Windows,
+it is compiled into libtsk_jni).
-- NetBeans uses ant to build Autopsy. The build target copies the
-TSK datamodel JAR file into the project. If you want to use the
-debug version of the TSK dll, then there is a different ant target
-in the TSK datamodel to copy the debug versions of the dlls.
+- NetBeans uses ant to build Autopsy. The build target copies the
+TSK datamodel JAR file into the project.
- On a Windows system, the compile-time ant target copies the
dependency libraries into the Autopsy code structure so that they can
diff --git a/Core/build.xml b/Core/build.xml
index 128d0c1286..c0936f07e8 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -24,6 +24,10 @@
+
+
+
+
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 9cdbb08f9e..86ed71097f 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -1,19 +1,21 @@
+file.reference.activemq-all-5.11.1.jar=release/modules/ext/activemq-all-5.11.1.jar
file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar
-file.reference.jython.jar-1=release/modules/ext/jython.jar
file.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1.jar
+file.reference.postgresql-9.4-1201-jdbc41.jar=release/modules/ext/postgresql-9.4-1201-jdbc41.jar
file.reference.opencv-248.jar=release/modules/ext/opencv-248.jar
file.reference.Rejistry-1.0-SNAPSHOT.jar=release/modules/ext/Rejistry-1.0-SNAPSHOT.jar
file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar
file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
-file.reference.tika-core-1.2.jar=release/modules/ext/tika-core-1.2.jar
+file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar
file.reference.Tsk_DataModel.jar=release/modules/ext/Tsk_DataModel.jar
file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
+javadoc.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip
license.file=../LICENSE-2.0.txt
nbm.homepage=http://www.sleuthkit.org/
nbm.module.author=Brian Carrier
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index c8089bda6c..78bb7e701e 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -184,9 +184,11 @@
org.sleuthkit.autopsy.actionsorg.sleuthkit.autopsy.casemodule
+ org.sleuthkit.autopsy.casemodule.eventsorg.sleuthkit.autopsy.casemodule.servicesorg.sleuthkit.autopsy.contentviewersorg.sleuthkit.autopsy.core
+ org.sleuthkit.autopsy.core.eventsorg.sleuthkit.autopsy.corecomponentinterfacesorg.sleuthkit.autopsy.corecomponentsorg.sleuthkit.autopsy.coreutils
@@ -203,14 +205,26 @@
org.sleuthkit.autopsy.reportorg.sleuthkit.datamodel
-
- ext/xmpcore-5.1.2.jar
- release/modules/ext/xmpcore-5.1.2.jar
- ext/jdom-2.0.5.jarrelease/modules/ext/jdom-2.0.5.jar
+
+ ext/postgresql-9.4-1201-jdbc41.jar
+ release/modules/ext/postgresql-9.4-1201-jdbc41.jar
+
+
+ ext/mchange-commons-java-0.2.9.jar
+ release/modules/ext/mchange-commons-java-0.2.9.jar
+
+
+ ext/c3p0-0.9.5.jar
+ release/modules/ext/c3p0-0.9.5.jar
+
+
+ ext/xmpcore-5.1.2.jar
+ release/modules/ext/xmpcore-5.1.2.jar
+ ext/StixLib.jarrelease/modules/ext/StixLib.jar
@@ -228,20 +242,32 @@
release/modules/ext/Rejistry-1.0-SNAPSHOT.jar
- ext/sevenzipjbinding.jar
- release/modules/ext/sevenzipjbinding.jar
+ ext/activemq-all-5.11.1.jar
+ release/modules/ext/activemq-all-5.11.1.jar
+
+
+ ext/Rejistry-1.0-SNAPSHOT.jar
+ release/modules/ext/Rejistry-1.0-SNAPSHOT.jarext/jython-standalone-2.7.0.jarrelease/modules/ext/jython-standalone-2.7.0.jar
+
+ ext/sevenzipjbinding.jar
+ release/modules/ext/sevenzipjbinding.jar
+ ext/sevenzipjbinding-AllPlatforms.jarrelease/modules/ext/sevenzipjbinding-AllPlatforms.jar
- ext/tika-core-1.2.jar
- release/modules/ext/tika-core-1.2.jar
+ ext/tika-core-1.5.jar
+ release/modules/ext/tika-core-1.5.jar
+
+
+ ext/metadata-extractor-2.8.1.jar
+ release/modules/ext/metadata-extractor-2.8.1.jarext/metadata-extractor-2.8.1.jar
diff --git a/Core/release/modules/ext/activemq-all-5.11.1.jar b/Core/release/modules/ext/activemq-all-5.11.1.jar
new file mode 100644
index 0000000000..59eff437fa
Binary files /dev/null and b/Core/release/modules/ext/activemq-all-5.11.1.jar differ
diff --git a/Core/release/modules/ext/metadata-extractor-2.8.1-src.zip b/Core/release/modules/ext/metadata-extractor-2.8.1-src.zip
new file mode 100755
index 0000000000..38c449d420
Binary files /dev/null and b/Core/release/modules/ext/metadata-extractor-2.8.1-src.zip differ
diff --git a/Core/release/modules/ext/tika-core-1.2.jar b/Core/release/modules/ext/tika-core-1.2.jar
deleted file mode 100755
index e1491ab5f2..0000000000
Binary files a/Core/release/modules/ext/tika-core-1.2.jar and /dev/null differ
diff --git a/Core/release/modules/ext/tika-core-1.5.jar b/Core/release/modules/ext/tika-core-1.5.jar
new file mode 100644
index 0000000000..d8e45e3f28
Binary files /dev/null and b/Core/release/modules/ext/tika-core-1.5.jar differ
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java
index 73121b13dc..2111f3a554 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013 Basis Technology Corp.
+ * Copyright 2013-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.actions;
import java.util.Collection;
import java.util.logging.Level;
import javax.swing.JOptionPane;
-
+import javax.swing.SwingUtilities;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -63,20 +63,25 @@ public class AddBlackboardArtifactTagAction extends AddTagAction {
@Override
protected void addTag(TagName tagName, String comment) {
- Collection extends BlackboardArtifact> selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
- for (BlackboardArtifact artifact : selectedArtifacts) {
- try {
- Case.getCurrentCase().getServices().getTagsManager().addBlackboardArtifactTag(artifact, tagName, comment);
- } catch (TskCoreException ex) {
- Logger.getLogger(AddBlackboardArtifactTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); //NON-NLS
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "AddBlackboardArtifactTagAction.unableToTag.msg",
- artifact.getDisplayName()),
- NbBundle.getMessage(this.getClass(),
- "AddBlackboardArtifactTagAction.taggingErr"),
- JOptionPane.ERROR_MESSAGE);
+ final Collection extends BlackboardArtifact> selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
+
+ new Thread(() -> {
+ for (BlackboardArtifact artifact : selectedArtifacts) {
+ try {
+ Case.getCurrentCase().getServices().getTagsManager().addBlackboardArtifactTag(artifact, tagName, comment);
+ } catch (TskCoreException ex) {
+ Logger.getLogger(AddBlackboardArtifactTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); //NON-NLS
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "AddBlackboardArtifactTagAction.unableToTag.msg",
+ artifact.getDisplayName()),
+ NbBundle.getMessage(this.getClass(),
+ "AddBlackboardArtifactTagAction.taggingErr"),
+ JOptionPane.ERROR_MESSAGE);
+ });
+ }
}
- }
+ }).start();
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java
index 47c0f69c0c..b1ea2b31f4 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013-15 Basis Technology Corp.
+ * Copyright 2013-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -41,6 +42,7 @@ public class AddContentTagAction extends AddTagAction {
// This class is a singleton to support multi-selection of nodes, since
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
// node in the array returns a reference to the same action object from Node.getActions(boolean).
+
private static AddContentTagAction instance;
public static synchronized AddContentTagAction getInstance() {
@@ -63,71 +65,93 @@ public class AddContentTagAction extends AddTagAction {
@Override
protected void addTag(TagName tagName, String comment) {
- Collection extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
- for (AbstractFile file : selectedFiles) {
- try {
- // Handle the special cases of current (".") and parent ("..") directory entries.
- if (".".equals(file.getName())) {
- Content parentFile = file.getParent();
- if (parentFile instanceof AbstractFile) {
- file = (AbstractFile) parentFile;
- } else {
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "AddContentTagAction.unableToTag.msg",
- parentFile.getName()),
- NbBundle.getMessage(this.getClass(),
- "AddContentTagAction.cannotApplyTagErr"),
- JOptionPane.WARNING_MESSAGE);
- continue;
- }
- } else if ("..".equals(file.getName())) {
- Content parentFile = file.getParent();
- if (parentFile instanceof AbstractFile) {
- parentFile = parentFile.getParent();
+ final Collection extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
+
+ new Thread(() -> {
+ for (AbstractFile file : selectedFiles) {
+ try {
+ // Handle the special cases of current (".") and parent ("..") directory entries.
+ if (file.getName().equals(".")) {
+ Content parentFile = file.getParent();
if (parentFile instanceof AbstractFile) {
file = (AbstractFile) parentFile;
} else {
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "AddContentTagAction.unableToTag.msg",
- parentFile.getName()),
- NbBundle.getMessage(this.getClass(),
- "AddContentTagAction.cannotApplyTagErr"),
- JOptionPane.WARNING_MESSAGE);
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.unableToTag.msg",
+ parentFile.getName()),
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.cannotApplyTagErr"),
+ JOptionPane.WARNING_MESSAGE);
+ });
continue;
}
- } else {
+ } else if (file.getName().equals("..")) {
+ Content parentFile = file.getParent();
+ if (parentFile instanceof AbstractFile) {
+ parentFile = (AbstractFile) ((AbstractFile) parentFile).getParent();
+ if (parentFile instanceof AbstractFile) {
+ file = (AbstractFile) parentFile;
+ } else {
+ final Content parentFileCopy = parentFile;
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.unableToTag.msg",
+ parentFileCopy.getName()),
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.cannotApplyTagErr"),
+ JOptionPane.WARNING_MESSAGE);
+ });
+ continue;
+ }
+ } else {
+ final Content parentFileCopy = parentFile;
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.unableToTag.msg",
+ parentFileCopy.getName()),
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.cannotApplyTagErr"),
+ JOptionPane.WARNING_MESSAGE);
+ });
+ continue;
+ }
+ }
+ // check if the same tag is being added for the same abstract file.
+ TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager();
+ List contentTagList = tagsManager.getContentTagsByContent(file);
+ for (ContentTag contentTag : contentTagList) {
+ if (contentTag.getName().getDisplayName().equals(tagName.getDisplayName())) {
+ AbstractFile fileCopy = file;
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.tagExists",
+ fileCopy.getName(), tagName.getDisplayName()),
+ NbBundle.getMessage(this.getClass(),
+ "AddContentTagAction.cannotApplyTagErr"),
+ JOptionPane.WARNING_MESSAGE);
+ });
+ return;
+ }
+ }
+ tagsManager.addContentTag(file, tagName, comment);
+ } catch (TskCoreException ex) {
+ Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); //NON-NLS
+ AbstractFile fileCopy = file;
+ SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(null,
NbBundle.getMessage(this.getClass(),
- "AddContentTagAction.unableToTag.msg",
- parentFile.getName()),
- NbBundle.getMessage(this.getClass(),
- "AddContentTagAction.cannotApplyTagErr"),
- JOptionPane.WARNING_MESSAGE);
- continue;
- }
+ "AddContentTagAction.unableToTag.msg2",
+ fileCopy.getName()),
+ NbBundle.getMessage(this.getClass(), "AddContentTagAction.taggingErr"),
+ JOptionPane.ERROR_MESSAGE);
+ });
}
- // check if the same tag is being added for the same abstract file.
- TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager();
- List contentTagList = tagsManager.getContentTagsByContent(file);
- boolean alreadyTaggedWithTagName = contentTagList.stream()
- .map(ContentTag::getName)
- .filter(tagName::equals)
- .findAny().isPresent();
- if (alreadyTaggedWithTagName) {
- continue; //skip this file, it already has a tag with this TagName.
- }
- tagsManager.addContentTag(file, tagName, comment);
- } catch (TskCoreException ex) {
- Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); //NON-NLS
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "AddContentTagAction.unableToTag.msg2",
- file.getName()),
- NbBundle.getMessage(this.getClass(), "AddContentTagAction.taggingErr"),
- JOptionPane.ERROR_MESSAGE);
}
- }
+ }).start();
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
index ca5c8b3174..832079a515 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
@@ -49,7 +49,12 @@ GetTagNameDialog.taggingErr=Tagging Error
GetTagNameDialog.tagNameAlreadyDef.msg=A {0} tag name has already been defined.
GetTagNameDialog.dupTagErr=Duplicate Tag Error
OpenLogFolder.error1=Log File Not Found: {0}
+OpenLogFolder.CouldNotOpenLogFolder=Could not open log folder
CTL_OpenLogFolder=Open Log Folder
+CTL_OpenOutputFolder=Open Output Folder
+OpenOutputFolder.error1=Output Folder Not Found: {0}
+OpenOutputFolder.noCaseOpen=No open case, therefore no current output folder available.
+OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder
ShowIngestProgressSnapshotAction.actionName.text=Get Ingest Progress Snapshot
OpenPythonModulesFolderAction.actionName.text=Python Plugins
OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java
index beaea6dc1d..ae3c8c52e5 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013 Basis Technology Corp.
+ * Copyright 2013-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -36,6 +37,7 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
public class DeleteBlackboardArtifactTagAction extends AbstractAction {
+ private static final long serialVersionUID = 1L;
private static final String MENU_TEXT = NbBundle.getMessage(DeleteBlackboardArtifactTagAction.class,
"DeleteBlackboardArtifactTagAction.deleteTags");
@@ -57,20 +59,49 @@ public class DeleteBlackboardArtifactTagAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent event) {
- Collection extends BlackboardArtifactTag> selectedTags = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class);
- for (BlackboardArtifactTag tag : selectedTags) {
- try {
- Case.getCurrentCase().getServices().getTagsManager().deleteBlackboardArtifactTag(tag);
- } catch (TskCoreException ex) {
- Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "DeleteBlackboardArtifactTagAction.unableToDelTag.msg",
- tag.getName()),
- NbBundle.getMessage(this.getClass(),
- "DeleteBlackboardArtifactTagAction.tagDelErr"),
- JOptionPane.ERROR_MESSAGE);
+ final Collection extends BlackboardArtifactTag> selectedTags = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class);
+ new Thread(() -> {
+ for (BlackboardArtifactTag tag : selectedTags) {
+ try {
+ Case.getCurrentCase().getServices().getTagsManager().deleteBlackboardArtifactTag(tag);
+ } catch (TskCoreException ex) {
+ Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "DeleteBlackboardArtifactTagAction.unableToDelTag.msg",
+ tag.getName()),
+ NbBundle.getMessage(this.getClass(),
+ "DeleteBlackboardArtifactTagAction.tagDelErr"),
+ JOptionPane.ERROR_MESSAGE);
+ });
+ }
}
- }
+ }).start();
}
+
+ /**
+ * Deprecated, use actionPerformed() instead.
+ *
+ * @param event The event associated with the action.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ protected void doAction(ActionEvent event) {
+ actionPerformed(event);
+ }
+
+ /**
+ * Deprecated, does nothing. The TagManager methods to create, update or
+ * delete tags now notify the case that there is a tag change. The case then
+ * publishes an event that triggers a refresh of the tags sub-tree in the
+ * tree view.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ protected void refreshDirectoryTree() {
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java
index 88dac00654..c37eedd024 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013 Basis Technology Corp.
+ * Copyright 2013-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -35,6 +36,7 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
public class DeleteContentTagAction extends AbstractAction {
+ private static final long serialVersionUID = 1L;
private static final String MENU_TEXT = NbBundle.getMessage(DeleteContentTagAction.class,
"DeleteContentTagAction.deleteTags");
@@ -56,19 +58,48 @@ public class DeleteContentTagAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
- Collection extends ContentTag> selectedTags = Utilities.actionsGlobalContext().lookupAll(ContentTag.class);
- for (ContentTag tag : selectedTags) {
- try {
- Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(tag);
- } catch (TskCoreException ex) {
- Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "DeleteContentTagAction.unableToDelTag.msg",
- tag.getName()),
- NbBundle.getMessage(this.getClass(), "DeleteContentTagAction.tagDelErr"),
- JOptionPane.ERROR_MESSAGE);
+ final Collection extends ContentTag> selectedTags = Utilities.actionsGlobalContext().lookupAll(ContentTag.class);
+ new Thread(() -> {
+ for (ContentTag tag : selectedTags) {
+ try {
+ Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(tag);
+ } catch (TskCoreException ex) {
+ Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "DeleteContentTagAction.unableToDelTag.msg",
+ tag.getName()),
+ NbBundle.getMessage(this.getClass(), "DeleteContentTagAction.tagDelErr"),
+ JOptionPane.ERROR_MESSAGE);
+ });
+ }
}
- }
+ }).start();
}
+
+ /**
+ * Deprecated, use actionPerformed() instead.
+ *
+ * @param event The event associated with the action.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ protected void doAction(ActionEvent event) {
+ actionPerformed(event);
+ }
+
+ /**
+ * Deprecated, does nothing. The TagManager methods to create, update or
+ * delete tags now notify the case that there is a tag change. The case then
+ * publishes an event that triggers a refresh of the tags sub-tree in the
+ * tree view.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ protected void refreshDirectoryTree() {
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
index 014d2d4299..bbadf4291d 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2014 Basis Technology Corp.
+ * Copyright 2014-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,15 +23,16 @@ import java.awt.event.ActionListener;
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
+import java.util.logging.Level;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.modules.Places;
-import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Action in menu to open the folder containing the log files
@@ -42,6 +43,8 @@ import org.sleuthkit.autopsy.casemodule.Case;
@ActionID(id = "org.sleuthkit.autopsy.actions.OpenLogFolderAction", category = "Help")
public final class OpenLogFolderAction implements ActionListener {
+ private static final Logger logger = Logger.getLogger(OpenLogFolderAction.class.getName());
+
@Override
public void actionPerformed(ActionEvent e) {
try {
@@ -61,7 +64,8 @@ public final class OpenLogFolderAction implements ActionListener {
Desktop.getDesktop().open(logDir);
}
} catch (IOException ex) {
- Exceptions.printStackTrace(ex);
+ logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "OpenLogFolder.CouldNotOpenLogFolder"), ex); //NON-NLS
+
}
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java
new file mode 100644
index 0000000000..2ea5c95538
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java
@@ -0,0 +1,71 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.actions;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.Desktop;
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import javax.swing.JOptionPane;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionReference;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+
+/**
+ * Action in menu to open the folder containing the output files
+ */
+@ActionRegistration(
+ displayName = "#CTL_OpenOutputFolder", iconInMenu = true)
+@ActionReference(path = "Menu/Help", position = 1850)
+@ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help")
+public final class OpenOutputFolderAction implements ActionListener {
+
+ private static final Logger logger = Logger.getLogger(OpenOutputFolderAction.class.getName());
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+
+ try {
+ File outputDir;
+ if (Case.isCaseOpen()) {
+ outputDir = new File(Case.getCurrentCase().getOutputDirectory());
+ if (outputDir.exists() == false) {
+ NotifyDescriptor d
+ = new NotifyDescriptor.Message(NbBundle.getMessage(this.getClass(),
+ "OpenOutputFolder.error1", outputDir.getAbsolutePath()),
+ NotifyDescriptor.ERROR_MESSAGE);
+ DialogDisplayer.getDefault().notify(d);
+ } else {
+ Desktop.getDesktop().open(outputDir);
+ }
+ } else {
+ JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.noCaseOpen"));
+ }
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder"), ex); //NON-NLS
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java
index 384f8f9cc3..88762ba96a 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java
@@ -20,12 +20,10 @@ package org.sleuthkit.autopsy.casemodule;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
-import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
-
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.event.ChangeEvent;
@@ -33,7 +31,8 @@ import javax.swing.event.ChangeListener;
import org.openide.WizardDescriptor;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
-import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+import org.openide.windows.WindowManager;
+import java.awt.Cursor;
/**
* The "Add Image" wizard panel1 handling the logic of selecting image file(s)
@@ -70,6 +69,7 @@ class AddImageWizardChooseDataSourcePanel implements WizardDescriptor.Panel sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,13 +25,13 @@ import java.awt.Window;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import org.openide.WizardDescriptor;
import org.openide.util.HelpCtx;
-import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
@@ -47,7 +47,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
*/
class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel {
- private IngestJobSettingsPanel ingestJobSettingsPanel;
+ private final IngestJobSettingsPanel ingestJobSettingsPanel;
/**
* The visual component that displays this panel. If you need to access the
@@ -220,13 +220,14 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel {
+ Case.getCurrentCase().notifyAddingDataSource(dataSourceId);
+ }).start();
DataSourceProcessorCallback cbObj = new DataSourceProcessorCallback() {
@Override
public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List errList, List contents) {
- dataSourceProcessorDone(result, errList, contents);
+ dataSourceProcessorDone(dataSourceId, result, errList, contents);
}
};
@@ -253,7 +257,10 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel {
+ Case.getCurrentCase().notifyFailedAddingDataSource(dataSourceId);
+ }).start();
dsProcessor.cancel();
}
@@ -261,8 +268,7 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel errList, List contents) {
-
+ private void dataSourceProcessorDone(UUID dataSourceId, DataSourceProcessorCallback.DataSourceProcessorResult result, List errList, List contents) {
// disable the cleanup task
cleanupTask.disable();
@@ -301,10 +307,13 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel {
+ if (!newContents.isEmpty()) {
+ Case.getCurrentCase().notifyDataSourceAdded(newContents.get(0), dataSourceId);
+ } else {
+ Case.getCurrentCase().notifyFailedAddingDataSource(dataSourceId);
+ }
+ }).start();
// Start ingest if we can
progressPanel.setStateStarted();
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
index ec12ed5a4d..c9a86ea72f 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
@@ -107,13 +107,13 @@ AddImageWizardIngestConfigVisual.getName.text=Configure Ingest Modules
AddImageWizardIterator.stepXofN=Step {0} of {1}
AddLocalFilesTask.localFileAdd.progress.text=Adding\: {0}/{1}
Case.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open\!
-Case.moduleErr=Module Error
-Case.changeCase.errListenToCaseUpdates.msg=A module caused an error listening to Case updates. See log to determine which module. Some data could be incomplete.
Case.create.exception.msg=Error creating a case\: {0} in dir {1}
+Case.databaseConnectionInfo.error.msg=Error accessing case database connection info
Case.open.exception.blankCase.msg=Case name is blank.
Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the database with the following path has been made\:\n {0}
Case.open.msgDlg.updated.title=Case Database Schema Update
Case.open.exception.checkFile.msg=Check that you selected the correct case file (usually with {0} extension)
+Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-user.
Case.open.exception.gen.msg=Error opening the case
Case.checkImgExist.confDlg.doesntExist.msg={0} has detected that one of the images associated with \n\
this case are missing. Would you like to search for them now?\n\
@@ -135,7 +135,11 @@ Case.createCaseDir.exception.existCantRW=Cannot create case dir, already exists
Case.createCaseDir.exception.cantCreate=Cannot create case dir\: {0}
Case.createCaseDir.exception.cantCreateCaseDir=Could not create case directory\: {0}
Case.createCaseDir.exception.cantCreateModDir=Could not create modules output directory\: {0}
+Case.createCaseDir.exception.cantCreateReportsDir=Could not create reports output directory\: {0}
Case.createCaseDir.exception.gen=Could not create case directory\: {0}
+Case.CollaborationSetup.FailNotify.ErrMsg=Failed to connect to any other nodes that may be collaborating on this case.
+Case.CollaborationSetup.FailNotify.Title=Connection Failure
+Case.GetCaseTypeGivenPath.Failure=Unable to get case type
CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\
Case Name\: {0}\n\
Case Directory\: {1}
@@ -145,10 +149,11 @@ Close the folder and file and try again or you can delete the case manually.
CaseDeleteAction.msgDlg.fileInUse.title=Error\: Folder In Use
CaseDeleteAction.msgDlg.caseDelete.msg=Case {0} has been deleted.
CaseOpenAction.autFilter.title={0} Case File ( {1})
-CaseOpenAction.msgDlg.fileNotExist.msg=Error\: File does not exist.
-CaseOpenAction.msgDlg.fileNotExist.title=Error
-CaseOpenAction.msgDlg.cantOpenCase.msg=Error\: could not open the case in folder {0}\: {1}
-CaseOpenAction.msgDlg.cantOpenCase.title=Error
+CaseOpenAction.msgDlg.cantOpenCase.title=Error Opening Case
+CaseCreateAction.msgDlg.cantCreateCase.msg=Cannot create case
+IntervalErrorReport.NewIssues=new issue(s)
+IntervalErrorReport.TotalIssues=total issue(s)
+IntervalErrorReport.ErrorText=Database Connection Error
CasePropertiesAction.window.title=Case Properties
CasePropertiesForm.updateCaseName.msgDlg.empty.msg=The caseName cannot be empty.
CasePropertiesForm.updateCaseName.msgDlg.empty.title=Error
@@ -183,13 +188,18 @@ MissingImageDialog.allDesc.text=All Supported Types
MissingImageDialog.display.title=Search for Missing Image
MissingImageDialog.confDlg.noFileSel.msg=No image file has been selected, are you sure you\nwould like to exit without finding the image.
MissingImageDialog.confDlg.noFileSel.title=Missing Image
+MissingImageDialog.ErrorSettingImage=Error setting image path. Please try again.
NewCaseVisualPanel1.getName.text=Case Info
NewCaseVisualPanel1.caseDirBrowse.selectButton.text=Select
+NewCaseVisualPanel1.badCredentials.text=Bad multi-user settings (see Tools, Options, Multi-user) or services are down.
+NewCaseVisualPanel1.MultiUserDisabled.text=Multi-user cases not enabled. See Tools, Options, Multi-user.
NewCaseVisualPanel2.getName.text=Additional Information
NewCaseWizardAction.closeCurCase.confMsg.msg=Do you want to save and close this case and proceed with the new case creation?
NewCaseWizardAction.closeCurCase.confMsg.title=Warning\: Closing the Current Case
NewCaseWizardAction.newCase.windowTitle.text=New Case Information
NewCaseWizardAction.getName.text=New Case Wizard
+NewCaseWizardAction.databaseProblem1.text=Cannot open database. Cancelling case creation.
+NewCaseWizardAction.databaseProblem2.text=Error
NewCaseWizardPanel1.validate.errMsg.invalidSymbols=The Case Name cannot contain any of the following symbols\: \\ / \: * ? " < > |
NewCaseWizardPanel1.validate.errMsg.dirExists=Case directory ''{0}'' already exists.
NewCaseWizardPanel1.validate.confMsg.createDir.msg=The base directory "{0}" does not exist. \n\n\
@@ -221,5 +231,33 @@ Detail\: \n\
Cannot open a non-Autopsy config file (at {1}).
XMLCaseManagement.open.msgDlg.notAutCase.title=Error
AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=Cancel
+ImageFilePanel.errorLabel.text=Error Label
+DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on \"C:\" drive
+NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on \"C:\" drive
+LocalFilesPanel.errorLabel.text=Error Label
+CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source
+CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1}
+MissingImageDialog.lbWarning.text=
+MissingImageDialog.lbWarning.toolTipText=
+SingleUserCaseImporter.AlreadyMultiUser=Case is already multi-user!
+SingleUserCaseImporter.BadCaseSourceFolder=Case source folder does not exist!
+SingleUserCaseImporter.BadImageSourceFolder=Image source folder does not exist!
+SingleUserCaseImporter.BadDatabaseFileName=Database file does not exist!
+SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping
+SingleUserCaseImporter.NonUniqueDatabaseName=Database name not unique. Skipping.
+SingleUserCaseImporter.PotentiallyNonUniqueDatabaseName=Unclear if database name unique. Moving ahead.
+SingleUserCaseImporter.ImportedAsMultiUser=\nThis case was imported as a multi-user collaborative case on
+SingleUserCaseImporter.UnableToCopySourceImages=Unable to copy source images
+SingleUserCaseImporter.DeletingCase=Deleting original case folder
+SingleUserCaseImporter.CanNotOpenDatabase=Unable to open database
+SingleUserCaseImporter.WillImport=Will import:
+SingleUserCaseImporter.WillNotImport=Will not import:
+SingleUserCaseImporter.None=None
+SingleUserCaseImporter.ContinueWithImport=Continue with import?
+SingleUserCaseImporter.Cancelled=Cancelled
+NewCaseVisualPanel1.caseParentDirWarningLabel.text=Case directory warning label
+NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user
+NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user
+NewCaseVisualPanel1.multiUserSettingsWarningLabel.text=Multi-user settings warning label
Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk.
Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties
index 3eb6853e11..5dff497d16 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties
@@ -102,8 +102,6 @@ AddImageWizardIngestConfigVisual.getName.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30
AddImageWizardIterator.stepXofN=\u30b9\u30c6\u30c3\u30d7{0}\uff0f{1}
AddLocalFilesTask.localFileAdd.progress.text=\u8ffd\u52a0\u4e2d\uff1a{0}/{1}
Case.getCurCase.exception.noneOpen=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\uff1b\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\uff01
-Case.moduleErr=\u30e2\u30b8\u30e5\u30fc\u30eb\u30a8\u30e9\u30fc
-Case.changeCase.errListenToCaseUpdates.msg=\u30b1\u30fc\u30b9\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u3092\u78ba\u8a8d\u4e2d\u306b\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u30a8\u30e9\u30fc\u3092\u8d77\u3053\u3057\u307e\u3057\u305f\u3002\u3069\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u304b\u30ed\u30b0\u3067\u78ba\u8a8d\u3057\u3066\u4e0b\u3055\u3044\u3002\u4e00\u90e8\u306e\u30c7\u30fc\u30bf\u304c\u4e0d\u5b8c\u5168\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
Case.create.exception.msg=\u30b1\u30fc\u30b9\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a\u30c7\u30a3\u30ec\u30af\u30c8\u30ea{1}\u306e{0}
Case.open.exception.blankCase.msg=\u30b1\u30fc\u30b9\u540d\u304c\u7a7a\u767d\u3067\u3059\u3002
Case.open.msgDlg.updated.msg=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b9\u30ad\u30fc\u30de\u3092\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u3057\u307e\u3057\u305f\u3002\n\
@@ -217,3 +215,6 @@ XMLCaseManagement.open.msgDlg.notAutCase.title=\u30a8\u30e9\u30fc
ImageFilePanel.noFatOrphansCheckbox.text=FAT\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u306e\u30aa\u30fc\u30d5\u30a1\u30f3\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u8996
LocalDiskPanel.noFatOrphansCheckbox.text=FAT\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u306e\u30aa\u30fc\u30d5\u30a1\u30f3\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u8996
AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=\u30ad\u30e3\u30f3\u30bb\u30eb
+ImageFilePanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb
+LocalFilesPanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb
+NewCaseVisualPanel1.caseParentDirWarningLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 04b88a496b..de01134bcd 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -19,7 +19,6 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Frame;
-import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedInputStream;
@@ -27,9 +26,11 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
@@ -38,26 +39,39 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
+import java.util.UUID;
import java.util.logging.Level;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.apache.commons.io.FileUtils;
-import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
-import org.openide.util.actions.SystemAction;
import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
+import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent;
+import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent;
+import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
+import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.services.Services;
+import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.CoreComponentControl;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.coreutils.Version;
-import org.sleuthkit.autopsy.events.BlackBoardArtifactTagAddedEvent;
-import org.sleuthkit.autopsy.events.BlackBoardArtifactTagDeletedEvent;
-import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
-import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
+import org.sleuthkit.autopsy.coreutils.NetworkUtils;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+import org.sleuthkit.autopsy.events.AutopsyEventException;
+import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
+import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
+import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
+import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
+import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
+import org.sleuthkit.autopsy.core.RuntimeProperties;
+import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
@@ -73,19 +87,26 @@ import org.sleuthkit.datamodel.TskException;
* open at a time. Use getCurrentCase() to retrieve the object for the current
* case.
*/
-@SuppressWarnings("deprecation") // TODO: Remove this when ErrorObserver is replaced.
public class Case implements SleuthkitCase.ErrorObserver {
private static final String autopsyVer = Version.getVersion(); // current version of autopsy. Change it when the version is changed
+ private static final String EVENT_CHANNEL_NAME = "%s-Case-Events";
private static String appName = null;
+ volatile private IntervalErrorReportData tskErrorReporter = null;
+ private static final int MIN_SECONDS_BETWEEN_ERROR_REPORTS = 60; // No less than 60 seconds between warnings for errors
+ private static final int MAX_SANITIZED_NAME_LENGTH = 47;
/**
* Name for the property that determines whether to show the dialog at
* startup
*/
public static final String propStartup = "LBL_StartupDialog"; //NON-NLS
- // pcs is initialized in CaseListener constructor
- private static final PropertyChangeSupport pcs = new PropertyChangeSupport(Case.class);
+
+ /**
+ * The event publisher is static so that subscribers only have to subscribe
+ * once to receive events for all cases.
+ */
+ private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
/**
* Events that the case module will fire. Event listeners can get the event
@@ -95,12 +116,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Property name that indicates the name of the current case has
- * changed. When a case is opened, "old name" is empty string and "new
- * name" is the name. When a case is closed, "old name" is the case name
- * and "new name" is empty string. When a case is renamed, "old name"
- * has the original name and "new name" has the new name.
+ * changed. The old value is the old case name, the new value is the new
+ * case name.
*/
- // @@@ BC: I propose that this is no longer called for case open/close.
NAME,
/**
* Property name that indicates the number of the current case has
@@ -115,6 +133,24 @@ public class Case implements SleuthkitCase.ErrorObserver {
* no examiner set.
*/
EXAMINER,
+ /**
+ * Property name used for a property change event that indicates a new
+ * data source (image, local/logical file or local disk) is being added
+ * to the current case. The old and new values of the
+ * PropertyChangeEvent are null - cast the PropertyChangeEvent to
+ * org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent to
+ * access event data.
+ */
+ ADDING_DATA_SOURCE,
+ /**
+ * Property name used for a property change event that indicates a
+ * failure adding a new data source (image, local/logical file or local
+ * disk) to the current case. The old and new values of the
+ * PropertyChangeEvent are null - cast the PropertyChangeEvent to
+ * org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent
+ * to access event data.
+ */
+ ADDING_DATA_SOURCE_FAILED,
/**
* Property name that indicates a new data source (image, disk or local
* file) has been added to the current case. The new value is the
@@ -156,7 +192,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
BLACKBOARD_ARTIFACT_TAG_ADDED,
/**
* Property name for the event when a new BlackBoardArtifactTag is
- * deleted. The new value is empty, the old value is the deleted tag
+ * deleted. The new value is empty, the old value is a
+ * {@link BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo}
+ * object with info about the deleted tag.
*/
BLACKBOARD_ARTIFACT_TAG_DELETED,
/**
@@ -166,11 +204,49 @@ public class Case implements SleuthkitCase.ErrorObserver {
CONTENT_TAG_ADDED,
/**
* Property name for the event when a new ContentTag is deleted. The new
- * value is empty, the old value is the deleted tag
+ * value is empty, the old value is a
+ * {@link ContentTagDeletedEvent.DeletedContentTagInfo} object with info
+ * about the deleted tag.
*/
CONTENT_TAG_DELETED;
};
+ /**
+ * This enum describes the type of case, either single-user (standalone) or
+ * multi-user (using PostgreSql)
+ */
+ public enum CaseType {
+
+ SINGLE_USER_CASE("Single-user case"),
+ MULTI_USER_CASE("Multi-user case");
+
+ private final String caseType;
+
+ private CaseType(String s) {
+ caseType = s;
+ }
+
+ public boolean equalsName(String otherType) {
+ return (otherType == null) ? false : caseType.equals(otherType);
+ }
+
+ public static CaseType fromString(String typeName) {
+ if (typeName != null) {
+ for (CaseType c : CaseType.values()) {
+ if (typeName.equalsIgnoreCase(c.caseType)) {
+ return c;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return caseType;
+ }
+ };
+
private String name;
private String number;
private String examiner;
@@ -179,35 +255,37 @@ public class Case implements SleuthkitCase.ErrorObserver {
private final SleuthkitCase db;
// Track the current case (only set with changeCase() method)
private static Case currentCase = null;
+ private final CaseType caseType;
private final Services services;
private static final Logger logger = Logger.getLogger(Case.class.getName());
static final String CASE_EXTENSION = "aut"; //NON-NLS
static final String CASE_DOT_EXTENSION = "." + CASE_EXTENSION;
+ private final static String CACHE_FOLDER = "Cache"; //NON-NLS
+ private final static String EXPORT_FOLDER = "Export"; //NON-NLS
+ private final static String LOG_FOLDER = "Log"; //NON-NLS
+ final static String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
+ private final static String REPORTS_FOLDER = "Reports"; //NON-NLS
+ private final static String TEMP_FOLDER = "Temp"; //NON-NLS
// we cache if the case has data in it yet since a few places ask for it and we dont' need to keep going to DB
private boolean hasData = false;
+ private CollaborationMonitor collaborationMonitor;
+
/**
* Constructor for the Case class
*/
- private Case(String name, String number, String examiner, String configFilePath, XMLCaseManagement xmlcm, SleuthkitCase db) {
+ private Case(String name, String number, String examiner, String configFilePath, XMLCaseManagement xmlcm, SleuthkitCase db, CaseType type) {
this.name = name;
this.number = number;
this.examiner = examiner;
this.configFilePath = configFilePath;
this.xmlcm = xmlcm;
+ this.caseType = type;
this.db = db;
this.services = new Services(db);
}
- /**
- * Does initialization that would leak a reference to this if done in the
- * constructor.
- */
- private void init() {
- db.addErrorObserver(this);
- }
-
/**
* Gets the currently opened case, if there is one.
*
@@ -240,72 +318,93 @@ public class Case implements SleuthkitCase.ErrorObserver {
*
*/
private static void changeCase(Case newCase) {
-
// close the existing case
Case oldCase = Case.currentCase;
Case.currentCase = null;
if (oldCase != null) {
- doCaseChange(null); //closes windows, etc
-
- try {
- pcs.firePropertyChange(Events.CURRENT_CASE.toString(), oldCase, null);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case listener threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(Case.class, "Case.moduleErr"),
- NbBundle.getMessage(Case.class,
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
+ doCaseChange(null); //closes windows, etc
+ if (null != oldCase.tskErrorReporter) {
+ oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case
+ oldCase.tskErrorReporter = null;
}
- doCaseNameChange("");
-
- try {
- pcs.firePropertyChange(Events.NAME.toString(), oldCase.name, "");
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case listener threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(Case.class, "Case.moduleErr"),
- NbBundle.getMessage(Case.class,
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
+ eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), oldCase, null));
+ if (CaseType.MULTI_USER_CASE == oldCase.getCaseType()) {
+ if (null != oldCase.collaborationMonitor) {
+ oldCase.collaborationMonitor.shutdown();
+ }
+ eventPublisher.closeRemoteEventChannel();
}
}
if (newCase != null) {
currentCase = newCase;
-
Logger.setLogDirectory(currentCase.getLogDirectoryPath());
-
- try {
- pcs.firePropertyChange(Events.CURRENT_CASE.toString(), null, currentCase);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case listener threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(Case.class, "Case.moduleErr"),
- NbBundle.getMessage(Case.class,
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
+ // sanity check
+ if (null != currentCase.tskErrorReporter) {
+ currentCase.tskErrorReporter.shutdown();
}
+ // start listening for TSK errors for the new case
+ currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECONDS_BETWEEN_ERROR_REPORTS,
+ NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
doCaseChange(currentCase);
-
- try {
- pcs.firePropertyChange(Events.NAME.toString(), "", currentCase.name);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(Case.class, "Case.moduleErr"),
- NbBundle.getMessage(Case.class,
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
+ SwingUtilities.invokeLater(() -> {
+ RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
+ });
+ if (CaseType.MULTI_USER_CASE == newCase.getCaseType()) {
+ try {
+ /**
+ * Use the text index name as the remote event channel name
+ * prefix since it is unique, the same as the case database
+ * name for a multiuser case, and is readily available
+ * through the Case.getTextIndexName() API.
+ */
+ eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, newCase.getTextIndexName()));
+ currentCase.collaborationMonitor = new CollaborationMonitor();
+ } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
+ logger.log(Level.SEVERE, "Failed to setup for collaboration", ex);
+ MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"));
+ }
}
- doCaseNameChange(currentCase.name);
+ eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
- RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
} else {
Logger.setLogDirectory(PlatformUtil.getLogDirectory());
}
}
+
+ @Override
+ public void receiveError(String context, String errorMessage) {
+ /* NOTE: We are accessing tskErrorReporter from two different threads.
+ * This is ok as long as we only read the value of tskErrorReporter
+ * because tskErrorReporter is declared as volatile.
+ */
+ if (null != tskErrorReporter) {
+ tskErrorReporter.addProblems(context, errorMessage);
+ }
+ }
AddImageProcess makeAddImageProcess(String timezone, boolean processUnallocSpace, boolean noFatOrphans) {
return this.db.makeAddImageProcess(timezone, processUnallocSpace, noFatOrphans);
}
+ /**
+ * Creates a new case (create the XML config file and database). Overload
+ * for API consistency, defaults to a single-user case.
+ *
+ * @param caseDir The directory to store case data in. Will be created if
+ * it doesn't already exist. If it exists, it should have
+ * all of the needed sub dirs that createCaseDirectory()
+ * will create.
+ * @param caseName the name of case
+ * @param caseNumber the case number
+ * @param examiner the examiner for this case
+ *
+ * @throws org.sleuthkit.autopsy.casemodule.CaseActionException
+ */
+ public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException {
+ create(caseDir, caseName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
+ }
+
/**
* Creates a new case (create the XML config file and database)
*
@@ -316,98 +415,219 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @param caseName the name of case
* @param caseNumber the case number
* @param examiner the examiner for this case
+ * @param caseType the type of case, single-user or multi-user
*/
- public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException {
+ public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
logger.log(Level.INFO, "Creating new case.\ncaseDir: {0}\ncaseName: {1}", new Object[]{caseDir, caseName}); //NON-NLS
// create case directory if it doesn't already exist.
if (new File(caseDir).exists() == false) {
- Case.createCaseDirectory(caseDir);
+ Case.createCaseDirectory(caseDir, caseType);
}
String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION;
XMLCaseManagement xmlcm = new XMLCaseManagement();
- xmlcm.create(caseDir, caseName, examiner, caseNumber); // create a new XML config file
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
+ Date date = new Date();
+ String santizedCaseName = sanitizeCaseName(caseName);
+ String indexName = santizedCaseName + "_" + dateFormat.format(date);
+ String dbName = null;
+
+ // figure out the database name and index name for text extraction
+ if (caseType == CaseType.SINGLE_USER_CASE) {
+ dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS
+ } else if (caseType == CaseType.MULTI_USER_CASE) {
+ dbName = indexName;
+ }
+
+ xmlcm.create(caseDir, caseName, examiner, caseNumber, caseType, dbName, indexName); // create a new XML config file
xmlcm.writeFile();
- String dbPath = caseDir + File.separator + "autopsy.db"; //NON-NLS
SleuthkitCase db = null;
try {
- db = SleuthkitCase.newCase(dbPath);
+ if (caseType == CaseType.SINGLE_USER_CASE) {
+ db = SleuthkitCase.newCase(dbName);
+ } else if (caseType == CaseType.MULTI_USER_CASE) {
+ db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir);
+ }
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error creating a case: " + caseName + " in dir " + caseDir, ex); //NON-NLS
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.create.exception.msg", caseName, caseDir), ex);
+ } catch (UserPreferencesException ex) {
+ logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
}
/**
* Two-stage initialization to avoid leaking reference to "this" in
* constructor.
*/
- Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db);
- newCase.init();
-
+ Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db, caseType);
changeCase(newCase);
}
/**
- * Opens the existing case (open the XML config file)
+ * Sanitize the case name for PostgreSQL database, Solr cores, and ActiveMQ
+ * topics. Makes it plain-vanilla enough that each item should be able to
+ * use it.
*
- * @param configFilePath the path of the configuration file that's opened
+ * Sanitize the PostgreSQL/Solr core, and ActiveMQ name by excluding:
+ * Control characters Non-ASCII characters Various others shown below
+ *
+ * Solr:
+ * http://stackoverflow.com/questions/29977519/what-makes-an-invalid-core-name
+ * may not be / \ :
+ *
+ * ActiveMQ:
+ * http://activemq.2283324.n4.nabble.com/What-are-limitations-restrictions-on-destination-name-td4664141.html
+ * may not be ?
+ *
+ * PostgreSQL:
+ * http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html 63
+ * chars max, must start with a-z or _ following chars can be letters _ or
+ * digits
+ *
+ * SQLite: Uses autopsy.db for the database name follows Windows naming
+ * convention
+ *
+ * @param caseName The name of the case as typed in by the user
+ *
+ * @return the sanitized case name to use for Database, Solr, and ActiveMQ
+ */
+ public static String sanitizeCaseName(String caseName) {
+
+ String result;
+
+ // Remove all non-ASCII characters
+ result = caseName.replaceAll("[^\\p{ASCII}]", "_");
+
+ // Remove all control characters
+ result = result.replaceAll("[\\p{Cntrl}]", "_");
+
+ // Remove / \ : ? space ' "
+ result = result.replaceAll("[ /?:'\"\\\\]", "_");
+
+ // Make it all lowercase
+ result = result.toLowerCase();
+
+ // Must start with letter or underscore for PostgreSQL. If not, prepend an underscore.
+ if (result.length() > 0 && !(Character.isLetter(result.codePointAt(0))) && !(result.codePointAt(0) == '_')) {
+ result = "_" + result;
+ }
+
+ // Chop to 63-16=47 left (63 max for PostgreSQL, taking 16 for the date _20151225_123456)
+ if (result.length() > MAX_SANITIZED_NAME_LENGTH) {
+ result = result.substring(0, MAX_SANITIZED_NAME_LENGTH);
+ }
+
+ if (result.isEmpty()) {
+ result = "case";
+ }
+
+ return result;
+ }
+
+ /**
+ * Opens an existing case.
+ *
+ * @param caseMetadataFilePath The path of the case metadata file for the
+ * case to be opened.
*
* @throws CaseActionException
*/
- public static void open(String configFilePath) throws CaseActionException {
- logger.log(Level.INFO, "Opening case.\nconfigFilePath: {0}", configFilePath); //NON-NLS
+ /**
+ * TODO: Deprecate this and throw a more general exception.
+ */
+ public static void open(String caseMetadataFilePath) throws CaseActionException {
+ if (!caseMetadataFilePath.endsWith(CASE_DOT_EXTENSION)) {
+ throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CASE_DOT_EXTENSION));
+ }
+
+ logger.log(Level.INFO, "Opening case, case metadata file path: {0}", caseMetadataFilePath); //NON-NLS
try {
- XMLCaseManagement xmlcm = new XMLCaseManagement();
+ /**
+ * Get the case metadata from the file.
+ */
+ CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
+ String caseName = metadata.getCaseName();
+ String caseNumber = metadata.getCaseNumber();
+ String examiner = metadata.getExaminer();
+ CaseType caseType = metadata.getCaseType();
+ String caseDir = metadata.getCaseDirectory();
- xmlcm.open(configFilePath); // open and load the config file to the document handler in the XML class
- xmlcm.writeFile(); // write any changes to the config file
-
- String caseName = xmlcm.getCaseName();
- String caseNumber = xmlcm.getCaseNumber();
- String examiner = xmlcm.getCaseExaminer();
- // if the caseName is "", case / config file can't be opened
- if (caseName.equals("")) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.blankCase.msg"));
+ /**
+ * Open the case database.
+ */
+ SleuthkitCase db;
+ if (caseType == CaseType.SINGLE_USER_CASE) {
+ String dbPath = Paths.get(caseDir, "autopsy.db").toString(); //NON-NLS
+ db = SleuthkitCase.openCase(dbPath);
+ } else {
+ if (!UserPreferences.getIsMultiUserModeEnabled()) {
+ throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
+ }
+ try {
+ db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
+ } catch (UserPreferencesException ex) {
+ logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
+ }
}
- String caseDir = xmlcm.getCaseDirectory();
- String dbPath = caseDir + File.separator + "autopsy.db"; //NON-NLS
- SleuthkitCase db = SleuthkitCase.openCase(dbPath);
- if (null != db.getBackupDatabasePath()) {
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg",
- db.getBackupDatabasePath()),
- NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
- JOptionPane.INFORMATION_MESSAGE);
- }
+ /**
+ * Do things that require a UI.
+ */
+ if (RuntimeProperties.coreComponentsAreActive()) {
+ /**
+ * If the case database was upgraded for a new schema, notify
+ * the user.
+ */
+ if (null != db.getBackupDatabasePath()) {
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg",
+ db.getBackupDatabasePath()),
+ NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
+ JOptionPane.INFORMATION_MESSAGE);
+ });
+ }
- checkImagesExist(db);
+ /**
+ * TODO: This currently has no value if it there is no user to
+ * interact with a fid missing images dialog.
+ */
+ checkImagesExist(db);
+ }
/**
* Two-stage initialization to avoid leaking reference to "this" in
- * constructor.
+ * constructor. TODO: Remove use of obsolete XMLCaseManagement
+ * class.
*/
- Case openedCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db);
- openedCase.init();
-
+ XMLCaseManagement xmlcm = new XMLCaseManagement();
+ xmlcm.open(caseMetadataFilePath);
+ Case openedCase = new Case(caseName, caseNumber, examiner, caseMetadataFilePath, xmlcm, db, caseType);
changeCase(openedCase);
- } catch (Exception ex) {
- logger.log(Level.SEVERE, "Error opening the case: ", ex); //NON-NLS
- // close the previous case if there's any
- CaseCloseAction closeCase = SystemAction.get(CaseCloseAction.class);
- closeCase.actionPerformed(null);
- if (!configFilePath.endsWith(CASE_DOT_EXTENSION)) {
- throw new CaseActionException(
- NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CASE_DOT_EXTENSION), ex);
- } else {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ". " + ex.getMessage(), ex);
+ } catch (CaseMetadataException | TskCoreException ex) {
+ /**
+ * Clean-up the case if it was actually opened. TODO: Do this
+ * better.
+ */
+ try {
+ Case badCase = Case.getCurrentCase();
+ badCase.closeCase();
+ } catch (CaseActionException | IllegalStateException unused) {
+ // Already logged.
}
+
+ throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ". " + ex.getMessage(), ex);
}
}
@@ -440,7 +660,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
int ret = JOptionPane.showConfirmDialog(null,
NbBundle.getMessage(Case.class,
"Case.checkImgExist.confDlg.doesntExist.msg",
- appName, path),
+ getAppName(), path),
NbBundle.getMessage(Case.class,
"Case.checkImgExist.confDlg.doesntExist.title"),
JOptionPane.YES_NO_OPTION);
@@ -457,110 +677,127 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Adds the image to the current case after it has been added to the DB
+ * Adds the image to the current case after it has been added to the DB.
* Sends out event and reopens windows if needed.
*
* @param imgPaths the paths of the image that being added
* @param imgId the ID of the image that being added
* @param timeZone the timeZone of the image where it's added
+ *
+ * @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
+ * {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and
+ * {@link #notifyFailedAddingDataSource(java.util.UUID)}
*/
@Deprecated
public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
- logger.log(Level.INFO, "Adding image to Case. imgPath: {0} ID: {1} TimeZone: {2}", new Object[]{imgPath, imgId, timeZone}); //NON-NLS
-
try {
- Image newImage = db.getImageById(imgId);
-
- try {
- pcs.firePropertyChange(Events.DATA_SOURCE_ADDED.toString(), null, newImage); // the new value is the instance of the image
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case listener threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "Case.moduleErr"),
- NbBundle.getMessage(this.getClass(),
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
- }
- CoreComponentControl.openCoreWindows();
- return newImage;
+ Image newDataSource = db.getImageById(imgId);
+ notifyDataSourceAdded(newDataSource, UUID.randomUUID());
+ return newDataSource;
} catch (Exception ex) {
throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
}
}
/**
- * Finishes adding new local data source to the case Sends out event and
- * reopens windows if needed.
+ * Finishes adding new local data source to the case. Sends out event and
+ * reopens windows if needed.
*
* @param newDataSource new data source added
+ *
+ * @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
+ * {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and
+ * {@link #notifyFailedAddingDataSource(java.util.UUID)}
*/
@Deprecated
void addLocalDataSource(Content newDataSource) {
-
- notifyNewDataSource(newDataSource);
+ notifyDataSourceAdded(newDataSource, UUID.randomUUID());
}
/**
- * Notifies the UI that a new data source has been added.
+ * Notifies case event subscribers (property change listeners) that a data
+ * source is being added to the case database.
*
+ * This should not be called from the event dispatch thread (EDT)
*
- * @param newDataSource new data source added
+ * @param dataSourceId A unique identifier for the data source. This UUID
+ * should be used to call notifyNewDataSource() after
+ * the data source is added.
*/
- void notifyNewDataSource(Content newDataSource) {
- notifyPropertyChangeEvent(new PropertyChangeEvent(Case.class, Events.DATA_SOURCE_ADDED.toString(), null, newDataSource));
- CoreComponentControl.openCoreWindows();
+ public void notifyAddingDataSource(UUID dataSourceId) {
+ eventPublisher.publish(new AddingDataSourceEvent(dataSourceId));
+ }
+
+ /**
+ * Notifies case event subscribers (property change listeners) that a data
+ * source failed to be added to the case database.
+ *
+ * This should not be called from the event dispatch thread (EDT)
+ *
+ * @param dataSourceId A unique identifier for the data source.
+ */
+ public void notifyFailedAddingDataSource(UUID dataSourceId) {
+ eventPublisher.publish(new AddingDataSourceFailedEvent(dataSourceId));
+ }
+
+ /**
+ * Notifies case event subscribers (property change listeners) that a data
+ * source is being added to the case database.
+ *
+ * This should not be called from the event dispatch thread (EDT)
+ *
+ * @param newDataSource New data source added.
+ * @param dataSourceId A unique identifier for the data source. Should be
+ * the same UUID used to call
+ * notifyAddingNewDataSource() when the process of
+ * adding the data source began.
+ */
+ public void notifyDataSourceAdded(Content newDataSource, UUID dataSourceId) {
+ eventPublisher.publish(new DataSourceAddedEvent(newDataSource, dataSourceId));
}
/**
* Notifies the UI that a new ContentTag has been added.
*
+ * This should not be called from the event dispatch thread (EDT)
+ *
* @param newTag new ContentTag added
*/
public void notifyContentTagAdded(ContentTag newTag) {
- notifyPropertyChangeEvent(new ContentTagAddedEvent(newTag));
+ eventPublisher.publish(new ContentTagAddedEvent(newTag));
}
/**
* Notifies the UI that a ContentTag has been deleted.
*
+ * This should not be called from the event dispatch thread (EDT)
+ *
* @param deletedTag ContentTag deleted
*/
public void notifyContentTagDeleted(ContentTag deletedTag) {
- notifyPropertyChangeEvent(new ContentTagDeletedEvent(deletedTag));
+ eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
}
/**
* Notifies the UI that a new BlackboardArtifactTag has been added.
*
+ * This should not be called from the event dispatch thread (EDT)
+ *
* @param newTag new BlackboardArtifactTag added
*/
public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
- notifyPropertyChangeEvent(new BlackBoardArtifactTagAddedEvent(newTag));
+ eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
}
/**
- * Notifies the UI that a BlackboardArtifactTag has been.
+ * Notifies the UI that a BlackboardArtifactTag has been deleted.
+ *
+ * This should not be called from the event dispatch thread (EDT)
*
* @param deletedTag BlackboardArtifactTag deleted
*/
public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
- notifyPropertyChangeEvent(new BlackBoardArtifactTagDeletedEvent(deletedTag));
- }
-
- /**
- * Notifies the UI about a Case level event.
- *
- * @param propertyChangeEvent the event to distribute
- */
- private void notifyPropertyChangeEvent(final PropertyChangeEvent propertyChangeEvent) {
- try {
- pcs.firePropertyChange(propertyChangeEvent);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "Case.moduleErr"),
- NbBundle.getMessage(this.getClass(),
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
- }
+ eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
}
/**
@@ -585,7 +822,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
*/
public void closeCase() throws CaseActionException {
changeCase(null);
-
try {
services.close();
this.xmlcm.close(); // close the xmlcm
@@ -626,6 +862,8 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Updates the case name.
*
+ * This should not be called from the EDT.
+ *
* @param oldCaseName the old case name that wants to be updated
* @param oldPath the old path that wants to be updated
* @param newCaseName the new case name
@@ -635,18 +873,15 @@ public class Case implements SleuthkitCase.ErrorObserver {
try {
xmlcm.setCaseName(newCaseName); // set the case
name = newCaseName; // change the local value
- RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case
- try {
- pcs.firePropertyChange(Events.NAME.toString(), oldCaseName, newCaseName);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case listener threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "Case.moduleErr"),
- NbBundle.getMessage(this.getClass(),
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
- }
- doCaseNameChange(newCaseName);
-
+ eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName));
+ SwingUtilities.invokeLater(() -> {
+ try {
+ RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case
+ updateMainWindowTitle(newCaseName);
+ } catch (Exception e) {
+ Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", e); //NON-NLS
+ }
+ });
} catch (Exception e) {
throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), e);
}
@@ -655,6 +890,8 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Updates the case examiner
*
+ * This should not be called from the EDT.
+ *
* @param oldExaminer the old examiner
* @param newExaminer the new examiner
*/
@@ -662,15 +899,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
try {
xmlcm.setCaseExaminer(newExaminer); // set the examiner
examiner = newExaminer;
- try {
- pcs.firePropertyChange(Events.EXAMINER.toString(), oldExaminer, newExaminer);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case listener threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "Case.moduleErr"),
- NbBundle.getMessage(this.getClass(),
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
- }
+ eventPublisher.publish(new AutopsyEvent(Events.EXAMINER.toString(), oldExaminer, newExaminer));
} catch (Exception e) {
throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateExaminer.exception.msg"), e);
}
@@ -679,6 +908,8 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Updates the case number
*
+ * This should not be called from the EDT.
+ *
* @param oldCaseNumber the old case number
* @param newCaseNumber the new case number
*/
@@ -686,16 +917,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
try {
xmlcm.setCaseNumber(newCaseNumber); // set the case number
number = newCaseNumber;
-
- try {
- pcs.firePropertyChange(Events.NUMBER.toString(), oldCaseNumber, newCaseNumber);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Case listener threw exception", e); //NON-NLS
- MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "Case.moduleErr"),
- NbBundle.getMessage(this.getClass(),
- "Case.changeCase.errListenToCaseUpdates.msg"),
- MessageNotifyUtil.MessageType.ERROR);
- }
+ eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseNumber, newCaseNumber));
} catch (Exception e) {
throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseNum.exception.msg"), e);
}
@@ -790,55 +1012,192 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Gets the full path to the temp directory of this case
+ * Get the case type.
+ *
+ * @return
+ */
+ public CaseType getCaseType() {
+ return this.caseType;
+ }
+
+ /**
+ * Gets the full path to the temp directory of this case. Will create it if
+ * it does not already exist.
*
* @return tempDirectoryPath
*/
public String getTempDirectory() {
- if (xmlcm == null) {
- return "";
- } else {
- return xmlcm.getTempDir();
- }
+ return getDirectory(TEMP_FOLDER);
}
/**
- * Gets the full path to the cache directory of this case
+ * Gets the full path to the cache directory of this case. Will create it if
+ * it does not already exist.
*
* @return cacheDirectoryPath
*/
public String getCacheDirectory() {
- if (xmlcm == null) {
- return "";
- } else {
- return xmlcm.getCacheDir();
- }
+ return getDirectory(CACHE_FOLDER);
}
/**
- * Gets the full path to the export directory of this case
+ * Gets the full path to the export directory of this case. Will create it
+ * if it does not already exist.
*
- * @return export DirectoryPath
+ * @return exportDirectoryPath
*/
public String getExportDirectory() {
- if (xmlcm == null) {
- return "";
- } else {
- return xmlcm.getExportDir();
- }
+ return getDirectory(EXPORT_FOLDER);
}
/**
- * Gets the full path to the log directory for this case.
+ * Gets the full path to the log directory of this case. Will create it if
+ * it does not already exist.
*
- * @return The log directory path.
+ * @return logDirectoryPath
*/
public String getLogDirectoryPath() {
- if (xmlcm == null) {
- return "";
- } else {
- return xmlcm.getLogDir();
+ return getDirectory(LOG_FOLDER);
+ }
+
+ /**
+ * Get the reports directory path where modules should save their reports.
+ * Will create it if it does not already exist.
+ *
+ * @return absolute path to the report output directory
+ */
+ public String getReportDirectory() {
+ return getDirectory(REPORTS_FOLDER);
+ }
+
+ /**
+ * Get module output directory path where modules should save their
+ * permanent data.
+ *
+ * @return absolute path to the module output directory
+ */
+ public String getModuleDirectory() {
+ return getDirectory(MODULE_FOLDER);
+ }
+
+ /**
+ * Get the output directory path where modules should save their permanent
+ * data. If single-user case, the directory is a subdirectory of the case
+ * directory. If multi-user case, the directory is a subdirectory of
+ * HostName, which is a subdirectory of the case directory.
+ *
+ * @return the path to the host output directory
+ */
+ public String getOutputDirectory() {
+ return getHostDirectory();
+ }
+
+ /**
+ * Get the specified directory path, create it if it does not already exist.
+ *
+ * @return absolute path to the directory
+ */
+ private String getDirectory(String input) {
+ File theDirectory = new File(getHostDirectory() + File.separator + input);
+ if (!theDirectory.exists()) { // Create it if it doesn't exist already.
+ theDirectory.mkdirs();
}
+ return theDirectory.toString();
+ }
+
+ /**
+ * Get relative (with respect to case dir) module output directory path
+ * where modules should save their permanent data. The directory is a
+ * subdirectory of this case dir.
+ *
+ * @return relative path to the module output dir
+ */
+ public String getModuleOutputDirectoryRelativePath() {
+ Path thePath;
+ if (getCaseType() == CaseType.MULTI_USER_CASE) {
+ thePath = Paths.get(NetworkUtils.getLocalHostName(), MODULE_FOLDER);
+ } else {
+ thePath = Paths.get(MODULE_FOLDER);
+ }
+ // Do not autocreate this relative path. It will have already been
+ // created when the case was made.
+ return thePath.toString();
+ }
+
+ /**
+ * Get the host output directory path where modules should save their
+ * permanent data. If single-user case, the directory is a subdirectory of
+ * the case directory. If multi-user case, the directory is a subdirectory
+ * of the hostName, which is a subdirectory of the case directory.
+ *
+ * @return the path to the host output directory
+ */
+ private String getHostDirectory() {
+ String caseDirectory = getCaseDirectory();
+ Path hostPath;
+ if (caseType == CaseType.MULTI_USER_CASE) {
+ hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
+ } else {
+ hostPath = Paths.get(caseDirectory);
+ }
+ if (!hostPath.toFile().exists()) {
+ hostPath.toFile().mkdirs();
+ }
+ return hostPath.toString();
+ }
+
+ /**
+ * Get module output directory path where modules should save their
+ * permanent data.
+ *
+ * @return absolute path to the module output directory
+ *
+ * @deprecated Use getModuleDirectory() instead.
+ */
+ @Deprecated
+ public String getModulesOutputDirAbsPath() {
+ return getModuleDirectory();
+ }
+
+ /**
+ * Get relative (with respect to case dir) module output directory path
+ * where modules should save their permanent data. The directory is a
+ * subdirectory of this case dir.
+ *
+ * @return relative path to the module output dir
+ *
+ * @deprecated Use getModuleOutputDirectoryRelativePath() instead
+ */
+ @Deprecated
+ public static String getModulesOutputDirRelPath() {
+ return "ModuleOutput"; //NON-NLS
+ }
+
+ /**
+ * Gets a PropertyChangeSupport object. The PropertyChangeSupport object
+ * returned is not used by instances of this class and does not have any
+ * PropertyChangeListeners.
+ *
+ * @return A new PropertyChangeSupport object.
+ *
+ * @deprecated Do not use.
+ */
+ @Deprecated
+ public static PropertyChangeSupport getPropertyChangeSupport() {
+ return new PropertyChangeSupport(Case.class);
+ }
+
+ /**
+ * Get the data model Content objects in the root of this case's hierarchy.
+ *
+ * @return a list of the root objects
+ *
+ * @throws org.sleuthkit.datamodel.TskCoreException
+ */
+ public List getDataSources() throws TskCoreException {
+ List list = db.getRootObjects();
+ hasData = (list.size() > 0);
+ return list;
}
/**
@@ -855,46 +1214,16 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Get absolute module output directory path where modules should save their
- * permanent data The directory is a subdirectory of this case dir.
+ * Get the name of the index where extracted text is stored for the case.
*
- * @return absolute path to the module output dir
+ * @return Index name.
*/
- public String getModulesOutputDirAbsPath() {
- return this.getCaseDirectory() + File.separator + getModulesOutputDirRelPath();
- }
-
- /**
- * Get relative (with respect to case dir) module output directory path
- * where modules should save their permanent data The directory is a
- * subdirectory of this case dir.
- *
- * @return relative path to the module output dir
- */
- public static String getModulesOutputDirRelPath() {
- return "ModuleOutput"; //NON-NLS
- }
-
- /**
- * get the PropertyChangeSupport of this class
- *
- * @return PropertyChangeSupport
- */
- public static PropertyChangeSupport getPropertyChangeSupport() {
- return pcs;
- }
-
- /**
- * Get the data model Content objects in the root of this case's hierarchy.
- *
- * @return a list of the root objects
- *
- * @throws org.sleuthkit.datamodel.TskCoreException
- */
- public List getDataSources() throws TskCoreException {
- List list = db.getRootObjects();
- hasData = (list.size() > 0);
- return list;
+ public String getTextIndexName() {
+ if (xmlcm == null) {
+ return "";
+ } else {
+ return xmlcm.getTextIndexName();
+ }
}
/**
@@ -918,12 +1247,74 @@ public class Case implements SleuthkitCase.ErrorObserver {
return timezones;
}
+ /**
+ * Adds a subscriber to all case events from this Autopsy node and other
+ * Autopsy nodes. To subscribe to only specific events, use one of the
+ * overloads of addEventSubscriber().
+ *
+ * @param listener The subscriber to add.
+ */
public static synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
- pcs.addPropertyChangeListener(listener);
+ addEventSubscriber(Stream.of(Events.values())
+ .map(Events::toString)
+ .collect(Collectors.toSet()), listener);
}
+ /**
+ * Removes a subscriber from all case events from this Autopsy node and
+ * other Autopsy nodes. To remove a subscription to only specific events,
+ * use one of the overloads of removeEventSubscriber().
+ *
+ * @param listener The subscriber to add.
+ */
public static synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
- pcs.removePropertyChangeListener(listener);
+ removeEventSubscriber(Stream.of(Events.values())
+ .map(Events::toString)
+ .collect(Collectors.toSet()), listener);
+ }
+
+ /**
+ * Adds a subscriber to events from this Autopsy node and other Autopsy
+ * nodes.
+ *
+ * @param eventNames The events the subscriber is interested in.
+ * @param subscriber The subscriber to add.
+ */
+ public static void addEventSubscriber(Set eventNames, PropertyChangeListener subscriber) {
+ eventPublisher.addSubscriber(eventNames, subscriber);
+ }
+
+ /**
+ * Adds a subscriber to events from this Autopsy node and other Autopsy
+ * nodes.
+ *
+ * @param eventName The event the subscriber is interested in.
+ * @param subscriber The subscriber to add.
+ */
+ public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
+ eventPublisher.addSubscriber(eventName, subscriber);
+ }
+
+ /**
+ * Adds a subscriber to events from this Autopsy node and other Autopsy
+ * nodes.
+ *
+ * @param eventName The event the subscriber is no longer interested in.
+ * @param subscriber The subscriber to add.
+ */
+ public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
+ eventPublisher.removeSubscriber(eventName, subscriber);
+ }
+
+ /**
+ * Removes a subscriber to events from this Autopsy node and other Autopsy
+ * nodes.
+ *
+ * @param eventNames The event the subscriber is no longer interested in.
+ * @param subscriber The subscriber to add.
+ */
+ public static void removeEventSubscriber(Set eventNames, PropertyChangeListener subscriber) {
+ eventPublisher.removeSubscriber(eventNames, subscriber);
}
/**
@@ -1015,10 +1406,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
return result;
}
- /*
- * The methods below are used to manage the case directories (creating,
- * checking, deleting, etc)
- */
/**
* to create the case directory
*
@@ -1028,19 +1415,20 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @throws CaseActionException throw if could not create the case dir
* @Deprecated
*/
+ @Deprecated
static void createCaseDirectory(String caseDir, String caseName) throws CaseActionException {
- createCaseDirectory(caseDir);
-
+ createCaseDirectory(caseDir, CaseType.SINGLE_USER_CASE);
}
/**
* Create the case directory and its needed subfolders.
*
- * @param caseDir Path to the case directory (typically base + case name)
+ * @param caseDir Path to the case directory (typically base + case name)
+ * @param caseType The type of case, single-user or multi-user
*
* @throws CaseActionException throw if could not create the case dir
*/
- static void createCaseDirectory(String caseDir) throws CaseActionException {
+ static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
File caseDirF = new File(caseDir);
if (caseDirF.exists()) {
@@ -1061,17 +1449,22 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
// create the folders inside the case directory
- result = result && (new File(caseDir + File.separator + XMLCaseManagement.EXPORT_FOLDER_RELPATH)).mkdir()
- && (new File(caseDir + File.separator + XMLCaseManagement.LOG_FOLDER_RELPATH)).mkdir()
- && (new File(caseDir + File.separator + XMLCaseManagement.TEMP_FOLDER_RELPATH)).mkdir()
- && (new File(caseDir + File.separator + XMLCaseManagement.CACHE_FOLDER_RELPATH)).mkdir();
+ String hostClause = "";
+
+ if (caseType == CaseType.MULTI_USER_CASE) {
+ hostClause = File.separator + NetworkUtils.getLocalHostName();
+ }
+ result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
+ && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
+ && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
+ && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
if (result == false) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
}
- final String modulesOutDir = caseDir + File.separator + getModulesOutputDirRelPath();
+ final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
result = new File(modulesOutDir).mkdir();
if (result == false) {
throw new CaseActionException(
@@ -1079,6 +1472,14 @@ public class Case implements SleuthkitCase.ErrorObserver {
modulesOutDir));
}
+ final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
+ result = new File(reportsOutDir).mkdir();
+ if (result == false) {
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
+ modulesOutDir));
+ }
+
} catch (Exception e) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
@@ -1104,20 +1505,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
StartupWindowProvider.getInstance().open();
}
- /**
- * Call if there are no images in the case. Displays a dialog offering to
- * add one.
- */
- private static void runAddImageAction() {
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- final AddImageAction action = Lookup.getDefault().lookup(AddImageAction.class);
- action.actionPerformed(null);
- }
- });
- }
-
/**
* Checks if a String is a valid case name
*
@@ -1153,7 +1540,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @param openedCase
*/
private static void checkSubFolders(Case openedCase) {
- String modulesOutputDir = openedCase.getModulesOutputDirAbsPath();
+ String modulesOutputDir = openedCase.getModuleDirectory();
File modulesOutputDirF = new File(modulesOutputDir);
if (!modulesOutputDirF.exists()) {
logger.log(Level.INFO, "Creating modules output dir for the case."); //NON-NLS
@@ -1177,34 +1564,63 @@ public class Case implements SleuthkitCase.ErrorObserver {
Case.clearTempFolder();
checkSubFolders(toChangeTo);
- // enable these menus
- CallableSystemAction.get(AddImageAction.class).setEnabled(true);
- CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
- CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
- CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu
+ if (RuntimeProperties.coreComponentsAreActive()) {
+ // enable these menus
+ SwingUtilities.invokeLater(() -> {
+ CallableSystemAction.get(AddImageAction.class).setEnabled(true);
+ CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
+ CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
+ CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu
+ });
- if (toChangeTo.hasData()) {
- // open all top components
- CoreComponentControl.openCoreWindows();
- } else {
- // close all top components
- CoreComponentControl.closeCoreWindows();
+ if (toChangeTo.hasData()) {
+ // open all top components
+ SwingUtilities.invokeLater(() -> {
+ CoreComponentControl.openCoreWindows();
+ });
+ } else {
+ // close all top components
+ SwingUtilities.invokeLater(() -> {
+ CoreComponentControl.closeCoreWindows();
+ });
+ }
}
- } else { // case is closed
- // close all top components first
- CoreComponentControl.closeCoreWindows();
- // disable these menus
- CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu
- CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
- CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu
- CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu
+ if (RuntimeProperties.coreComponentsAreActive()) {
+ SwingUtilities.invokeLater(() -> {
+ updateMainWindowTitle(currentCase.name);
+ });
+ } else {
+ SwingUtilities.invokeLater(() -> {
+ Frame f = WindowManager.getDefault().getMainWindow();
+ f.setTitle(Case.getAppName()); // set the window name to just application name
+ });
+ }
+
+ } else { // case is closed
+ if (RuntimeProperties.coreComponentsAreActive()) {
+
+ SwingUtilities.invokeLater(() -> {
+ // close all top components first
+ CoreComponentControl.closeCoreWindows();
+
+ // disable these menus
+ CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu
+ CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
+ CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu
+ CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu
+ });
+ }
//clear pending notifications
- MessageNotifyUtil.Notify.clear();
+ SwingUtilities.invokeLater(() -> {
+ MessageNotifyUtil.Notify.clear();
+ });
- Frame f = WindowManager.getDefault().getMainWindow();
- f.setTitle(Case.getAppName()); // set the window name to just application name
+ SwingUtilities.invokeLater(() -> {
+ Frame f = WindowManager.getDefault().getMainWindow();
+ f.setTitle(Case.getAppName()); // set the window name to just application name
+ });
//try to force gc to happen
System.gc();
@@ -1217,7 +1633,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
//case name change helper
- private static void doCaseNameChange(String newCaseName) {
+ private static void updateMainWindowTitle(String newCaseName) {
// update case name
if (!newCaseName.equals("")) {
Frame f = WindowManager.getDefault().getMainWindow();
@@ -1225,29 +1641,13 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
}
- //delete image helper
- private void doDeleteImage() {
- // no more image left in this case
- if (currentCase.hasData()) {
- // close all top components
- CoreComponentControl.closeCoreWindows();
- }
- }
-
- @Override
- public void receiveError(String context, String errorMessage) {
- MessageNotifyUtil.Notify.error(context, errorMessage);
- }
-
/**
* Adds a report to the case.
*
- * @param localPath The path of the report file, must be in the case
- * directory or one of its subdirectories.
- * @param sourceModuleName The name of the module that created the report.
- * @param reportName The report name, may be empty.
- *
- * @return A Report data transfer object (DTO) for the new row.
+ * @param localPath The path of the report file, must be in the case
+ * directory or one of its subdirectories.
+ * @param srcModuleName The name of the module that created the report.
+ * @param reportName The report name, may be empty.
*
* @throws TskCoreException
*/
@@ -1260,12 +1660,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
throw new TskCoreException(errorMsg, ex);
}
Report report = this.db.addReport(normalizedLocalPath, srcModuleName, reportName);
- try {
- Case.pcs.firePropertyChange(Events.REPORT_ADDED.toString(), null, report);
- } catch (Exception ex) {
- String errorMessage = String.format("A Case %s listener threw an exception", Events.REPORT_ADDED.toString()); //NON-NLS
- logger.log(Level.SEVERE, errorMessage, ex);
- }
+ eventPublisher.publish(new ReportAddedEvent(report));
}
public List getAllReports() throws TskCoreException {
@@ -1306,13 +1701,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
}
- // fire property change event.
- try {
- Case.pcs.firePropertyChange(Events.REPORT_DELETED.toString(), null, null);
- } catch (Exception ex) {
- String errorMessage = String.format("A Case %s listener threw an exception", Events.REPORT_DELETED.toString()); //NON-NLS
- logger.log(Level.SEVERE, errorMessage, ex);
- }
+ eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), null, null));
}
}
@@ -1331,4 +1720,5 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
return hasData;
}
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java
index 5f953c7bf9..1fc7c79575 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,15 +19,11 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Component;
-import java.awt.EventQueue;
import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.logging.Level;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
+import javax.swing.SwingWorker;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
@@ -37,7 +33,7 @@ import org.openide.util.actions.Presenter;
* The action to close the current Case. This class should be disabled on
* creation and it will be enabled on new case creation or case opened.
*/
-final class CaseCloseAction extends CallableSystemAction implements Presenter.Toolbar {
+public final class CaseCloseAction extends CallableSystemAction implements Presenter.Toolbar {
JButton toolbarButton = new JButton();
@@ -49,13 +45,7 @@ final class CaseCloseAction extends CallableSystemAction implements Presenter.To
putValue(Action.NAME, NbBundle.getMessage(CaseCloseAction.class, "CTL_CaseCloseAct")); // put the action Name
// set action of the toolbar button
- toolbarButton.addActionListener(new ActionListener() {
-
- @Override
- public void actionPerformed(ActionEvent e) {
- CaseCloseAction.this.actionPerformed(e);
- }
- });
+ toolbarButton.addActionListener(CaseCloseAction.this::actionPerformed);
this.setEnabled(false);
}
@@ -71,23 +61,24 @@ final class CaseCloseAction extends CallableSystemAction implements Presenter.To
return;
}
- Case result = Case.getCurrentCase();
+ new SwingWorker() {
- if (!MessageNotifyUtil.Message.confirm("Are you sure you want to close current case?")) {
- return;
- }
-
- try {
- result.closeCase();
- EventQueue.invokeLater(new Runnable() {
- @Override
- public void run() {
- StartupWindowProvider.getInstance().open();
+ @Override
+ protected Void doInBackground() throws Exception {
+ try {
+ Case result = Case.getCurrentCase();
+ result.closeCase();
+ } catch (CaseActionException | IllegalStateException unused) {
+ // Already logged.
}
- });
- } catch (Exception ex) {
- Logger.getLogger(CaseCloseAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS
- }
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ StartupWindowProvider.getInstance().open();
+ }
+ }.execute();
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
new file mode 100644
index 0000000000..b2bbb54397
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
@@ -0,0 +1,136 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import java.nio.file.Path;
+
+/**
+ * Provides access to case metadata.
+ */
+public final class CaseMetadata {
+
+ /**
+ * Exception thrown by the CaseMetadata class when there is a problem
+ * accessing the metadata for a case.
+ */
+ public final static class CaseMetadataException extends Exception {
+
+ private CaseMetadataException(String message) {
+ super(message);
+ }
+
+ private CaseMetadataException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ private final Case.CaseType caseType;
+ private final String caseName;
+ private final String caseNumber;
+ private final String examiner;
+ private final String caseDirectory;
+ private final String caseDatabaseName;
+
+ /**
+ * Constructs an object that provides access to case metadata.
+ *
+ * @param metadataFilePath
+ */
+ public CaseMetadata(Path metadataFilePath) throws CaseMetadataException {
+ try {
+ // NOTE: This class will eventually replace XMLCaseManagement.
+ // This constructor should parse all of the metadata. In the future,
+ // case metadata may be moved into the case database.
+ XMLCaseManagement metadata = new XMLCaseManagement();
+ metadata.open(metadataFilePath.toString());
+ caseType = metadata.getCaseType();
+ caseName = metadata.getCaseName();
+ if (caseName.isEmpty()) {
+ throw new CaseMetadataException("Case name missing");
+ }
+ caseNumber = metadata.getCaseNumber();
+ examiner = metadata.getCaseExaminer();
+ caseDirectory = metadata.getCaseDirectory();
+ if (caseDirectory.isEmpty()) {
+ throw new CaseMetadataException("Case directory missing");
+ }
+ caseDatabaseName = metadata.getDatabaseName();
+ if (Case.CaseType.MULTI_USER_CASE == caseType && caseDatabaseName.isEmpty()) {
+ throw new CaseMetadataException("Case database name missing");
+ }
+ } catch (CaseActionException ex) {
+ throw new CaseMetadataException(ex.getLocalizedMessage(), ex);
+ }
+ }
+
+ /**
+ * Gets the case type.
+ *
+ * @return The case type.
+ */
+ public Case.CaseType getCaseType() {
+ return this.caseType;
+ }
+
+ /**
+ * Gets the case name.
+ *
+ * @return The case name.
+ */
+ public String getCaseName() {
+ return caseName;
+ }
+
+ /**
+ * Gets the case number.
+ *
+ * @return The case number, may be empty.
+ */
+ public String getCaseNumber() {
+ return caseNumber;
+ }
+
+ /**
+ * Gets the examiner.
+ *
+ * @return The examiner, may be empty.
+ */
+ public String getExaminer() {
+ return examiner;
+ }
+
+ /**
+ * Gets the case directory.
+ *
+ * @return The case directory.
+ */
+ public String getCaseDirectory() {
+ return caseDirectory;
+ }
+
+ /**
+ * Gets the case database name.
+ *
+ * @return The case database name, will be empty for a single-user case.
+ */
+ public String getCaseDatabaseName() {
+ return caseDatabaseName;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java
index 125063f4be..4e8243d3ba 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java
@@ -19,6 +19,9 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.event.ActionEvent;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
+import org.openide.util.actions.CallableSystemAction;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.ServiceProvider;
@@ -28,7 +31,7 @@ import org.openide.util.lookup.ServiceProvider;
* @author jantonius
*/
@ServiceProvider(service = CaseNewActionInterface.class)
-public final class CaseNewAction implements CaseNewActionInterface {
+public final class CaseNewAction extends CallableSystemAction implements CaseNewActionInterface {
private NewCaseWizardAction wizard = SystemAction.get(NewCaseWizardAction.class);
@@ -41,4 +44,18 @@ public final class CaseNewAction implements CaseNewActionInterface {
public void actionPerformed(ActionEvent e) {
wizard.performAction();
}
+
+ @Override
+ public void performAction() {
+ }
+
+ @Override
+ public String getName() {
+ return NbBundle.getMessage(CaseNewAction.class, "CTL_CaseNewAction");
+ }
+
+ @Override
+ public HelpCtx getHelpCtx() {
+ return HelpCtx.DEFAULT_HELP;
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java
index f6bb0070dd..7cea2ef29e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,97 +18,87 @@
*/
package org.sleuthkit.autopsy.casemodule;
-import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
-import java.util.logging.Level;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
+import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
-import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.Version;
/**
- * The action to open a existing case. This class is always enabled.
+ * An action that opens an existing case.
*/
@ServiceProvider(service = CaseOpenAction.class)
public final class CaseOpenAction implements ActionListener {
- private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName());
private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS
- private final JFileChooser fc = new JFileChooser();
- private FileFilter autFilter;
+ private final JFileChooser fileChooser = new JFileChooser();
+ private final FileFilter caseMetadataFileFilter;
/**
- * The constructor
+ * Constructs an action that opens an existing case.
*/
public CaseOpenAction() {
- autFilter = new FileNameExtensionFilter(
- NbBundle.getMessage(CaseOpenAction.class, "CaseOpenAction.autFilter.title", Version.getName(),
- Case.CASE_DOT_EXTENSION),
- Case.CASE_EXTENSION);
- fc.setDragEnabled(false);
- fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
- fc.setMultiSelectionEnabled(false);
- fc.setFileFilter(autFilter);
- try {
- if (ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE) != null) {
- fc.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); //NON-NLS
- }
- } catch (Exception e) {
+ caseMetadataFileFilter = new FileNameExtensionFilter(NbBundle.getMessage(CaseOpenAction.class, "CaseOpenAction.autFilter.title", Version.getName(), Case.CASE_DOT_EXTENSION), Case.CASE_EXTENSION);
+ fileChooser.setDragEnabled(false);
+ 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
}
}
/**
- * Pop-up the File Chooser to open the existing case (.aut file)
+ * Pops up a file chooser to allow the user to select a case meta data file
+ * (.aut file) and attempts to open the case described by the file.
*
- * @param e the action event
+ * @param e The action event.
*/
@Override
public void actionPerformed(ActionEvent e) {
- int retval = fc.showOpenDialog((Component) e.getSource());
-
+ /**
+ * Pop up a file chooser to allow the user to select a case meta data
+ * file (.aut file)
+ */
+ int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow());
if (retval == JFileChooser.APPROVE_OPTION) {
- String path = fc.getSelectedFile().getPath();
- String dirPath = fc.getSelectedFile().getParent();
- ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator)));
- // check if the file exists
- if (!new File(path).exists()) {
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "CaseOpenAction.msgDlg.fileNotExist.msg"),
- NbBundle.getMessage(this.getClass(),
- "CaseOpenAction.msgDlg.fileNotExist.title"),
- JOptionPane.ERROR_MESSAGE);
- this.actionPerformed(e); // show the dialog box again
- } else {
- // try to close Startup window if there's one
- try {
- StartupWindowProvider.getInstance().close();
- } catch (Exception ex) {
- // no need to show the error message to the user.
- logger.log(Level.WARNING, "Error closing startup window.", ex); //NON-NLS
- }
- try {
- Case.open(path); // open the case
- } catch (CaseActionException ex) {
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(this.getClass(),
- "CaseOpenAction.msgDlg.cantOpenCase.msg", path,
- ex.getMessage()),
- NbBundle.getMessage(this.getClass(),
- "CaseOpenAction.msgDlg.cantOpenCase.title"),
- JOptionPane.ERROR_MESSAGE);
- logger.log(Level.WARNING, "Error opening case in folder " + path, ex); //NON-NLS
-
- StartupWindowProvider.getInstance().open();
- }
+ /**
+ * This is a bit of a hack, but close the startup window, if it was
+ * the source of the action invocation.
+ */
+ try {
+ StartupWindowProvider.getInstance().close();
+ } catch (Exception unused) {
}
+
+ /**
+ * Try to open the caswe associated with the case meta data file the
+ * user selected.
+ */
+ final String path = fileChooser.getSelectedFile().getPath();
+ String dirPath = fileChooser.getSelectedFile().getParent();
+ ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, dirPath.substring(0, dirPath.lastIndexOf(File.separator)));
+ new Thread(() -> {
+ try {
+ Case.open(path);
+ } catch (CaseActionException ex) {
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null, ex.getMessage(), NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE);
+ if (!Case.isCaseOpen()) {
+ StartupWindowProvider.getInstance().open();
+ }
+ });
+ }
+ }).start();
}
}
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd b/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
index 30a86d859a..63c9aa49df 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
@@ -12,6 +12,12 @@
+
+
+
+
+
+
@@ -92,6 +98,11 @@
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java
new file mode 100644
index 0000000000..4ea3463acc
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java
@@ -0,0 +1,601 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.Serializable;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.progress.ProgressHandleFactory;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent;
+import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent;
+import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.NetworkUtils;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+import org.sleuthkit.autopsy.events.AutopsyEventException;
+import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
+import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisStartedEvent;
+
+/**
+ * A collaboration monitor listens to local events and translates them into
+ * collaboration tasks that are broadcast to collaborating nodes and informs the
+ * user of collaboration tasks on other nodes using progress bars.
+ */
+final class CollaborationMonitor {
+
+ private static final String EVENT_CHANNEL_NAME = "%s-Collaboration-Monitor-Events";
+ private static final String COLLABORATION_MONITOR_EVENT = "COLLABORATION_MONITOR_EVENT";
+ private static final Set CASE_EVENTS_OF_INTEREST = new HashSet<>(Arrays.asList(new String[]{Case.Events.ADDING_DATA_SOURCE.toString(), Case.Events.DATA_SOURCE_ADDED.toString(), Case.Events.ADDING_DATA_SOURCE_FAILED.toString()}));
+ private static final int NUMBER_OF_PERIODIC_TASK_THREADS = 2;
+ private static final String PERIODIC_TASK_THREAD_NAME = "collab-monitor-periodic-tasks-%d";
+ private static final long HEARTBEAT_INTERVAL_MINUTES = 1;
+ private static final long MAX_MISSED_HEARTBEATS = 5;
+ private static final long STALE_TASKS_DETECTION_INTERVAL_MINUTES = 2;
+ private static final long EXECUTOR_TERMINATION_WAIT_SECS = 30;
+ private static final Logger logger = Logger.getLogger(CollaborationMonitor.class.getName());
+ private final String hostName;
+ private final LocalTasksManager localTasksManager;
+ private final RemoteTasksManager remoteTasksManager;
+ private final AutopsyEventPublisher eventPublisher;
+ private final ScheduledThreadPoolExecutor periodicTasksExecutor;
+
+ /**
+ * Constructs a collaboration monitor that listens to local events and
+ * translates them into collaboration tasks that are broadcast to
+ * collaborating nodes, informs the user of collaboration tasks on other
+ * nodes using progress bars, and monitors the health of key collaboration
+ * services.
+ */
+ CollaborationMonitor() throws CollaborationMonitorException {
+ /**
+ * Get the local host name so it can be used to identify the source of
+ * collaboration tasks broadcast by this node.
+ */
+ hostName = NetworkUtils.getLocalHostName();
+
+ /**
+ * Create an event publisher that will be used to communicate with
+ * collaboration monitors on other nodes working on the case.
+ */
+ eventPublisher = new AutopsyEventPublisher();
+ try {
+ Case openedCase = Case.getCurrentCase();
+ String channelPrefix = openedCase.getTextIndexName();
+ eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, channelPrefix));
+ } catch (AutopsyEventException ex) {
+ throw new CollaborationMonitorException("Failed to initialize", ex);
+ }
+
+ /**
+ * Create a remote tasks manager to track and display the progress of
+ * remote tasks.
+ */
+ remoteTasksManager = new RemoteTasksManager();
+ eventPublisher.addSubscriber(COLLABORATION_MONITOR_EVENT, remoteTasksManager);
+
+ /**
+ * Create a local tasks manager to track and broadcast local tasks.
+ */
+ localTasksManager = new LocalTasksManager();
+ IngestManager.getInstance().addIngestJobEventListener(localTasksManager);
+ Case.addEventSubscriber(CASE_EVENTS_OF_INTEREST, localTasksManager);
+
+ /**
+ * Start periodic tasks that:
+ *
+ * 1. Send heartbeats to collaboration monitors on other nodes.
+ * 2. Check for stale remote tasks.
+ */
+ periodicTasksExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_PERIODIC_TASK_THREADS, new ThreadFactoryBuilder().setNameFormat(PERIODIC_TASK_THREAD_NAME).build());
+ periodicTasksExecutor.scheduleAtFixedRate(new HeartbeatTask(), HEARTBEAT_INTERVAL_MINUTES, HEARTBEAT_INTERVAL_MINUTES, TimeUnit.MINUTES);
+ periodicTasksExecutor.scheduleAtFixedRate(new StaleTaskDetectionTask(), STALE_TASKS_DETECTION_INTERVAL_MINUTES, STALE_TASKS_DETECTION_INTERVAL_MINUTES, TimeUnit.MINUTES);
+ }
+
+ /**
+ * Shuts down this collaboration monitor.
+ */
+ void shutdown() {
+ if (null != periodicTasksExecutor) {
+ periodicTasksExecutor.shutdownNow();
+ try {
+ while (!periodicTasksExecutor.awaitTermination(EXECUTOR_TERMINATION_WAIT_SECS, TimeUnit.SECONDS)) {
+ logger.log(Level.WARNING, "Waited at least {0} seconds for periodic tasks executor to shut down, continuing to wait", EXECUTOR_TERMINATION_WAIT_SECS); //NON-NLS
+ }
+ } catch (InterruptedException ex) {
+ logger.log(Level.SEVERE, "Unexpected interrupt while stopping periodic tasks executor", ex); //NON-NLS
+ }
+ }
+
+ Case.removeEventSubscriber(CASE_EVENTS_OF_INTEREST, localTasksManager);
+ IngestManager.getInstance().removeIngestJobEventListener(localTasksManager);
+
+ if (null != eventPublisher) {
+ eventPublisher.removeSubscriber(COLLABORATION_MONITOR_EVENT, remoteTasksManager);
+ eventPublisher.closeRemoteEventChannel();
+ }
+
+ remoteTasksManager.shutdown();
+ }
+
+ /**
+ * The local tasks manager listens to local events and translates them into
+ * tasks it broadcasts to collaborating nodes. Note that all access to the
+ * task collections is synchronized since they may be accessed by both the
+ * threads publishing property change events and by the heartbeat task
+ * thread.
+ */
+ private final class LocalTasksManager implements PropertyChangeListener {
+
+ private long nextTaskId;
+ private final Map uuidsToAddDataSourceTasks;
+ private final Map jobIdsTodataSourceAnalysisTasks;
+
+ /**
+ * Constructs a local tasks manager that listens to local events and
+ * translates them into tasks that can be broadcast to collaborating
+ * nodes.
+ */
+ LocalTasksManager() {
+ nextTaskId = 0;
+ uuidsToAddDataSourceTasks = new HashMap<>();
+ jobIdsTodataSourceAnalysisTasks = new HashMap<>();
+ }
+
+ /**
+ * Translates events into updates of the collection of local tasks this
+ * node is broadcasting to other nodes.
+ *
+ * @param event A PropertyChangeEvent.
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ if (AutopsyEvent.SourceType.LOCAL == ((AutopsyEvent) event).getSourceType()) {
+ String eventName = event.getPropertyName();
+ if (eventName.equals(Case.Events.ADDING_DATA_SOURCE.toString())) {
+ addDataSourceAddTask((AddingDataSourceEvent) event);
+ } else if (eventName.equals(Case.Events.ADDING_DATA_SOURCE_FAILED.toString())) {
+ removeDataSourceAddTask(((AddingDataSourceFailedEvent) event).getDataSourceId());
+ } else if (eventName.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
+ removeDataSourceAddTask(((DataSourceAddedEvent) event).getDataSourceId());
+ } else if (eventName.equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_STARTED.toString())) {
+ addDataSourceAnalysisTask((DataSourceAnalysisStartedEvent) event);
+ } else if (eventName.equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())) {
+ removeDataSourceAnalysisTask((DataSourceAnalysisCompletedEvent) event);
+ }
+ }
+ }
+
+ /**
+ * Adds an adding data source task to the collection of local tasks and
+ * publishes the updated collection to any collaborating nodes.
+ *
+ * @param event An adding data source event.
+ */
+ synchronized void addDataSourceAddTask(AddingDataSourceEvent event) {
+ String status = NbBundle.getMessage(CollaborationMonitor.class, "CollaborationMonitor.addingDataSourceStatus.msg", hostName);
+ uuidsToAddDataSourceTasks.put(event.getDataSourceId().hashCode(), new Task(++nextTaskId, status));
+ eventPublisher.publishRemotely(new CollaborationEvent(hostName, getCurrentTasks()));
+ }
+
+ /**
+ * Removes an adding data source task from the collection of local tasks
+ * and publishes the updated collection to any collaborating nodes.
+ *
+ * @param dataSourceId A data source id to pair a data source added or
+ * adding data source failed event with an adding
+ * data source event.
+ */
+ synchronized void removeDataSourceAddTask(UUID dataSourceId) {
+ uuidsToAddDataSourceTasks.remove(dataSourceId.hashCode());
+ eventPublisher.publishRemotely(new CollaborationEvent(hostName, getCurrentTasks()));
+ }
+
+ /**
+ * Adds a data source analysis task to the collection of local tasks and
+ * publishes the updated collection to any collaborating nodes.
+ *
+ * @param event A data source analysis started event.
+ */
+ synchronized void addDataSourceAnalysisTask(DataSourceAnalysisStartedEvent event) {
+ String status = NbBundle.getMessage(CollaborationMonitor.class, "CollaborationMonitor.analyzingDataSourceStatus.msg", hostName, event.getDataSource().getName());
+ jobIdsTodataSourceAnalysisTasks.put(event.getDataSourceIngestJobId(), new Task(++nextTaskId, status));
+ eventPublisher.publishRemotely(new CollaborationEvent(hostName, getCurrentTasks()));
+ }
+
+ /**
+ * Removes a data source analysis task from the collection of local
+ * tasks and publishes the updated collection to any collaborating
+ * nodes.
+ *
+ * @param event A data source analysis completed event.
+ */
+ synchronized void removeDataSourceAnalysisTask(DataSourceAnalysisCompletedEvent event) {
+ jobIdsTodataSourceAnalysisTasks.remove(event.getDataSourceIngestJobId());
+ eventPublisher.publishRemotely(new CollaborationEvent(hostName, getCurrentTasks()));
+ }
+
+ /**
+ * Gets the current local tasks.
+ *
+ * @return A mapping of task IDs to tasks, may be empty.
+ */
+ synchronized Map getCurrentTasks() {
+ Map currentTasks = new HashMap<>();
+ uuidsToAddDataSourceTasks.values().stream().forEach((task) -> {
+ currentTasks.put(task.getId(), task);
+ });
+ jobIdsTodataSourceAnalysisTasks.values().stream().forEach((task) -> {
+ currentTasks.put(task.getId(), task);
+ });
+ return currentTasks;
+ }
+ }
+
+ /**
+ * Listens for collaboration event messages broadcast by collaboration
+ * monitors on other nodes and translates them into remote tasks represented
+ * locally using progress bars. Note that all access to the remote tasks is
+ * synchronized since it may be accessed by both the threads publishing
+ * property change events and by the thread running periodic checks for
+ * "stale" tasks.
+ */
+ private final class RemoteTasksManager implements PropertyChangeListener {
+
+ private final Map hostsToTasks;
+
+ /**
+ * Constructs an object that listens for collaboration event messages
+ * broadcast by collaboration monitors on other nodes and translates
+ * them into remote tasks represented locally using progress bars.
+ */
+ RemoteTasksManager() {
+ hostsToTasks = new HashMap<>();
+ }
+
+ /**
+ * Updates the remote tasks in response to a collaboration event
+ * received from another node.
+ *
+ * @param event The collaboration event.
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ if (event.getPropertyName().equals(COLLABORATION_MONITOR_EVENT)) {
+ updateTasks((CollaborationEvent) event);
+ }
+ }
+
+ /**
+ * Finishes the progress bars for all remote tasks.
+ */
+ synchronized void shutdown() {
+ finishAllTasks();
+ }
+
+ /**
+ * Updates the remote tasks to reflect a collaboration event received
+ * from another node.
+ *
+ * @param event The collaboration event.
+ */
+ synchronized void updateTasks(CollaborationEvent event) {
+ RemoteTasks tasksForHost = hostsToTasks.get(event.getHostName());
+ if (null != tasksForHost) {
+ tasksForHost.update(event);
+ } else {
+ hostsToTasks.put(event.getHostName(), new RemoteTasks(event));
+ }
+ }
+
+ /**
+ * Finishes the progress bars any remote tasks that have gone stale,
+ * i.e., tasks for which updates have ceased, presumably because the
+ * collaborating node has gone down or there is a network issue.
+ */
+ synchronized void finishStaleTasks() {
+ for (Iterator> it = hostsToTasks.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = it.next();
+ RemoteTasks tasksForHost = entry.getValue();
+ if (tasksForHost.isStale()) {
+ tasksForHost.finishAllTasks();
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Finishes the progress bars for all remote tasks.
+ */
+ synchronized void finishAllTasks() {
+ for (Iterator> it = hostsToTasks.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = it.next();
+ RemoteTasks tasksForHost = entry.getValue();
+ tasksForHost.finishAllTasks();
+ it.remove();
+ }
+ }
+
+ /**
+ * A collection of progress bars for tasks on a collaborating node.
+ */
+ private final class RemoteTasks {
+
+ private final long MAX_MINUTES_WITHOUT_UPDATE = HEARTBEAT_INTERVAL_MINUTES * MAX_MISSED_HEARTBEATS;
+ private Instant lastUpdateTime;
+ private Map taskIdsToProgressBars;
+
+ /**
+ * Construct a set of progress bars to represent remote tasks for a
+ * particular host.
+ *
+ * @param event A collaboration event.
+ */
+ RemoteTasks(CollaborationEvent event) {
+ /**
+ * Set the initial value of the last update time stamp.
+ */
+ lastUpdateTime = Instant.now();
+
+ taskIdsToProgressBars = new HashMap<>();
+ event.getCurrentTasks().values().stream().forEach((task) -> {
+ ProgressHandle progress = ProgressHandleFactory.createHandle(event.getHostName());
+ progress.start();
+ progress.progress(task.getStatus());
+ taskIdsToProgressBars.put(task.getId(), progress);
+ });
+ }
+
+ /**
+ * Updates this remote tasks collection.
+ *
+ * @param event A collaboration event from the collaborating node
+ * associated with these tasks.
+ */
+ void update(CollaborationEvent event) {
+ /**
+ * Update the last update timestamp.
+ */
+ lastUpdateTime = Instant.now();
+
+ /**
+ * Create or update the progress bars for the current tasks of
+ * the node that published the event.
+ */
+ Map remoteTasks = event.getCurrentTasks();
+ remoteTasks.values().stream().forEach((task) -> {
+ ProgressHandle progress = taskIdsToProgressBars.get(task.getId());
+ if (null != progress) {
+ /**
+ * Update the existing progress bar.
+ */
+ progress.progress(task.getStatus());
+ } else {
+ /**
+ * A new task, create a progress bar.
+ */
+ progress = ProgressHandleFactory.createHandle(event.getHostName());
+ progress.start();
+ progress.progress(task.getStatus());
+ taskIdsToProgressBars.put(task.getId(), progress);
+ }
+ });
+
+ /**
+ * If a task is no longer in the task list from the remote node,
+ * it is finished. Remove the progress bars for finished tasks.
+ */
+ for (Iterator> iterator = taskIdsToProgressBars.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry entry = iterator.next();
+ if (!remoteTasks.containsKey(entry.getKey())) {
+ ProgressHandle progress = entry.getValue();
+ progress.finish();
+ iterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Unconditionally finishes the entire set or remote tasks. To be
+ * used when a host drops off unexpectedly.
+ */
+ void finishAllTasks() {
+ taskIdsToProgressBars.values().stream().forEach((progress) -> {
+ progress.finish();
+ });
+ taskIdsToProgressBars.clear();
+ }
+
+ /**
+ * Determines whether or not the time since the last update of this
+ * remote tasks collection is greater than the maximum acceptable
+ * interval between updates.
+ *
+ * @return True or false.
+ */
+ boolean isStale() {
+ return Duration.between(lastUpdateTime, Instant.now()).toMinutes() >= MAX_MINUTES_WITHOUT_UPDATE;
+ }
+ }
+
+ }
+
+ /**
+ * A Runnable task that periodically publishes the local tasks in progress
+ * on this node, providing a heartbeat message for collaboration monitors on
+ * other nodes. The current local tasks are included in the heartbeat
+ * message so that nodes that have just joined the event channel know what
+ * this node is doing, even if they join after the current tasks are begun.
+ */
+ private final class HeartbeatTask implements Runnable {
+
+ /**
+ * Publish a heartbeat message.
+ */
+ @Override
+ public void run() {
+ eventPublisher.publishRemotely(new CollaborationEvent(hostName, localTasksManager.getCurrentTasks()));
+ }
+ }
+
+ /**
+ * A Runnable task that periodically deals with any remote tasks that have
+ * gone stale, i.e., tasks for which updates have ceased, presumably because
+ * the collaborating node has gone down or there is a network issue.
+ */
+ private final class StaleTaskDetectionTask implements Runnable {
+
+ /**
+ * Check for stale remote tasks and clean them up, if found.
+ */
+ @Override
+ public void run() {
+ remoteTasksManager.finishStaleTasks();
+ }
+ }
+
+ /**
+ * An Autopsy event to be sent in event messages to the collaboration
+ * monitors on other Autopsy nodes.
+ */
+ private final static class CollaborationEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private final String hostName;
+ private final Map currentTasks;
+
+ /**
+ * Constructs an Autopsy event to be sent in event messages to the
+ * collaboration monitors on other Autopsy nodes.
+ *
+ * @param hostName The name of the host sending the event.
+ * @param currentTasks The tasks in progress for this Autopsy node.
+ */
+ CollaborationEvent(String hostName, Map currentTasks) {
+ super(COLLABORATION_MONITOR_EVENT, null, null);
+ this.hostName = hostName;
+ this.currentTasks = currentTasks;
+ }
+
+ /**
+ * Gets the host name of the Autopsy node that published this event.
+ *
+ * @return The host name.
+ */
+ String getHostName() {
+ return hostName;
+ }
+
+ /**
+ * Gets the current tasks for the Autopsy node that published this
+ * event.
+ *
+ * @return A mapping of task IDs to current tasks
+ */
+ Map getCurrentTasks() {
+ return currentTasks;
+ }
+
+ }
+
+ /**
+ * A representation of a task in progress on this Autopsy node.
+ */
+ private final static class Task implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private final long id;
+ private final String status;
+
+ /**
+ * Constructs a representation of a task in progress on this Autopsy
+ * node.
+ *
+ * @param id
+ * @param status
+ */
+ Task(long id, String status) {
+ this.id = id;
+ this.status = status;
+ }
+
+ /**
+ * Gets ID of this task.
+ *
+ * @return A task id, unique to this task for this case and this Autopsy
+ * node.
+ */
+ long getId() {
+ return id;
+ }
+
+ /**
+ * Gets the status of the task at the time this object was constructed.
+ *
+ * @return A task status string.
+ */
+ String getStatus() {
+ return status;
+ }
+ }
+
+ /**
+ * Custom exception class for the collaboration monitor.
+ */
+ final static class CollaborationMonitorException extends Exception {
+
+ /**
+ * Constructs and instance of the custom exception class for the
+ * collaboration monitor.
+ *
+ * @param message Exception message.
+ */
+ CollaborationMonitorException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs and instance of the custom exception class for the
+ * collaboration monitor.
+ *
+ * @param message Exception message.
+ * @param throwable Exception cause.
+ */
+ CollaborationMonitorException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java
index 793629da66..440d0b4d20 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,16 +18,18 @@
*/
package org.sleuthkit.autopsy.casemodule;
-import java.awt.*;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
+import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
+import javax.swing.KeyStroke;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
@@ -42,7 +44,6 @@ public class CueBannerPanel extends javax.swing.JPanel {
// for error handling
private static JPanel caller = new JPanel();
- private String className = this.getClass().toString();
public CueBannerPanel() {
initComponents();
@@ -209,6 +210,10 @@ public class CueBannerPanel extends javax.swing.JPanel {
// set the location of the popUp Window on the center of the screen
recentCasesWindow.setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2);
+ recentCasesWindow.setLocationRelativeTo(this);
+ recentCasesWindow.getRootPane().registerKeyboardAction(e -> {
+ recentCasesWindow.dispose();
+ }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
OpenRecentCasePanel welcomeWindow = OpenRecentCasePanel.getInstance();
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form
index b4dbaafd63..bfccc9bdd9 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form
@@ -43,6 +43,7 @@
+
@@ -57,7 +58,9 @@
-
+
+
+
@@ -66,7 +69,7 @@
-
+
@@ -131,5 +134,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java
index a1a285ede9..4d59cd4d91 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java
@@ -37,6 +37,7 @@ import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.PathValidator;
/**
* ImageTypePanel for adding an image file such as .img, .E0x, .00x, etc.
@@ -65,6 +66,8 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
fc.setMultiSelectionEnabled(false);
+ errorLabel.setVisible(false);
+
boolean firstFilter = true;
for (FileFilter filter : fileChooserFilters) {
if (firstFilter) { // set the first on the list as the default selection
@@ -115,6 +118,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
timeZoneComboBox = new javax.swing.JComboBox();
noFatOrphansCheckbox = new javax.swing.JCheckBox();
descLabel = new javax.swing.JLabel();
+ errorLabel = new javax.swing.JLabel();
setMinimumSize(new java.awt.Dimension(0, 65));
setPreferredSize(new java.awt.Dimension(403, 65));
@@ -139,6 +143,9 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.descLabel.text")); // NOI18N
+ errorLabel.setForeground(new java.awt.Color(255, 0, 0));
+ org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.errorLabel.text")); // NOI18N
+
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@@ -158,7 +165,8 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
.addComponent(noFatOrphansCheckbox)
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
- .addComponent(descLabel)))
+ .addComponent(descLabel))
+ .addComponent(errorLabel))
.addGap(0, 20, Short.MAX_VALUE))
);
layout.setVerticalGroup(
@@ -169,7 +177,9 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(browseButton)
.addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addGap(18, 18, 18)
+ .addGap(3, 3, 3)
+ .addComponent(errorLabel)
+ .addGap(1, 1, 1)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(timeZoneLabel)
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
@@ -177,7 +187,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
.addComponent(noFatOrphansCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(descLabel)
- .addContainerGap(13, Short.MAX_VALUE))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
@@ -208,6 +218,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton browseButton;
private javax.swing.JLabel descLabel;
+ private javax.swing.JLabel errorLabel;
private javax.swing.JCheckBox noFatOrphansCheckbox;
private javax.swing.JLabel pathLabel;
private javax.swing.JTextField pathTextField;
@@ -252,11 +263,15 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
* @return true if a proper image has been selected, false otherwise
*/
public boolean validatePanel() {
+ errorLabel.setVisible(false);
String path = getContentPaths();
if (path == null || path.isEmpty()) {
return false;
}
+ // display warning if there is one (but don't disable "next" button)
+ warnIfPathIsInvalid(path);
+
boolean isExist = Case.pathExists(path);
boolean isPhysicalDrive = Case.isPhysicalDrive(path);
boolean isPartition = Case.isPartition(path);
@@ -264,6 +279,19 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
return (isExist || isPhysicalDrive || isPartition);
}
+ /**
+ * Validates path to selected data source and displays warning if it is
+ * invalid.
+ *
+ * @param path Absolute path to the selected data source
+ */
+ private void warnIfPathIsInvalid(String path) {
+ if (!PathValidator.isValid(path, Case.getCurrentCase().getCaseType())) {
+ errorLabel.setVisible(true);
+ errorLabel.setText(NbBundle.getMessage(this.getClass(), "DataSourceOnCDriveError.text"));
+ }
+ }
+
public void storeSettings() {
String imagePathName = getContentPaths();
if (null != imagePathName) {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImportDoneCallback.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImportDoneCallback.java
new file mode 100644
index 0000000000..bb83712cc3
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImportDoneCallback.java
@@ -0,0 +1,6 @@
+package org.sleuthkit.autopsy.casemodule;
+
+public interface ImportDoneCallback {
+
+ void importDoneCallback(boolean result, String resultString);
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java b/Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java
new file mode 100644
index 0000000000..52ea056333
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java
@@ -0,0 +1,92 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
+
+/**
+ * This class enables capturing errors and batching them for reporting on a
+ * no-more-than-x number of seconds basis. When created, you specify the minimum
+ * time between user notifications. When the time between notifications has
+ * expired, the next error encountered will cause a report to be shown to the
+ * user.
+ */
+class IntervalErrorReportData {
+
+ private final Case currentCase;
+ private long newProblems;
+ private long totalProblems;
+ private long lastReportedDate;
+ private final int milliSecondsBetweenReports;
+ private final String message;
+
+ /**
+ * Create a new IntervalErrorReprotData instance and subscribe for TSK error
+ * notifications for the current case.
+ *
+ * @param currentCase Case for which TSK errors should be tracked
+ * and displayed.
+ * @param secondsBetweenReports Minimum number of seconds between reports.
+ * It will not warn more frequently than this.
+ * @param message The message that will be shown when warning
+ * the user
+ */
+ IntervalErrorReportData(Case currentCase, int secondsBetweenReports, String message) {
+ this.newProblems = 0;
+ this.totalProblems = 0;
+ this.lastReportedDate = 0; // arm the first warning by choosing zero
+ this.milliSecondsBetweenReports = secondsBetweenReports * 1000; // convert to milliseconds
+ this.message = message;
+ this.currentCase = currentCase;
+ this.currentCase.getSleuthkitCase().addErrorObserver(this.currentCase);
+ }
+
+ /**
+ * Un-subscribe from TSK error notifications for current case.
+ */
+ void shutdown() {
+ this.currentCase.getSleuthkitCase().removeErrorObserver(this.currentCase);
+ }
+
+ /**
+ * Call this to add problems to the class. When the time threshold is met
+ * (or if this is the first problem encountered), a warning will be shown to
+ * the user.
+ *
+ * @param context The context in which the error occurred.
+ * @param errorMessage A description of the error that occurred.
+ */
+ void addProblems(String context, String errorMessage) {
+ this.newProblems += 1;
+ this.totalProblems += newProblems;
+
+ long currentTimeStamp = System.currentTimeMillis();
+ if ((currentTimeStamp - lastReportedDate) > milliSecondsBetweenReports) {
+ this.lastReportedDate = currentTimeStamp;
+ MessageNotifyUtil.Notify.error(message, context + ", " + errorMessage + " "
+ + this.newProblems + " "
+ + NbBundle.getMessage(IntervalErrorReportData.class, "IntervalErrorReport.NewIssues")
+ + " " + this.totalProblems + " "
+ + NbBundle.getMessage(IntervalErrorReportData.class, "IntervalErrorReport.TotalIssues")
+ + ".");
+ this.newProblems = 0;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form
index d71086457c..55707d9f0c 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form
@@ -61,7 +61,7 @@
-
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java
index 4502a8dde5..669ffad552 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java
@@ -93,9 +93,9 @@ final class LocalDiskPanel extends JPanel {
diskComboBox.setModel(model);
diskComboBox.setRenderer(model);
+ errorLabel.setVisible(false);
errorLabel.setText("");
diskComboBox.setEnabled(false);
-
}
/**
@@ -167,7 +167,7 @@ final class LocalDiskPanel extends JPanel {
.addComponent(noFatOrphansCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(descLabel)
- .addContainerGap(21, Short.MAX_VALUE))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.form
index cf08c9c38f..eb9d6182bb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.form
@@ -60,6 +60,10 @@
+
+
+
+
@@ -67,15 +71,16 @@
-
+
+
-
+
-
-
+
+
@@ -136,5 +141,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java
index 15b042bbc1..eae470e0b4 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java
@@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.casemodule;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
+import java.util.Arrays;
+import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.JFileChooser;
@@ -30,7 +32,9 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import java.util.logging.Level;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.PathValidator;
/**
* Add input wizard subpanel for adding local files / dirs to the case
@@ -61,8 +65,8 @@ class LocalFilesPanel extends JPanel {
private void customInit() {
localFileChooser.setMultiSelectionEnabled(true);
+ errorLabel.setVisible(false);
selectedPaths.setText("");
-
}
//@Override
@@ -93,9 +97,35 @@ class LocalFilesPanel extends JPanel {
//@Override
public boolean validatePanel() {
+
+ // display warning if there is one (but don't disable "next" button)
+ warnIfPathIsInvalid(getContentPaths());
+
return enableNext;
}
+ /**
+ * Validates path to selected data source and displays warning if it is
+ * invalid.
+ *
+ * @param path Absolute path to the selected data source
+ */
+ private void warnIfPathIsInvalid(String path) {
+ errorLabel.setVisible(false);
+
+ // Path variable for "Local files" module is a coma separated string containg multiple paths
+ List pathsList = Arrays.asList(path.split(","));
+ CaseType currentCaseType = Case.getCurrentCase().getCaseType();
+
+ for (String currentPath : pathsList) {
+ if (!PathValidator.isValid(currentPath, currentCaseType)) {
+ errorLabel.setVisible(true);
+ errorLabel.setText(NbBundle.getMessage(this.getClass(), "DataSourceOnCDriveError.text"));
+ return;
+ }
+ }
+ }
+
//@Override
public void select() {
reset();
@@ -106,8 +136,7 @@ class LocalFilesPanel extends JPanel {
currentFiles.clear();
selectedPaths.setText("");
enableNext = false;
-
- //pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true);
+ errorLabel.setVisible(false);
}
@Override
@@ -150,6 +179,7 @@ class LocalFilesPanel extends JPanel {
clearButton = new javax.swing.JButton();
jScrollPane2 = new javax.swing.JScrollPane();
selectedPaths = new javax.swing.JTextArea();
+ errorLabel = new javax.swing.JLabel();
localFileChooser.setApproveButtonText(org.openide.util.NbBundle.getMessage(LocalFilesPanel.class, "LocalFilesPanel.localFileChooser.approveButtonText")); // NOI18N
localFileChooser.setApproveButtonToolTipText(org.openide.util.NbBundle.getMessage(LocalFilesPanel.class, "LocalFilesPanel.localFileChooser.approveButtonToolTipText")); // NOI18N
@@ -185,6 +215,9 @@ class LocalFilesPanel extends JPanel {
selectedPaths.setToolTipText(org.openide.util.NbBundle.getMessage(LocalFilesPanel.class, "LocalFilesPanel.selectedPaths.toolTipText")); // NOI18N
jScrollPane2.setViewportView(selectedPaths);
+ errorLabel.setForeground(new java.awt.Color(255, 0, 0));
+ org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(LocalFilesPanel.class, "LocalFilesPanel.errorLabel.text")); // NOI18N
+
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@@ -199,19 +232,23 @@ class LocalFilesPanel extends JPanel {
.addComponent(selectButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(clearButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGap(2, 2, 2))
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(errorLabel)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(infoLabel)
.addGap(5, 5, 5)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
.addComponent(selectButton)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 17, Short.MAX_VALUE)
- .addComponent(clearButton))
- .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
- .addGap(0, 0, 0))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(clearButton)))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(errorLabel))
);
}// //GEN-END:initComponents
@@ -256,6 +293,7 @@ class LocalFilesPanel extends JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton clearButton;
+ private javax.swing.JLabel errorLabel;
private javax.swing.JLabel infoLabel;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JScrollPane jScrollPane2;
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form
index d730da08dc..8225797395 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form
@@ -112,10 +112,15 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -127,7 +132,9 @@
-
+
+
+
@@ -153,6 +160,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java
index 18eda3fffc..d5e5e647e5 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java
@@ -137,6 +137,7 @@ class MissingImageDialog extends javax.swing.JDialog {
containerPanel = new javax.swing.JPanel();
pathNameTextField = new javax.swing.JTextField();
browseButton = new javax.swing.JButton();
+ lbWarning = new javax.swing.JLabel();
titleLabel = new javax.swing.JLabel();
titleSeparator = new javax.swing.JSeparator();
@@ -191,16 +192,24 @@ class MissingImageDialog extends javax.swing.JDialog {
}
});
+ lbWarning.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N
+ lbWarning.setForeground(new java.awt.Color(244, 0, 0));
+ org.openide.awt.Mnemonics.setLocalizedText(lbWarning, org.openide.util.NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.lbWarning.text")); // NOI18N
+ lbWarning.setToolTipText(org.openide.util.NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.lbWarning.toolTipText")); // NOI18N
+
javax.swing.GroupLayout containerPanelLayout = new javax.swing.GroupLayout(containerPanel);
containerPanel.setLayout(containerPanelLayout);
containerPanelLayout.setHorizontalGroup(
containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(containerPanelLayout.createSequentialGroup()
.addContainerGap()
- .addComponent(pathNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 285, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(browseButton)
- .addContainerGap(83, Short.MAX_VALUE))
+ .addGroup(containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(lbWarning, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(containerPanelLayout.createSequentialGroup()
+ .addComponent(pathNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 285, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(browseButton)
+ .addContainerGap(83, Short.MAX_VALUE))))
);
containerPanelLayout.setVerticalGroup(
containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -209,12 +218,13 @@ class MissingImageDialog extends javax.swing.JDialog {
.addGroup(containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(pathNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(browseButton))
- .addContainerGap(62, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(lbWarning, javax.swing.GroupLayout.DEFAULT_SIZE, 19, Short.MAX_VALUE)
+ .addGap(18, 18, 18))
);
- titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 12));
- org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle
- .getMessage(MissingImageDialog.class, "MissingImageDialog.titleLabel.text")); // NOI18N
+ titleLabel.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.titleLabel.text")); // NOI18N
titleSeparator.setForeground(new java.awt.Color(102, 102, 102));
@@ -254,10 +264,11 @@ class MissingImageDialog extends javax.swing.JDialog {
String newPath = pathNameTextField.getText();
//TODO handle local files
db.setImagePaths(obj_id, Arrays.asList(new String[]{newPath}));
+ this.dispose();
} catch (TskCoreException ex) {
+ lbWarning.setText(NbBundle.getMessage(this.getClass(), "MissingImageDialog.ErrorSettingImage"));
logger.log(Level.WARNING, "Error setting image paths", ex); //NON-NLS
}
- this.dispose();
}//GEN-LAST:event_selectButtonActionPerformed
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
@@ -273,7 +284,7 @@ class MissingImageDialog extends javax.swing.JDialog {
private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
String oldText = pathNameTextField.getText();
-
+ lbWarning.setText("");
// set the current directory of the FileChooser if the ImagePath Field is valid
File currentDir = new File(oldText);
if (currentDir.exists()) {
@@ -294,6 +305,7 @@ class MissingImageDialog extends javax.swing.JDialog {
private javax.swing.JPanel buttonPanel;
private javax.swing.JButton cancelButton;
private javax.swing.JPanel containerPanel;
+ private javax.swing.JLabel lbWarning;
private javax.swing.JTextField pathNameTextField;
private javax.swing.JButton selectButton;
private javax.swing.JLabel titleLabel;
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form
index 2a2c068ccf..c30aa65b0b 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.form
@@ -1,6 +1,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java
index 1c758179f1..6aebc0fe99 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,25 +24,51 @@ import java.awt.*;
import java.io.File;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
-import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
+import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.autopsy.coreutils.PathValidator;
/**
- * The wizard panel for the new case creation.
- *
- * @author jantonius
+ * The JPanel for the first page of the new case wizard.
*/
final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
- private JFileChooser fc = new JFileChooser();
- private NewCaseWizardPanel1 wizPanel;
+ private final JFileChooser fileChooser = new JFileChooser();
+ private final NewCaseWizardPanel1 wizPanel;
+ /**
+ * Constructs the JPanel for the first page of the new case wizard.
+ *
+ * @param wizPanel The wizard panmel that owns this panel.
+ */
NewCaseVisualPanel1(NewCaseWizardPanel1 wizPanel) {
- initComponents();
this.wizPanel = wizPanel;
- caseNameTextField.getDocument().addDocumentListener(this);
- caseParentDirTextField.getDocument().addDocumentListener(this);
+ initComponents();
+ TextFieldListener listener = new TextFieldListener();
+ caseNameTextField.getDocument().addDocumentListener(listener);
+ caseParentDirTextField.getDocument().addDocumentListener(listener);
+ caseParentDirWarningLabel.setVisible(false);
+ }
+
+ /**
+ * Should be called by the readSettings() of the wizard panel that owns this
+ * UI panel so that this panel can read settings for each invocation of the
+ * wizard as well.
+ */
+ void readSettings() {
+ caseNameTextField.setText("");
+ if (UserPreferences.getIsMultiUserModeEnabled()) {
+ multiUserCaseRadioButton.setEnabled(true);
+ multiUserCaseRadioButton.setSelected(true);
+ multiUserSettingsWarningLabel.setVisible(false);
+ } else {
+ multiUserCaseRadioButton.setEnabled(false);
+ singleUserCaseRadioButton.setSelected(true);
+ multiUserSettingsWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.MultiUserDisabled.text"));
+ }
+ validateSettings();
}
/**
@@ -61,27 +87,112 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
*
* @return caseName the case name from the case name text field
*/
- public String getCaseName() {
+ String getCaseName() {
return this.caseNameTextField.getText();
}
+ /**
+ * Allows the the wizard panel that owns this UI panel to set the base case
+ * directory to a persisted vlaue.
+ *
+ * @param caseParentDir The persisted path to the base case directory.
+ */
+ void setCaseParentDir(String caseParentDir) {
+ caseParentDirTextField.setText(caseParentDir);
+ validateSettings();
+ }
+
/**
* Gets the base directory that the user typed on the base directory text
* field. Will add file separator if it was not added.
*
* @return baseDirectory the base directory from the case dir text field
*/
- public String getCaseParentDir() {
+ String getCaseParentDir() {
String parentDir = this.caseParentDirTextField.getText();
-
if (parentDir.endsWith(File.separator) == false) {
parentDir = parentDir + File.separator;
}
return parentDir;
}
- public JTextField getCaseParentDirTextField() {
- return this.caseParentDirTextField;
+ /**
+ * Gets the case type.
+ *
+ * @return CaseType as set via radio buttons
+ */
+ CaseType getCaseType() {
+ CaseType value = CaseType.SINGLE_USER_CASE;
+ if (singleUserCaseRadioButton.isSelected()) {
+ value = CaseType.SINGLE_USER_CASE;
+ } else if (multiUserCaseRadioButton.isSelected()) {
+ value = CaseType.MULTI_USER_CASE;
+ }
+ return value;
+ }
+
+ /**
+ * Called when the user interacts with a child UI component of this panel,
+ * this method notifies the wizard panel that owns this panel and then
+ * validates the user's settings.
+ */
+ private void handleUpdate() {
+ wizPanel.fireChangeEvent();
+ validateSettings();
+ }
+
+ /**
+ * Does validation of the current settings and enables or disables the
+ * "Next" button of the wizard panel that owns this panel.
+ */
+ private void validateSettings() {
+ /**
+ * Check the base case directory for the selected case type and show a
+ * warning if it is a dubious choice.
+ */
+ caseParentDirWarningLabel.setVisible(false);
+ String parentDir = getCaseParentDir();
+ if (!PathValidator.isValid(parentDir, getCaseType())) {
+ caseParentDirWarningLabel.setVisible(true);
+ caseParentDirWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.CaseFolderOnCDriveError.text"));
+ }
+
+ /**
+ * Enable the "Next" button for the wizard if there is text entered for
+ * the case name and base case directory. Also make sure that multi-user
+ * cases are enabled if the multi-user case radio button is selected.
+ */
+ String caseName = getCaseName();
+ if (!caseName.equals("") && !parentDir.equals("")) {
+ caseDirTextField.setText(parentDir + caseName);
+ wizPanel.setIsFinish(true);
+ } else {
+ caseDirTextField.setText("");
+ wizPanel.setIsFinish(false);
+ }
+ }
+
+ /**
+ * Handles validation when the user provides input to text field components
+ * of this panel.
+ */
+ private class TextFieldListener implements DocumentListener {
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ handleUpdate();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ handleUpdate();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ handleUpdate();
+ }
+
}
/**
@@ -92,6 +203,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
// //GEN-BEGIN:initComponents
private void initComponents() {
+ caseTypeButtonGroup = new javax.swing.ButtonGroup();
jLabel1 = new javax.swing.JLabel();
caseNameLabel = new javax.swing.JLabel();
caseDirLabel = new javax.swing.JLabel();
@@ -100,10 +212,13 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
caseDirBrowseButton = new javax.swing.JButton();
jLabel2 = new javax.swing.JLabel();
caseDirTextField = new javax.swing.JTextField();
+ singleUserCaseRadioButton = new javax.swing.JRadioButton();
+ multiUserCaseRadioButton = new javax.swing.JRadioButton();
+ multiUserSettingsWarningLabel = new javax.swing.JLabel();
+ caseParentDirWarningLabel = new javax.swing.JLabel();
- jLabel1.setFont(jLabel1.getFont().deriveFont(Font.BOLD, 14));
- org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle
- .getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.jLabel1.text_1")); // NOI18N
+ jLabel1.setFont(new java.awt.Font("Tahoma", 1, 14)); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.jLabel1.text_1")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(caseNameLabel, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.caseNameLabel.text_1")); // NOI18N
@@ -125,6 +240,28 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
caseDirTextField.setEditable(false);
caseDirTextField.setText(org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.caseDirTextField.text_1")); // NOI18N
+ caseTypeButtonGroup.add(singleUserCaseRadioButton);
+ org.openide.awt.Mnemonics.setLocalizedText(singleUserCaseRadioButton, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.singleUserCaseRadioButton.text")); // NOI18N
+ singleUserCaseRadioButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ singleUserCaseRadioButtonActionPerformed(evt);
+ }
+ });
+
+ caseTypeButtonGroup.add(multiUserCaseRadioButton);
+ org.openide.awt.Mnemonics.setLocalizedText(multiUserCaseRadioButton, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.multiUserCaseRadioButton.text")); // NOI18N
+ multiUserCaseRadioButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ multiUserCaseRadioButtonActionPerformed(evt);
+ }
+ });
+
+ multiUserSettingsWarningLabel.setForeground(new java.awt.Color(255, 0, 0));
+ org.openide.awt.Mnemonics.setLocalizedText(multiUserSettingsWarningLabel, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.multiUserSettingsWarningLabel.text")); // NOI18N
+
+ caseParentDirWarningLabel.setForeground(new java.awt.Color(255, 0, 0));
+ org.openide.awt.Mnemonics.setLocalizedText(caseParentDirWarningLabel, org.openide.util.NbBundle.getMessage(NewCaseVisualPanel1.class, "NewCaseVisualPanel1.caseParentDirWarningLabel.text")); // NOI18N
+
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@@ -132,22 +269,35 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jLabel2)
.addGroup(layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
- .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
- .addComponent(caseDirLabel)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jLabel2)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(caseDirTextField, javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
+ .addComponent(jLabel1)
+ .addGap(0, 227, Short.MAX_VALUE))
+ .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
+ .addComponent(caseDirLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(caseParentDirTextField))
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(caseNameLabel)
+ .addGap(26, 26, 26)
+ .addComponent(caseNameTextField))
+ .addComponent(multiUserSettingsWarningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(caseParentDirTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
- .addComponent(caseNameLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(caseNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addComponent(caseDirTextField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 380, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(caseDirBrowseButton)))
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addComponent(caseDirBrowseButton)))
+ .addContainerGap())
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(singleUserCaseRadioButton)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(multiUserCaseRadioButton))
+ .addComponent(caseParentDirWarningLabel))
+ .addGap(0, 0, Short.MAX_VALUE))))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -167,7 +317,15 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(caseDirTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(32, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(singleUserCaseRadioButton)
+ .addComponent(multiUserCaseRadioButton))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(caseParentDirWarningLabel)
+ .addGap(1, 1, 1)
+ .addComponent(multiUserSettingsWarningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
@@ -179,23 +337,27 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
* @param evt the action event
*/
private void caseDirBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_caseDirBrowseButtonActionPerformed
- // show the directory chooser where the case directory will be created
- fc.setDragEnabled(false);
+ fileChooser.setDragEnabled(false);
if (!caseParentDirTextField.getText().trim().equals("")) {
- fc.setCurrentDirectory(new File(caseParentDirTextField.getText()));
+ fileChooser.setCurrentDirectory(new File(caseParentDirTextField.getText()));
}
- fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
- //fc.setSelectedFile(new File("C:\\Program Files\\"));
- //disableTextField(fc); // disable all the text field on the file chooser
-
- int returnValue = fc.showDialog((Component) evt.getSource(), NbBundle.getMessage(this.getClass(),
+ fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ int choice = fileChooser.showDialog((Component) evt.getSource(), NbBundle.getMessage(this.getClass(),
"NewCaseVisualPanel1.caseDirBrowse.selectButton.text"));
- if (returnValue == JFileChooser.APPROVE_OPTION) {
- String path = fc.getSelectedFile().getPath();
- caseParentDirTextField.setText(path); // put the path to the textfield
+ if (JFileChooser.APPROVE_OPTION == choice) {
+ String path = fileChooser.getSelectedFile().getPath();
+ caseParentDirTextField.setText(path);
}
}//GEN-LAST:event_caseDirBrowseButtonActionPerformed
+ private void singleUserCaseRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_singleUserCaseRadioButtonActionPerformed
+ handleUpdate();
+ }//GEN-LAST:event_singleUserCaseRadioButtonActionPerformed
+
+ private void multiUserCaseRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multiUserCaseRadioButtonActionPerformed
+ handleUpdate();
+ }//GEN-LAST:event_multiUserCaseRadioButtonActionPerformed
+
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton caseDirBrowseButton;
private javax.swing.JLabel caseDirLabel;
@@ -203,8 +365,13 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
private javax.swing.JLabel caseNameLabel;
private javax.swing.JTextField caseNameTextField;
private javax.swing.JTextField caseParentDirTextField;
+ private javax.swing.JLabel caseParentDirWarningLabel;
+ private javax.swing.ButtonGroup caseTypeButtonGroup;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
+ private javax.swing.JRadioButton multiUserCaseRadioButton;
+ private javax.swing.JLabel multiUserSettingsWarningLabel;
+ private javax.swing.JRadioButton singleUserCaseRadioButton;
// End of variables declaration//GEN-END:variables
/**
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java
index 0891ce74ed..5e6b5e5ceb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,8 @@ import java.io.File;
import java.text.MessageFormat;
import java.util.logging.Level;
import javax.swing.JComponent;
+import javax.swing.SwingWorker;
+import javax.swing.SwingUtilities;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
@@ -33,6 +35,12 @@ import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
import org.openide.util.actions.SystemAction;
import org.sleuthkit.autopsy.coreutils.Logger;
+import javax.swing.JOptionPane;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
+import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.datamodel.CaseDbConnectionInfo;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskData.DbType;
/**
* Action to open the New Case wizard.
@@ -74,7 +82,7 @@ final class NewCaseWizardAction extends CallableSystemAction {
* The method to perform new case creation
*/
private void newCaseAction() {
- WizardDescriptor wizardDescriptor = new WizardDescriptor(getPanels());
+ final WizardDescriptor wizardDescriptor = new WizardDescriptor(getPanels());
// {0} will be replaced by WizardDesriptor.Panel.getComponent().getName()
wizardDescriptor.setTitleFormat(new MessageFormat("{0}"));
wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text"));
@@ -82,32 +90,67 @@ final class NewCaseWizardAction extends CallableSystemAction {
dialog.setVisible(true);
dialog.toFront();
- boolean finished = wizardDescriptor.getValue() == WizardDescriptor.FINISH_OPTION; // check if it finishes (it's not cancelled)
- boolean isCancelled = wizardDescriptor.getValue() == WizardDescriptor.CANCEL_OPTION; // check if the "Cancel" button is pressed
+ if (wizardDescriptor.getValue() == WizardDescriptor.FINISH_OPTION) {
+ new SwingWorker() {
- // if the finish button is pressed (not cancelled)
- if (finished) {
- // now start the 'Add Image' wizard
- //TODO fix for local
- AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
- addImageAction.actionPerformed(null);
- }
+ @Override
+ protected Void doInBackground() throws Exception {
+ // Create case.
- // if Cancel button is pressed
- if (isCancelled) {
- String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
- if (createdDirectory != null) {
- logger.log(Level.INFO, "Deleting a created case directory due to isCancelled set, dir: " + createdDirectory); //NON-NLS
- Case.deleteCaseDirectory(new File(createdDirectory));
- }
- // if there's case opened, close the case
- if (Case.existsCurrentCase()) {
- // close the previous case if there's any
- CaseCloseAction closeCase = SystemAction.get(CaseCloseAction.class);
- closeCase.actionPerformed(null);
- }
+ String caseNumber = (String) wizardDescriptor.getProperty("caseNumber"); //NON-NLS
+ String examiner = (String) wizardDescriptor.getProperty("caseExaminer"); //NON-NLS
+ final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
+ String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
+ CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
+
+ Case.create(createdDirectory, caseName, caseNumber, examiner, caseType);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ get();
+ CaseType currentCaseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
+ CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
+ if ((currentCaseType == CaseType.SINGLE_USER_CASE) || ((info.getDbType() != DbType.SQLITE) && SleuthkitCase.tryConnectOld(info.getHost(), info.getPort(), info.getUserName(), info.getPassword(), info.getDbType()))) {
+ AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
+ addImageAction.actionPerformed(null);
+ } else {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem1.text"),
+ NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem2.text"),
+ JOptionPane.ERROR_MESSAGE);
+ doFailedCaseCleanup(wizardDescriptor);
+ }
+
+ } catch (Exception ex) {
+ final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(),
+ "CaseCreateAction.msgDlg.cantCreateCase.msg") + " " + caseName,
+ NbBundle.getMessage(this.getClass(),
+ "CaseOpenAction.msgDlg.cantOpenCase.title"),
+ JOptionPane.ERROR_MESSAGE);
+ });
+ doFailedCaseCleanup(wizardDescriptor);
+ }
+ }
+ }.execute();
+ } else {
+ new Thread(() -> {
+ doFailedCaseCleanup(wizardDescriptor);
+ }).start();
+ }
+ }
+
+ private void doFailedCaseCleanup(WizardDescriptor wizardDescriptor) {
+ String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
+
+ if (createdDirectory != null) {
+ logger.log(Level.INFO, "Deleting a created case directory due to an error, dir: {0}", createdDirectory); //NON-NLS
+ Case.deleteCaseDirectory(new File(createdDirectory));
}
- panels = null; // reset the panel
}
/**
@@ -131,7 +174,7 @@ final class NewCaseWizardAction extends CallableSystemAction {
if (c instanceof JComponent) { // assume Swing components
JComponent jc = (JComponent) c;
// Sets step number of a component
- jc.putClientProperty("WizardPanel_contentSelectedIndex", new Integer(i));
+ jc.putClientProperty("WizardPanel_contentSelectedIndex", i);
// Sets steps names for a panel
jc.putClientProperty("WizardPanel_contentData", steps);
// Turn on subtitle creation on each step
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java
index d368bef209..b7b432a9eb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java
@@ -33,8 +33,8 @@ import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.WizardDescriptor;
import org.openide.WizardValidationException;
-import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
/**
@@ -170,7 +170,8 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,14 +21,14 @@ package org.sleuthkit.autopsy.casemodule;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
-import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openide.WizardDescriptor;
import org.openide.WizardValidationException;
-import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
-import org.openide.util.NbBundle;
+import org.openide.windows.WindowManager;
+import java.awt.Cursor;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
/**
* The "New Case" wizard panel with a component on it. This class represents
@@ -48,6 +48,7 @@ class NewCaseWizardPanel2 implements WizardDescriptor.ValidatingPanel it;
synchronized (listeners) {
- it = new HashSet(listeners).iterator();
+ it = new HashSet<>(listeners).iterator();
}
ChangeEvent ev = new ChangeEvent(this);
while (it.hasNext()) {
@@ -154,6 +155,7 @@ class NewCaseWizardPanel2 implements WizardDescriptor.ValidatingPanel sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,8 +24,8 @@ import java.io.File;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import javax.swing.JTable;
+import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
-
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
@@ -37,7 +37,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
static String[] caseNames;
static String[] casePaths;
- private static Logger logger = Logger.getLogger(OpenRecentCasePanel.class.getName());
+ private static final Logger logger = Logger.getLogger(OpenRecentCasePanel.class.getName());
private static OpenRecentCasePanel instance;
private RecentCasesTableModel model;
@@ -184,8 +184,8 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
logger.log(Level.INFO, "No Case paths exist, cannot open the case"); //NON-NLS
return;
}
- String casePath = casePaths[imagesTable.getSelectedRow()];
- String caseName = caseNames[imagesTable.getSelectedRow()];
+ final String casePath = casePaths[imagesTable.getSelectedRow()];
+ final String caseName = caseNames[imagesTable.getSelectedRow()];
if (!casePath.equals("")) {
// Close the startup menu
try {
@@ -195,27 +195,34 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
logger.log(Level.WARNING, "Error: couldn't open case: " + caseName, ex); //NON-NLS
}
// Open the recent cases
- try {
- if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) {
- JOptionPane.showMessageDialog(null,
- NbBundle.getMessage(OpenRecentCasePanel.class,
- "OpenRecentCasePanel.openCase.msgDlg.caseDoesntExist.msg",
- caseName),
- NbBundle.getMessage(OpenRecentCasePanel.class,
- "OpenRecentCasePanel.openCase.msgDlg.err"),
- JOptionPane.ERROR_MESSAGE);
- RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore
+ if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "OpenRecentCasePanel.openCase.msgDlg.caseDoesntExist.msg",
+ caseName),
+ NbBundle.getMessage(this.getClass(),
+ "OpenRecentCasePanel.openCase.msgDlg.err"),
+ JOptionPane.ERROR_MESSAGE);
+ RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore
- //if case is not opened, open the start window
- if (Case.isCaseOpen() == false) {
- StartupWindowProvider.getInstance().open();
- }
-
- } else {
- Case.open(casePath); // open the case
+ //if case is not opened, open the start window
+ if (Case.isCaseOpen() == false) {
+ StartupWindowProvider.getInstance().open();
}
- } catch (CaseActionException ex) {
- logger.log(Level.WARNING, "Error: couldn't open case: " + caseName, ex); //NON-NLS
+
+ } else {
+ new Thread(() -> {
+ try {
+ Case.open(casePath);
+ } catch (CaseActionException ex) {
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null, ex.getMessage(), NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE);
+ if (!Case.isCaseOpen()) {
+ StartupWindowProvider.getInstance().open();
+ }
+ });
+ }
+ }).start();
}
}
}
@@ -286,7 +293,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
ret = shortenPath(casePaths[rowIndex]);
break;
default:
- logger.log(Level.SEVERE, "Invalid table column index: " + columnIndex); //NON-NLS
+ logger.log(Level.SEVERE, "Invalid table column index: {0}", columnIndex); //NON-NLS
break;
}
return ret;
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java
index 5120f98152..a4859c9846 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,11 +22,10 @@ import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
-import java.util.logging.Level;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.coreutils.Logger;
/**
* This class is used to add the action to the recent case menu item. When the
@@ -34,8 +33,8 @@ import org.sleuthkit.autopsy.coreutils.Logger;
*/
class RecentItems implements ActionListener {
- String caseName;
- String casePath;
+ final String caseName;
+ final String casePath;
private JPanel caller; // for error handling
/**
@@ -65,22 +64,25 @@ class RecentItems implements ActionListener {
//if case is not opened, open the start window
if (Case.isCaseOpen() == false) {
- EventQueue.invokeLater(new Runnable() {
-
- @Override
- public void run() {
- StartupWindowProvider.getInstance().open();
- }
-
+ EventQueue.invokeLater(() -> {
+ StartupWindowProvider.getInstance().open();
});
}
} else {
- try {
- Case.open(casePath); // open the case
- } catch (CaseActionException ex) {
- Logger.getLogger(RecentItems.class.getName()).log(Level.WARNING, "Error: Couldn't open recent case at " + casePath, ex); //NON-NLS
- }
+ new Thread(() -> {
+ // Create case.
+ try {
+ Case.open(casePath);
+ } catch (CaseActionException ex) {
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(null, ex.getMessage(), NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), JOptionPane.ERROR_MESSAGE);
+ if (!Case.isCaseOpen()) {
+ StartupWindowProvider.getInstance().open();
+ }
+ });
+ }
+ }).start();
}
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseImporter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseImporter.java
new file mode 100644
index 0000000000..b5498cc284
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseImporter.java
@@ -0,0 +1,1544 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import java.awt.Dimension;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.logging.Level;
+import javax.swing.JOptionPane;
+import static javax.swing.JOptionPane.OK_CANCEL_OPTION;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+import org.apache.commons.io.FileUtils;
+import org.openide.util.NbBundle;
+import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
+import static org.sleuthkit.autopsy.casemodule.Case.MODULE_FOLDER;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.CaseDbConnectionInfo;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.autopsy.coreutils.NetworkUtils;
+import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
+import org.sleuthkit.autopsy.coreutils.UNCPathUtilities;
+
+/**
+ * Import case(s) from single-user to multi-user. Recursively scans subfolders.
+ */
+public class SingleUserCaseImporter implements Runnable {
+
+ private static final String AUTOPSY_DB_FILE = "autopsy.db"; //NON-NLS
+ private static final String DOTAUT = ".aut"; //NON-NLS
+ public static final String CASE_IMPORT_LOG_FILE = "case_import_log.txt"; //NON-NLS
+ private static final String logDateFormat = "yyyy/MM/dd HH:mm:ss"; //NON-NLS
+ //If TIMELINE_FOLDER changes, also update TIMELINE in EventsRepository
+ private static final String TIMELINE_FOLDER = "Timeline"; //NON-NLS
+ //If TIMELINE_FILE changes, also update TIMELINE_FILE in EventDB
+ private final static String TIMELINE_FILE = "events.db"; //NON-NLS
+ private final static String AIM_LOG_FILE_NAME = "auto_ingest_log.txt"; //NON-NLS
+ private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(logDateFormat);
+ private static final int MAX_DB_NAME_LENGTH = 63;
+ private final String SEP = System.getProperty("line.separator");
+ private final Object threadWaitNotifyLock = new Object();
+
+ private final Path caseInputFolder;
+ private final String caseOutputFolder;
+ private final String imageInputFolder;
+ private final String imageOutputFolder;
+ private final boolean copySourceImages;
+ private final boolean deleteCase;
+ private final CaseDbConnectionInfo db;
+ private final ImportDoneCallback notifyOnComplete;
+ private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities();
+ private PrintWriter writer;
+ private XMLCaseManagement oldXmlCaseManagement;
+ private XMLCaseManagement newXmlCaseManagement;
+ private boolean addTimestamp;
+ private int userAnswer = 0;
+
+ /**
+ * SingleUserCaseImporter constructor
+ *
+ * @param caseInput the folder to start our case search from. Will
+ * find valid cases from this folder down, and
+ * process them.
+ * @param caseOutput the folder to place processed cases into
+ * @param imageInput the folder that holds the images to copy over
+ * @param imageOutput the destination folder for the images
+ * @param database the connection information to talk to the
+ * PostgreSQL db
+ * @param copySourceImages true if images should be copied
+ * @param deleteCase true if the old version of the case should be
+ * deleted after import
+ * @param addTimestamp true if the output case name should end in a
+ * timestamp, false otherwise
+ * @param callback a callback from the calling panel for
+ * notification when the import has completed. This
+ * is a Runnable on a different thread.
+ */
+ public SingleUserCaseImporter(String caseInput, String caseOutput, String imageInput, String imageOutput, CaseDbConnectionInfo database,
+ boolean copySourceImages, boolean deleteCase, ImportDoneCallback callback, boolean addTimestamp) {
+ this.caseInputFolder = Paths.get(caseInput);
+ this.caseOutputFolder = caseOutput;
+ this.imageInputFolder = imageInput;
+ this.imageOutputFolder = imageOutput;
+ this.copySourceImages = copySourceImages;
+ this.deleteCase = deleteCase;
+ this.db = database;
+ this.notifyOnComplete = callback;
+ this.addTimestamp = addTimestamp;
+ }
+
+ /**
+ * Tests if the input path has a corresponding image input folder and no
+ * repeated case names in the path. If both of these conditions are true, we
+ * can process this case, otherwise we can not.
+ *
+ * @param icd the import case data for the current case
+ *
+ * @return true if we can process it, false if not
+ */
+ private boolean canProcess(ImportCaseData icd) {
+ try {
+ String relativeCaseName = TimeStampUtils.removeTimeStamp(icd.getRelativeCaseName());
+ String caseName = TimeStampUtils.removeTimeStamp(icd.getOldCaseName());
+
+ // check for image folder
+ Path testImageInputsFromOldCase = Paths.get(imageInputFolder, relativeCaseName);
+ if (copySourceImages) {
+ if (!testImageInputsFromOldCase.toFile().isDirectory()) {
+ log(imageInputFolder + " has no corresponding images folder. Not able to process.");
+ return false;
+ } else {
+ icd.setSpecificImageInputFolder(testImageInputsFromOldCase);
+ }
+
+ Path imagePath = Paths.get(imageInputFolder);
+ // see if case name is in the image path. This causes bad things to happen with the parsing.
+ for (int x = 0; x < imagePath.getNameCount(); ++x) {
+ if (caseName.toLowerCase().equals(imagePath.getName(x).toString().toLowerCase())) {
+ log(imagePath.toString() + " has case name \"" + caseName + "\" within path. Not able to process.");
+ return false;
+ }
+ }
+ } else {
+ icd.setSpecificImageInputFolder(testImageInputsFromOldCase);
+ }
+ } catch (Exception ex) {
+ log("Error processing " + icd.specificCaseInputFolder.toString() + ": " + ex.getMessage());
+ return false; // anything goes wrong, bail.
+ }
+
+ return true;
+ }
+
+ /**
+ * Handles most of the heavy lifting for importing cases from single-user to
+ * multi-user. Creates new .aut file, moves folders to the right place,
+ * imports the database, and updates paths within the database.
+ *
+ * @param icd the Import Case Data for the current case
+ *
+ * @return true if successful, false if not
+ */
+ private boolean processCase(ImportCaseData icd) {
+ boolean result = true;
+ try {
+ log("Importing case " + icd.getSpecificCaseInputFolder().toString() + " to " + caseOutputFolder + "\\" + icd.getOldCaseName()); //NON-NLS
+
+ checkInputDatabase(icd.getSpecificCaseInputFolder());
+
+ oldXmlCaseManagement = new XMLCaseManagement();
+
+ // read old xml config
+ oldXmlCaseManagement.open(icd.getSpecificCaseInputFolder().resolve(TimeStampUtils.removeTimeStamp(icd.getOldCaseName()) + DOTAUT).toString());
+ if (oldXmlCaseManagement.getCaseType() == CaseType.MULTI_USER_CASE) {
+ throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.AlreadyMultiUser"));
+ }
+
+ prepareOutput(icd);
+
+ // create sanitized names for database and solr
+ String caseName = TimeStampUtils.removeTimeStamp(icd.getNewCaseName()); // caseName holds the deconflicted, timestampless name of the case
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); //NON-NLS
+ Date date = new Date();
+ String santizedDatabaseName = Case.sanitizeCaseName(caseName);
+ String dbName = santizedDatabaseName + "_" + dateFormat.format(date); //NON-NLS
+ String solrName = dbName;
+
+ icd.setSpecificImageOutputFolder(Paths.get(imageOutputFolder, icd.getNewCaseName()));
+ copyResults(icd); // Copy items to new hostname folder structure
+ dbName = importDb(dbName, icd.getSpecificCaseInputFolder(), icd.getSpecificCaseOutputFolder().toString()); // Change from SQLite to PostgreSQL
+
+ fixPaths(icd, dbName); // Update paths in DB
+
+ copyImages(icd); // Copy images over
+
+ // create new XML config
+ newXmlCaseManagement = new XMLCaseManagement();
+ newXmlCaseManagement.create(icd.getSpecificCaseOutputFolder().toString(),
+ caseName,
+ oldXmlCaseManagement.getCaseExaminer(),
+ oldXmlCaseManagement.getCaseNumber(),
+ CaseType.MULTI_USER_CASE, dbName, solrName);
+
+ // Set created date. This calls writefile, no need to call it again
+ newXmlCaseManagement.setCreatedDate(oldXmlCaseManagement.getCreatedDate());
+
+ // At this point the import has been finished successfully so we can delete the original case
+ // (if requested). This *should* be fairly safe - at this point we know there was an autopsy file
+ // and database in the given directory so the user shouldn't be able to accidently blow away
+ // their C drive.
+ if (deleteCase) {
+ log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.DeletingCase") + " " + icd.getSpecificCaseInputFolder().toString());
+ FileUtils.deleteDirectory(icd.getSpecificCaseInputFolder().toFile());
+ }
+
+ result = reportLostImages(db, dbName);
+
+ log("Finished importing case " + icd.getSpecificCaseInputFolder().toString() + " to " + icd.getSpecificCaseOutputFolder().toString());
+ } catch (Exception exp) {
+ /// clean up here
+ log("Error processing " + icd.specificCaseInputFolder.toString() + ": " + exp.getMessage());
+ result = false;
+ }
+ return result;
+ }
+
+ /**
+ * Searches for images in the filesystem. It parses the new PostgreSQL
+ * database to find images that should exist, and notifies when they do not.
+ *
+ * @param db database credentials
+ * @param dbName the name of the database
+ *
+ * @return true if successfully found all images, false otherwise.
+ */
+ private boolean reportLostImages(CaseDbConnectionInfo db, String dbName) {
+ boolean result = true;
+ if (copySourceImages) {
+ try {
+ Class.forName("org.postgresql.Driver"); //NON-NLS
+ Connection dbConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS
+ Statement inputStatement = dbConnection.createStatement();
+ ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS
+
+ while (inputResultSet.next()) {
+
+ File theFile = new File(inputResultSet.getString(2));
+ if (false == theFile.exists()) {
+ log("Unable to find image " + theFile.toString() + " for case " + dbName);
+ result = false;
+ }
+ }
+ } catch (Exception ex) {
+ log("Error. Unable to verify images were copied.");
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Figure out the input folder for images and return it.
+ *
+ * @param icd the import case data for the current case
+ *
+ * @return the name of the proper input folder
+ */
+ private File findInputFolder(ImportCaseData icd) {
+
+ File thePath = icd.getSpecificImageInputFolder().resolve(icd.getOldCaseName()).toFile();
+ if (thePath.isDirectory()) {
+ /// we've found it
+ return thePath;
+ } else {
+ return icd.getSpecificImageInputFolder().toFile();
+ }
+ }
+
+ /**
+ * Ensure the input source has an autopsy.db and exists.
+ *
+ * @param caseInput The folder containing a case to import.
+ *
+ * @throws Exception
+ */
+ private void checkInputDatabase(Path caseInput) throws Exception {
+ Path path = caseInput.resolve(AUTOPSY_DB_FILE);
+ if (false == path.toFile().exists()) {
+ throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.BadDatabaseFileName"));
+ }
+ }
+
+ /**
+ * Handles case folder, PosgreSql database, and Solr core name deconfliction
+ * Sets the appropriate portions of the ImportCaseData object.
+ *
+ * @param icd the case data folder name
+ *
+ * @throws Exception
+ */
+ private void prepareOutput(ImportCaseData icd) throws Exception {
+ // test for uniqueness
+ String caseName = icd.getOldCaseName();
+ File specificOutputFolder = Paths.get(caseOutputFolder, caseName).toFile();
+ String sanitizedCaseName = caseName;
+ if (specificOutputFolder.exists()) {
+ // not unique. add numbers before timestamp to specific case name
+ String timeStamp = TimeStampUtils.getTimeStampOnly(caseName); //NON-NLS
+ sanitizedCaseName = TimeStampUtils.removeTimeStamp(caseName);
+
+ int number = 1;
+ String temp = ""; //NON-NLS
+ while (specificOutputFolder.exists()) {
+ if (number == Integer.MAX_VALUE) {
+ // oops. it never became unique. give up.
+ throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.NonUniqueOutputFolder") + sanitizedCaseName);
+ }
+ temp = sanitizedCaseName + "_" + Integer.toString(number) + timeStamp; //NON-NLS
+ specificOutputFolder = Paths.get(caseOutputFolder, temp).toFile();
+ ++number;
+ }
+ sanitizedCaseName = temp;
+ }
+
+ if (addTimestamp && !TimeStampUtils.endsWithTimeStamp(sanitizedCaseName)) {
+ sanitizedCaseName += "_" + TimeStampUtils.createTimeStamp();
+ }
+
+ Path caseOutputPath = Paths.get(caseOutputFolder, sanitizedCaseName);
+ icd.setNewCaseName(sanitizedCaseName);
+ icd.setSpecificCaseOutputFolder(caseOutputPath);
+ caseOutputPath.toFile().mkdirs(); // create output folders just in case
+ }
+
+ /**
+ * Copy all the folders at the base level to the new scheme involving
+ * hostname. Also take care of a few files such as logs, timeline db, etc.
+ *
+ * @param icd the case data
+ *
+ * @throws IOException
+ */
+ private void copyResults(ImportCaseData icd) throws IOException {
+ /// get hostname
+ String hostName = NetworkUtils.getLocalHostName();
+ Path destination;
+ Path source;
+
+ source = icd.getSpecificCaseInputFolder();
+ if (source.toFile().exists()) {
+ destination = icd.getSpecificCaseOutputFolder().resolve(hostName);
+ FileUtils.copyDirectory(source.toFile(), destination.toFile());
+ }
+
+ source = icd.getSpecificCaseInputFolder().resolve(TIMELINE_FILE);
+ if (source.toFile().exists()) {
+ destination = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, MODULE_FOLDER, TIMELINE_FOLDER, TIMELINE_FILE);
+ FileUtils.copyFile(source.toFile(), destination.toFile());
+ }
+
+ source = icd.getSpecificCaseInputFolder().resolve(AIM_LOG_FILE_NAME);
+ destination = icd.getSpecificCaseOutputFolder().resolve(AIM_LOG_FILE_NAME);
+ if (source.toFile().exists()) {
+ FileUtils.copyFile(source.toFile(), destination.toFile());
+
+ }
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(destination.toString(), true)))) {
+ out.println(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.ImportedAsMultiUser") + new Date());
+ } catch (IOException e) {
+ // if unable to log it, no problem
+ }
+
+ // Remove the single-user .aut file, database, Timeline database and log
+ File oldAutopsyFile = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, TimeStampUtils.removeTimeStamp(icd.getOldCaseName()) + DOTAUT).toFile();
+ if (oldAutopsyFile.exists()) {
+ oldAutopsyFile.delete();
+ }
+
+ File oldDatabaseFile = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, AUTOPSY_DB_FILE).toFile();
+ if (oldDatabaseFile.exists()) {
+ oldDatabaseFile.delete();
+ }
+
+ File oldTimelineFile = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, TIMELINE_FILE).toFile();
+ if (oldTimelineFile.exists()) {
+ oldTimelineFile.delete();
+ }
+
+ File oldIngestLog = Paths.get(icd.getSpecificCaseOutputFolder().toString(), hostName, AIM_LOG_FILE_NAME).toFile();
+ if (oldIngestLog.exists()) {
+ oldIngestLog.delete();
+ }
+ }
+
+ /**
+ * Import the database from SQLite to PostgreSQL. Do not change any of the
+ * data while loading it over. Fixing paths is done once the database is
+ * completely imported.
+ *
+ * @param dbName the name of the database, could have name collision
+ * @param inputPath the path to the input case
+ * @param outputCaseName the name of the output case, could have extra
+ * digits to avoid name collisions
+ *
+ * @return the deconflicted name of the PostgreSQL database that was created
+ *
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ private String importDb(String dbName, Path inputPath, String outputCaseName) throws SQLException, ClassNotFoundException, Exception {
+ // deconflict the database name
+ dbName = deconflictDatabaseName(db, dbName);
+
+ // Create a new database via SleuthkitCase
+ SleuthkitCase newCase = SleuthkitCase.newCase(dbName, db, outputCaseName);
+ newCase.close();
+
+ /// Migrate from SQLite to PostgreSQL
+ Class.forName("org.sqlite.JDBC"); //NON-NLS
+ Connection sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + inputPath.resolve(AUTOPSY_DB_FILE).toString(), "", ""); //NON-NLS
+
+ Connection postgresqlConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS
+
+ // blackboard_artifact_types
+ Statement inputStatement = sqliteConnection.createStatement();
+ ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifact_types"); //NON-NLS
+ Statement outputStatement;
+ Statement numberingPK;
+ long biggestPK = 0;
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ Statement check = postgresqlConnection.createStatement();
+ ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_artifact_types WHERE artifact_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS
+ if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist
+ String sql = "INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name) VALUES ("
+ + value + ", '"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "',"
+ + " ? )"; //NON-NLS
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 3, 1);
+ pst.executeUpdate();
+ }
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE blackboard_artifact_types_artifact_type_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // blackboard_attribute_types
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_attribute_types"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ Statement check = postgresqlConnection.createStatement();
+ ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_attribute_types WHERE attribute_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS
+ if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist
+ String sql = "INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name) VALUES ("
+ + value + ", '"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "',"
+ + " ? )"; //NON-NLS
+
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 3, 1);
+ pst.executeUpdate();
+ }
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE blackboard_attribute_types_attribute_type_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_objects
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_objects"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ outputStatement.executeUpdate("INSERT INTO tsk_objects (obj_id, par_obj_id, type) VALUES ("
+ + value + ","
+ + getNullableLong(inputResultSet, 2) + ","
+ + inputResultSet.getInt(3) + ")"); //NON-NLS
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_objects_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_image_names, no primary key
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ outputStatement.executeUpdate("INSERT INTO tsk_image_names (obj_id, name, sequence) VALUES ("
+ + inputResultSet.getLong(1) + ",'"
+ + inputResultSet.getString(2) + "',"
+ + inputResultSet.getInt(3) + ")"); //NON-NLS
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+
+ // tsk_image_info
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_info"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ String sql = "INSERT INTO tsk_image_info (obj_id, type, ssize, tzone, size, md5, display_name) VALUES ("
+ + value + ","
+ + getNullableInt(inputResultSet, 2) + ","
+ + getNullableInt(inputResultSet, 3) + ","
+ + " ? ,"
+ + getNullableLong(inputResultSet, 5) + ","
+ + " ? ,"
+ + " ? )"; //NON-NLS
+
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 4, 1);
+ populateNullableString(pst, inputResultSet, 6, 2);
+ populateNullableString(pst, inputResultSet, 7, 3);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_image_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_fs_info
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_fs_info"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ String sql = "INSERT INTO tsk_fs_info (obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name) VALUES ("
+ + value + ","
+ + inputResultSet.getLong(2) + ","
+ + inputResultSet.getInt(3) + ","
+ + inputResultSet.getLong(4) + ","
+ + inputResultSet.getLong(5) + ","
+ + inputResultSet.getLong(6) + ","
+ + inputResultSet.getLong(7) + ","
+ + inputResultSet.getLong(8) + ","
+ + " ? )"; //NON-NLS
+
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 9, 1);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_fs_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_files_path
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files_path"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ outputStatement.executeUpdate("INSERT INTO tsk_files_path (obj_id, path) VALUES ("
+ + value + ", '"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "')"); //NON-NLS
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_files_path_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_files
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ String sql = "INSERT INTO tsk_files (obj_id, fs_obj_id, attr_type, attr_id, name, meta_addr, meta_seq, type, has_layout, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, mode, uid, gid, md5, known, parent_path) VALUES ("
+ + value + ","
+ + getNullableLong(inputResultSet, 2) + ","
+ + getNullableInt(inputResultSet, 3) + ","
+ + getNullableInt(inputResultSet, 4) + ",'"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(5)) + "',"
+ + getNullableLong(inputResultSet, 6) + ","
+ + getNullableLong(inputResultSet, 7) + ","
+ + getNullableInt(inputResultSet, 8) + ","
+ + getNullableInt(inputResultSet, 9) + ","
+ + getNullableInt(inputResultSet, 10) + ","
+ + getNullableInt(inputResultSet, 11) + ","
+ + getNullableInt(inputResultSet, 12) + ","
+ + getNullableInt(inputResultSet, 13) + ","
+ + getNullableInt(inputResultSet, 14) + ","
+ + getNullableLong(inputResultSet, 15) + ","
+ + getNullableLong(inputResultSet, 16) + ","
+ + getNullableLong(inputResultSet, 17) + ","
+ + getNullableLong(inputResultSet, 18) + ","
+ + getNullableLong(inputResultSet, 19) + ","
+ + getNullableInt(inputResultSet, 20) + ","
+ + getNullableInt(inputResultSet, 21) + ","
+ + getNullableInt(inputResultSet, 22) + ","
+ + " ? ,"
+ + getNullableInt(inputResultSet, 24) + ","
+ + " ? )"; //NON-NLS
+
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 23, 1);
+ populateNullableString(pst, inputResultSet, 25, 2);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_files_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_file_layout, no primary key
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_file_layout"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ outputStatement.executeUpdate("INSERT INTO tsk_file_layout (obj_id, byte_start, byte_len, sequence) VALUES ("
+ + inputResultSet.getLong(1) + ","
+ + inputResultSet.getLong(2) + ","
+ + inputResultSet.getLong(3) + ","
+ + inputResultSet.getInt(4) + ")"); //NON-NLS
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+
+ // tsk_db_info, no primary key
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_db_info"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ Statement check = postgresqlConnection.createStatement();
+ ResultSet checkResult = check.executeQuery("SELECT * FROM tsk_db_info WHERE schema_ver=" + inputResultSet.getInt(1) + " AND tsk_ver=" + inputResultSet.getInt(2)); //NON-NLS
+ if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist
+ outputStatement.executeUpdate("INSERT INTO tsk_db_info (schema_ver, tsk_ver) VALUES ("
+ + getNullableInt(inputResultSet, 1) + ","
+ + getNullableInt(inputResultSet, 2) + ")"); //NON-NLS
+ }
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+
+ // tag_names
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tag_names"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ String sql = "INSERT INTO tag_names (tag_name_id, display_name, description, color) VALUES ("
+ + value + ","
+ + " ? ,'"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(3)) + "','"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(4)) + "')"; //NON-NLS
+
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 2, 1);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tag_names_tag_name_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // reports
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM reports"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ outputStatement.executeUpdate("INSERT INTO reports (report_id, path, crtime, src_module_name, report_name) VALUES ("
+ + value + ", '"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "',"
+ + inputResultSet.getInt(3) + ",'"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(4)) + "','"
+ + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(5)) + "')"); //NON-NLS
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE reports_report_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // blackboard_artifacts
+ biggestPK = Long.MIN_VALUE; // This table uses very large negative primary key values, so start at Long.MIN_VALUE
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifacts"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ outputStatement.executeUpdate("INSERT INTO blackboard_artifacts (artifact_id, obj_id, artifact_type_id) VALUES ("
+ + value + ","
+ + inputResultSet.getLong(2) + ","
+ + inputResultSet.getLong(3) + ")"); //NON-NLS
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE blackboard_artifacts_artifact_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // blackboard_attributes, no primary key
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_attributes"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ String sql = "INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_byte, value_text, value_int32, value_int64, value_double) VALUES ("
+ + inputResultSet.getLong(1) + ","
+ + inputResultSet.getLong(2) + ","
+ + " ? ,"
+ + " ? ,"
+ + inputResultSet.getLong(5) + ","
+ + inputResultSet.getInt(6) + ","
+ + " ? ,"
+ + " ? ,"
+ + getNullableInt(inputResultSet, 9) + ","
+ + getNullableLong(inputResultSet, 10) + ","
+ + " ? )"; //NON-NLS
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 3, 1);
+ populateNullableString(pst, inputResultSet, 4, 2);
+ populateNullableByteArray(pst, inputResultSet, 7, 3);
+ populateNullableString(pst, inputResultSet, 8, 4);
+ populateNullableNumeric(pst, inputResultSet, 11, 5);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+
+ // tsk_vs_parts
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_vs_parts"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ String sql = "INSERT INTO tsk_vs_parts (obj_id, addr, start, length, descr, flags) VALUES ("
+ + value + ","
+ + inputResultSet.getLong(2) + ","
+ + inputResultSet.getLong(3) + ","
+ + inputResultSet.getLong(4) + ","
+ + " ? ,"
+ + inputResultSet.getInt(6) + ")"; //NON-NLS
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 5, 1);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_vs_parts_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_vs_info
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_vs_info"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ outputStatement.executeUpdate("INSERT INTO tsk_vs_info (obj_id, vs_type, img_offset, block_size) VALUES ("
+ + value + ","
+ + inputResultSet.getInt(2) + ","
+ + inputResultSet.getLong(3) + ","
+ + inputResultSet.getLong(4) + ")"); //NON-NLS
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_vs_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_files_derived
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files_derived"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ String sql = "INSERT INTO tsk_files_derived (obj_id, derived_id, rederive) VALUES ("
+ + value + ","
+ + inputResultSet.getLong(2) + ","
+ + " ? )"; //NON-NLS
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 3, 1);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_files_derived_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // tsk_files_derived_method
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files_derived_method"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ String sql = "INSERT INTO tsk_files_derived_method (derived_id, tool_name, tool_version, other) VALUES ("
+ + value + ", '"
+ + inputResultSet.getString(2) + "','"
+ + inputResultSet.getString(3) + "',"
+ + " ? )"; //NON-NLS
+ PreparedStatement pst = postgresqlConnection.prepareStatement(sql);
+ populateNullableString(pst, inputResultSet, 4, 1);
+ pst.executeUpdate();
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE tsk_files_derived_method_derived_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // content_tags
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM content_tags"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ outputStatement.executeUpdate("INSERT INTO content_tags (tag_id, obj_id, tag_name_id, comment, begin_byte_offset, end_byte_offset) VALUES ("
+ + value + ","
+ + inputResultSet.getLong(2) + ","
+ + inputResultSet.getLong(3) + ",'"
+ + inputResultSet.getString(4) + "',"
+ + inputResultSet.getLong(5) + ","
+ + inputResultSet.getLong(6) + ")"); //NON-NLS
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE content_tags_tag_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ // blackboard_artifact_tags
+ biggestPK = 0;
+ inputStatement = sqliteConnection.createStatement();
+ inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifact_tags"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ outputStatement = postgresqlConnection.createStatement();
+ try {
+ long value = inputResultSet.getLong(1);
+ if (value > biggestPK) {
+ biggestPK = value;
+ }
+ outputStatement.executeUpdate("INSERT INTO blackboard_artifact_tags (tag_id, artifact_id, tag_name_id, comment) VALUES ("
+ + value + ","
+ + inputResultSet.getLong(2) + ","
+ + inputResultSet.getLong(3) + ",'"
+ + inputResultSet.getString(4) + "')"); //NON-NLS
+
+ } catch (SQLException ex) {
+ if (ex.getErrorCode() != 0) { // 0 if the entry already exists
+ throw new SQLException(ex);
+ }
+ }
+ }
+ numberingPK = postgresqlConnection.createStatement();
+ numberingPK.execute("ALTER SEQUENCE blackboard_artifact_tags_tag_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS
+
+ sqliteConnection.close();
+ postgresqlConnection.close();
+
+ return dbName;
+ }
+
+ /**
+ * Checks that our database name is unique. If it is not, attempts to add
+ * numbers to it until it is unique. Gives up if it goes through all
+ * positive integers without finding a unique name.
+ *
+ * @param db Database credentials
+ * @param baseDbName proposed name of the database to check for collisions
+ *
+ * @return name to use for the new database. Could be the name passed in.
+ *
+ * @throws ClassNotFoundException
+ * @throws SQLException
+ * @throws Exception
+ */
+ private String deconflictDatabaseName(CaseDbConnectionInfo db, String baseDbName) throws ClassNotFoundException, SQLException, Exception {
+
+ Class.forName("org.postgresql.Driver"); //NON-NLS
+ Connection dbNameConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
+
+ int number = 1;
+ boolean unique = false;
+ String sanitizedDbName = baseDbName;
+ if (sanitizedDbName.length() > MAX_DB_NAME_LENGTH) {
+ sanitizedDbName = sanitizedDbName.substring(0, MAX_DB_NAME_LENGTH);
+ }
+
+ if (dbNameConnection != null) {
+ while (unique == false) {
+ Statement st = dbNameConnection.createStatement();
+ ResultSet answer = st.executeQuery("SELECT datname FROM pg_catalog.pg_database WHERE LOWER(datname) LIKE LOWER('" + sanitizedDbName + "%')"); //NON-NLS
+
+ if (!answer.next()) {
+ unique = true;
+ } else {
+ // not unique. add numbers before dbName.
+ if (number == Integer.MAX_VALUE) {
+ // oops. it never became unique. give up.
+ throw new Exception(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.NonUniqueDatabaseName"));
+ }
+ sanitizedDbName = "_" + Integer.toString(number) + "_" + baseDbName; //NON-NLS
+
+ // Chop full db name to 63 characters (max for PostgreSQL)
+ if (sanitizedDbName.length() > MAX_DB_NAME_LENGTH) {
+ sanitizedDbName = sanitizedDbName.substring(0, MAX_DB_NAME_LENGTH);
+ }
+ ++number;
+ }
+ }
+ dbNameConnection.close();
+ } else {
+ // Could be caused by database credentials, using user accounts that
+ // can not check if other databases exist, so allow it to continue
+ log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.PotentiallyNonUniqueDatabaseName"));
+ }
+
+ return sanitizedDbName;
+ }
+
+ /**
+ * Get the images from the old case and place them in the central
+ * repository, if the user chose to.
+ *
+ * @param icd the Import Case Data
+ *
+ * @throws IOException
+ */
+ private void copyImages(ImportCaseData icd) throws IOException {
+ if (copySourceImages) {
+ File imageSource = findInputFolder(icd); // Find the folder for the input images
+ File imageDestination = new File(icd.getSpecificImageOutputFolder().toString());
+
+ // If we can find the input images, copy if needed.
+ if (imageSource.exists()) {
+ FileUtils.copyDirectory(imageSource, imageDestination);
+
+ } else {
+ log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.UnableToCopySourceImages"));
+ }
+ }
+ }
+
+ /**
+ * Fix up any paths in the database that refer to items that have moved.
+ * Candidates include events.db, input images, reports, file paths, etc.
+ *
+ * @param icd the import case data for the current case
+ * @param dbName the name of the database
+ */
+ private void fixPaths(ImportCaseData icd, String dbName) throws SQLException {
+ /// Fix paths in reports, tsk_files_path, and tsk_image_names tables
+
+ String input = icd.getSpecificImageInputFolder().toString();
+ String output = icd.getSpecificImageOutputFolder().toString();
+
+ Connection postgresqlConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS
+ if (postgresqlConnection != null) {
+ String hostName = NetworkUtils.getLocalHostName();
+
+ // add hostname to reports
+ Statement updateStatement = postgresqlConnection.createStatement();
+ updateStatement.executeUpdate("UPDATE reports SET path=CONCAT('" + hostName + "/', path) WHERE path IS NOT NULL AND path != ''"); //NON-NLS
+
+ // add hostname to tsk_files_path
+ updateStatement = postgresqlConnection.createStatement();
+ updateStatement.executeUpdate("UPDATE tsk_files_path SET path=CONCAT('" + hostName + "\\', path) WHERE path IS NOT NULL AND path != ''"); //NON-NLS
+
+ String caseName = TimeStampUtils.removeTimeStamp(icd.getOldCaseName()).toLowerCase();
+
+ if (copySourceImages) {
+ // update path for images
+ Statement inputStatement = postgresqlConnection.createStatement();
+ ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS
+
+ while (inputResultSet.next()) {
+ Path oldPath = Paths.get(inputResultSet.getString(2));
+
+ for (int x = 0; x < oldPath.getNameCount(); ++x) {
+ if (oldPath.getName(x).toString().toLowerCase().equals(caseName)) {
+ Path newPath = Paths.get(output, oldPath.subpath(x + 1, oldPath.getNameCount()).toString());
+ updateStatement = postgresqlConnection.createStatement();
+ updateStatement.executeUpdate("UPDATE tsk_image_names SET name='" + newPath.toString() + "' WHERE obj_id = " + inputResultSet.getInt(1)); //NON-NLS
+ break;
+ }
+ }
+ }
+ }
+ postgresqlConnection.close();
+ } else {
+ log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.CanNotOpenDatabase"));
+ }
+ }
+
+ /**
+ * Return an integer from the ResultSet converted to String or NULL, by
+ * checking ResultSet.wasNull()
+ *
+ * @param rs the ResultSet to work with
+ * @param index the index into the ResultSet to work with
+ *
+ * @return the proper value, the integer, or NULL
+ *
+ * @throws SQLException
+ */
+ private String getNullableInt(ResultSet rs, int index) throws SQLException {
+ int value = rs.getInt(index);
+ if (rs.wasNull()) {
+ return "NULL"; //NON-NLS
+ } else {
+ return Integer.toString(value);
+ }
+ }
+
+ /**
+ * Return a long from the ResultSet converted to String or NULL, by checking
+ * ResultSet.wasNull()
+ *
+ * @param rs the ResultSet to work with
+ * @param index the index into the ResultSet to work with
+ *
+ * @return the proper value, the long, or NULL
+ *
+ * @throws SQLException
+ */
+ private String getNullableLong(ResultSet rs, int index) throws SQLException {
+ long value = rs.getLong(index);
+ if (rs.wasNull()) {
+ return "NULL"; //NON-NLS
+ } else {
+ return Long.toString(value);
+ }
+ }
+
+ /**
+ * Place a NULL inside a prepared statement if needed, otherwise, place the
+ * String that was in the ResultSet.
+ *
+ * @param pst the prepared statement
+ * @param rs the ResultSet to work with
+ * @param rsIndex index for the result set
+ * @param psIndex index for the prepared statement
+ *
+ * @throws SQLException
+ */
+ private void populateNullableString(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException {
+ String nullableString = rs.getString(rsIndex);
+ if (rs.wasNull()) {
+ pst.setNull(psIndex, java.sql.Types.NULL);
+ } else {
+ pst.setString(psIndex, SleuthkitCase.escapeSingleQuotes(nullableString));
+ }
+ }
+
+ /**
+ * Place a NULL inside a prepared statement if needed, otherwise, place the
+ * byte array that was in the ResultSet.
+ *
+ * @param pst the prepared statement
+ * @param rs the ResultSet to work with
+ * @param rsIndex index for the result set
+ * @param psIndex index for the prepared statement
+ *
+ * @throws SQLException
+ */
+ private void populateNullableByteArray(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException {
+ byte[] nullableBytes = rs.getBytes(rsIndex);
+ if (rs.wasNull()) {
+ pst.setNull(psIndex, java.sql.Types.NULL);
+ } else {
+ pst.setBytes(psIndex, nullableBytes);
+ }
+ }
+
+ /**
+ * Place a NULL inside a prepared statement if needed, otherwise, place the
+ * double that was in the ResultSet.
+ *
+ * @param pst the prepared statement
+ * @param rs the ResultSet to work with
+ * @param rsIndex index for the result set
+ * @param psIndex index for the prepared statement
+ *
+ * @throws SQLException
+ */
+ private void populateNullableNumeric(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException {
+ double nullableNumeric = rs.getDouble(rsIndex);
+ if (rs.wasNull()) {
+ pst.setNull(psIndex, java.sql.Types.NULL);
+ } else {
+ pst.setDouble(psIndex, nullableNumeric);
+ }
+ }
+
+ private class ImportCaseData {
+
+ private Path specificCaseInputFolder;
+ private Path specificCaseOutputFolder;
+ private Path specificImageInputFolder;
+ private Path specificImageOutputFolder;
+ private String relativeCaseName;
+ private String newCaseName;
+ private String oldCaseName;
+
+ public Path getSpecificCaseInputFolder() {
+ return specificCaseInputFolder;
+ }
+
+ public Path getSpecificCaseOutputFolder() {
+ return specificCaseOutputFolder;
+ }
+
+ public Path getSpecificImageInputFolder() {
+ return specificImageInputFolder;
+ }
+
+ public Path getSpecificImageOutputFolder() {
+ return specificImageOutputFolder;
+ }
+
+ public String getRelativeCaseName() {
+ return relativeCaseName;
+ }
+
+ public String getOldCaseName() {
+ return oldCaseName;
+ }
+
+ public String getNewCaseName() {
+ return newCaseName;
+ }
+
+ public void setSpecificCaseInputFolder(Path caseInputFolder) {
+ this.specificCaseInputFolder = caseInputFolder;
+ }
+
+ public void setSpecificCaseOutputFolder(Path caseOutputFolder) {
+ this.specificCaseOutputFolder = caseOutputFolder;
+ }
+
+ public void setSpecificImageInputFolder(Path imageInputFolder) {
+ this.specificImageInputFolder = imageInputFolder;
+ }
+
+ public void setSpecificImageOutputFolder(Path imageOutputFolder) {
+ this.specificImageOutputFolder = imageOutputFolder;
+ }
+
+ public void setRelativeCaseName(Path input, Path aut) {
+ this.relativeCaseName = input.relativize(aut).toString();
+ }
+
+ public void setOldCaseName(String oldCaseName) {
+ this.oldCaseName = oldCaseName;
+ }
+
+ public void setNewCaseName(String newCaseName) {
+ this.newCaseName = newCaseName;
+ }
+
+ public ImportCaseData(Path p) {
+ this.specificCaseInputFolder = p;
+ this.oldCaseName = p.getFileName().toString();
+ this.specificCaseOutputFolder = null;
+ this.specificImageInputFolder = null;
+ this.specificImageOutputFolder = null;
+ this.relativeCaseName = null;
+ this.newCaseName = null;
+ }
+ }
+
+ /**
+ * This is the runnable's run method. It causes the iteration on all .aut
+ * files in the path, calling processCase for each one.
+ */
+ @Override
+ public void run() {
+ openLog();
+ boolean result = true;
+
+ // iterate for .aut files
+ FindDotAutFolders dotAutFolders = new FindDotAutFolders();
+ try {
+ Path walked = Files.walkFileTree(caseInputFolder, dotAutFolders);
+ } catch (IOException ex) {
+ log(ex.getMessage());
+ result = false;
+ }
+
+ ArrayList ableToProcess = new ArrayList<>();
+ ArrayList unableToProcess = new ArrayList<>();
+
+ // validate we can convert this .aut file, one by one
+ for (Path p : dotAutFolders.getTheList()) {
+ ImportCaseData icd = new ImportCaseData(p);
+ icd.setRelativeCaseName(caseInputFolder, p);
+ if (canProcess(icd)) {
+ ableToProcess.add(icd);
+ } else {
+ unableToProcess.add(icd);
+ }
+ }
+
+ StringBuilder casesThatWillBeProcessed = new StringBuilder();
+ StringBuilder casesThatWillNotBeProcessed = new StringBuilder();
+
+ casesThatWillBeProcessed.append(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.WillImport")).append(SEP); // NON-NLS
+ if (ableToProcess.isEmpty()) {
+ casesThatWillBeProcessed.append(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.None")).append(SEP); // NON-NLS
+ } else {
+ for (ImportCaseData icd : ableToProcess) {
+ casesThatWillBeProcessed.append(icd.getSpecificCaseInputFolder().toString()).append(SEP);
+ }
+ }
+
+ if (!unableToProcess.isEmpty()) {
+ casesThatWillNotBeProcessed.append(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.WillNotImport")).append(SEP); // NON-NLS
+ for (ImportCaseData icd : unableToProcess) {
+ casesThatWillNotBeProcessed.append(icd.getSpecificCaseInputFolder().toString()).append(SEP);
+ }
+ }
+
+ JTextArea jta = new JTextArea(casesThatWillBeProcessed.toString() + SEP + casesThatWillNotBeProcessed.toString());
+ jta.setEditable(false);
+ JScrollPane jsp = new JScrollPane(jta) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(700, 480);
+ }
+ };
+
+ SwingUtilities.invokeLater(() -> {
+ userAnswer = JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(),
+ jsp,
+ NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.ContinueWithImport"), // NON-NLS
+ OK_CANCEL_OPTION);
+ synchronized (threadWaitNotifyLock) {
+ threadWaitNotifyLock.notify();
+ }
+ });
+
+ synchronized (threadWaitNotifyLock) {
+ try {
+ threadWaitNotifyLock.wait();
+ } catch (InterruptedException ex) {
+ log("Unable to wait for user input");
+ }
+ }
+
+ if (userAnswer == JOptionPane.OK_OPTION) {
+ // feed .aut files in one by one for processing
+ for (ImportCaseData icd : ableToProcess) {
+ if (false == processCase(icd)) {
+ result = false;
+ }
+ }
+ closeLog(result);
+ if (notifyOnComplete != null) {
+ notifyOnComplete.importDoneCallback(result, ""); // NON-NLS
+ }
+ } else {
+ closeLog(result);
+ if (notifyOnComplete != null) {
+ notifyOnComplete.importDoneCallback(false, NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.Cancelled")); // NON-NLS
+ }
+ }
+ }
+
+ /**
+ * Open the case import log in the base output folder.
+ *
+ */
+ private void openLog() {
+ File temp = new File(caseOutputFolder);
+ temp.mkdirs();
+ File logFile = Paths.get(caseOutputFolder, CASE_IMPORT_LOG_FILE).toFile();
+ try {
+ writer = new PrintWriter(new BufferedWriter(new FileWriter(logFile, logFile.exists())), true);
+ } catch (IOException ex) {
+ writer = null;
+ Logger.getLogger(SingleUserCaseImporter.class.getName()).log(Level.WARNING, "Error opening log file " + logFile.toString(), ex);
+ }
+ log("Starting batch processing of " + caseInputFolder.toString() + " to " + caseOutputFolder);
+ }
+
+ /**
+ * Log a message to the case import log in the base output folder.
+ *
+ * @param message the message to log.
+ */
+ private void log(String message) {
+ if (writer != null) {
+ writer.println(String.format("%s %s", simpleDateFormat.format((Date.from(Instant.now()).getTime())), message)); //NON-NLS
+ }
+ }
+
+ /**
+ *
+ * Close the case import log in the base output folder.
+ *
+ * @param result this informs the log if the end result was successful or
+ * not. True if all was successful, false otherwise.
+ */
+ private void closeLog(boolean result) {
+ log("Completed batch processing of " + caseInputFolder.toString() + " to " + caseOutputFolder + ". Batch processing result: " + ((result == true) ? "Success" : "Failure"));
+ if (writer != null) {
+ writer.close();
+ }
+ }
+
+ /**
+ * This class extends SimpleFileVisitor to find all the cases to process
+ * based upon .aut files.
+ */
+ private class FindDotAutFolders extends SimpleFileVisitor {
+
+ private final ArrayList theList;
+
+ public FindDotAutFolders() {
+ this.theList = new ArrayList<>();
+ }
+
+ /**
+ * Handle comparing .aut file and containing folder names without
+ * timestamps on either one. It strips them off if they exist.
+ *
+ * @param directory the directory we are currently visiting.
+ * @param attrs file attributes.
+ *
+ * @return Continue if we want to carry one, SKIP_SUBTREE if we've found
+ * a .aut file, precluding searching any deeper into this
+ * folder.
+ *
+ * @throws IOException
+ */
+ @Override
+ public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException {
+ // find all files that end in .aut
+ File[] dotAutFiles = directory.toFile().listFiles((File dir, String name) -> name.toLowerCase().endsWith(DOTAUT));
+
+ for (File specificFile : dotAutFiles) {
+ // if it ends in a timestamp, strip it off
+ String sanitizedCaseName = specificFile.getName();
+ if (TimeStampUtils.endsWithTimeStamp(sanitizedCaseName)) {
+ sanitizedCaseName = sanitizedCaseName.substring(0, sanitizedCaseName.length() - TimeStampUtils.getTimeStampLength());
+ }
+
+ // if folder ends in a timestamp, strip it off
+ String sanitizedFolderName = directory.getFileName().toString();
+ if (TimeStampUtils.endsWithTimeStamp(sanitizedFolderName)) {
+ sanitizedFolderName = sanitizedFolderName.substring(0, sanitizedFolderName.length() - TimeStampUtils.getTimeStampLength());
+ }
+
+ // If file and folder match, found leaf node case
+ if (sanitizedCaseName.toLowerCase().startsWith(sanitizedFolderName.toLowerCase())) {
+ theList.add(directory);
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ }
+
+ // If no matching .aut files, traverse subfolders
+ return FileVisitResult.CONTINUE;
+ }
+
+ /**
+ * This returns the list of folders we've found that need to be looked
+ * at for possible import as multi-user cases.
+ *
+ * @return the theList
+ */
+ public ArrayList getTheList() {
+ return theList;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java
index 6fff0a1ff4..d609c9359f 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java
@@ -59,6 +59,7 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa
// set the location of the popUp Window on the center of the screen
setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2);
+ setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
welcomeWindow = new CueBannerPanel();
@@ -80,6 +81,7 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa
@Override
public void open() {
welcomeWindow.refresh();
+ setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
setVisible(true);
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java
index 147337965b..4d00a60606 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,8 +33,10 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent {
int length;
static boolean hasRecentCase = false;
- /** the constructor */
- UpdateRecentCases(){
+ /**
+ * the constructor
+ */
+ UpdateRecentCases() {
// display last 5 cases.
length = RecentCases.LENGTH - 1;
}
@@ -60,7 +62,7 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent {
menuItem.setActionCommand(caseName[i].toUpperCase());
menuItem.addActionListener(new RecentItems(caseName[i], casePath[i]));
comps[i] = menuItem;
- hasRecentCase = hasRecentCase || true;
+ hasRecentCase = true;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java b/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java
index c41269f449..d1e6b87a3a 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2012 Basis Technology Corp.
+ * Copyright 2012-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,10 +29,10 @@ import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
-import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
+import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
@@ -60,6 +60,7 @@ class XMLCaseManagement implements CaseConfigFileInterface {
final static String SCHEMA_VERSION_NAME = "SchemaVersion"; //NON-NLS
final static String AUTOPSY_CRVERSION_NAME = "AutopsyCreatedVersion"; //NON-NLS
final static String AUTOPSY_MVERSION_NAME = "AutopsySavedVersion"; //NON-NLS
+ final static String CASE_TEXT_INDEX_NAME = "TextIndexName"; //NON-NLS
// folders inside case directory
final static String LOG_FOLDER_NAME = "LogFolder"; //NON-NLS
final static String LOG_FOLDER_RELPATH = "Log"; //NON-NLS
@@ -69,6 +70,8 @@ class XMLCaseManagement implements CaseConfigFileInterface {
final static String EXPORT_FOLDER_RELPATH = "Export"; //NON-NLS
final static String CACHE_FOLDER_NAME = "CacheFolder"; //NON-NLS
final static String CACHE_FOLDER_RELPATH = "Cache"; //NON-NLS
+ final static String CASE_TYPE = "CaseType"; //NON-NLS
+ final static String DATABASE_NAME = "DatabaseName"; //NON-NLS
// folders attribute
final static String RELATIVE_NAME = "Relative"; // relevant path info NON-NLS
// folder attr values
@@ -77,23 +80,26 @@ class XMLCaseManagement implements CaseConfigFileInterface {
// the document
private Document doc;
// general info
- private DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)");
+ private final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)");
private String caseDirPath; // case directory path
private String caseName; // case name
- private String caseNumber; // case number
+ private String caseNumber; // case number
private String examiner; // examiner name
- private String schemaVersion = "1.0";
- private String autopsySavedVersion;
+ private final String schemaVersion = "1.0";
+ private final String autopsySavedVersion;
+ private CaseType caseType; // The type of case: local or shared
+ private String dbName; // The name of the database
+ private String textIndexName; // The name of the index where extracted text is stored.
+
// for error handling
private JPanel caller;
- private String className = this.getClass().toString();
+ private final String className = this.getClass().toString();
private static final Logger logger = Logger.getLogger(XMLCaseManagement.class.getName());
/**
* The constructor
*/
- XMLCaseManagement() {
-// System.setProperty("netbeans.buildnumber", autopsyVer); // set the current autopsy version // moved to CoreComponents installer
+ public XMLCaseManagement() {
autopsySavedVersion = System.getProperty("netbeans.buildnumber");
}
@@ -148,6 +154,23 @@ class XMLCaseManagement implements CaseConfigFileInterface {
}
+ /**
+ * Sets the created date on the XML configuration file. This method is for
+ * preserving the created date when converting a case from single-user to
+ * multi-user.
+ *
+ * @param createdDate the date the case was originally created
+ *
+ * @throws org.sleuthkit.autopsy.casemodule.CaseActionException
+ */
+ public void setCreatedDate(String createdDate) throws CaseActionException {
+ String newDate = dateFormat.format(new Date());
+ Element rootEl = getRootElement();
+ rootEl.getElementsByTagName(CREATED_DATE_NAME).item(0).setTextContent(createdDate);
+ rootEl.getElementsByTagName(MODIFIED_DATE_NAME).item(0).setTextContent(newDate);
+ writeFile();
+ }
+
/**
* Sets the examiner on the XML configuration file
*
@@ -187,6 +210,89 @@ class XMLCaseManagement implements CaseConfigFileInterface {
caseNumber = givenCaseNumber; // change this to change the xml file if needed
}
+ /**
+ * Sets the case type internally (on local variable in this class)
+ *
+ * @param givenCaseType the new case type
+ */
+ private void setCaseType(CaseType givenCaseType) {
+ caseType = givenCaseType; // change this to change the xml file if needed
+ }
+
+ /**
+ * Gets the case Type from the document handler. Defaults to local if it
+ * can't figure it out.
+ *
+ * @return caseType from the document handler
+ */
+ public CaseType getCaseType() {
+ if (doc == null) {
+ return CaseType.SINGLE_USER_CASE;
+ } else {
+ if (getCaseElement().getElementsByTagName(CASE_TYPE).getLength() > 0) {
+ Element nameElement = (Element) getCaseElement().getElementsByTagName(CASE_TYPE).item(0);
+ return CaseType.fromString(nameElement.getTextContent());
+ } else {
+ return CaseType.SINGLE_USER_CASE;
+ }
+ }
+ }
+
+ /**
+ * Sets the database name internally (on local variable in this class)
+ *
+ * @param givenDbName the new db name
+ */
+ private void setDatabaseName(String givenDbName) {
+ dbName = givenDbName; // change this to change the xml file if needed
+ }
+
+ /**
+ * Gets the database name from the document handler
+ *
+ * @return the database name
+ */
+ public String getDatabaseName() {
+ if (doc == null) {
+ return "";
+ } else {
+ if (getCaseElement().getElementsByTagName(DATABASE_NAME).getLength() > 0) {
+ Element nameElement = (Element) getCaseElement().getElementsByTagName(DATABASE_NAME).item(0);
+ return nameElement.getTextContent();
+ } else {
+ return ""; /// couldn't find one, so return a blank name
+ }
+ }
+ }
+
+ /**
+ * Sets the text index name internally (on local variable in this class)
+ *
+ * @param textIndexName the new name for the index where extracted text is
+ * stored for the case.
+ */
+ private void setTextIndexName(String textIndexName) {
+ this.textIndexName = textIndexName; // change this to change the xml file if needed
+ }
+
+ /**
+ * Gets the name of the index where extracted text is stored.
+ *
+ * @return the index name
+ */
+ public String getTextIndexName() {
+ if (doc == null) {
+ return "";
+ } else {
+ if (getCaseElement().getElementsByTagName(CASE_TEXT_INDEX_NAME).getLength() > 0) {
+ Element nameElement = (Element) getCaseElement().getElementsByTagName(CASE_TEXT_INDEX_NAME).item(0);
+ return nameElement.getTextContent();
+ } else {
+ return ""; /// couldn't find one, so return a blank index name
+ }
+ }
+ }
+
/**
* Sets the examiner name internally (on local variable in this class)
*
@@ -282,7 +388,7 @@ class XMLCaseManagement implements CaseConfigFileInterface {
*
* @return createdDate the creation date of this case
*/
- protected String getCreatedDate() {
+ public String getCreatedDate() {
if (doc != null) {
Element crDateElement = (Element) getRootElement().getElementsByTagName(CREATED_DATE_NAME).item(0);
return crDateElement.getTextContent();
@@ -437,13 +543,17 @@ class XMLCaseManagement implements CaseConfigFileInterface {
* Initialize the basic values for a new case management file. Note: this is
* the schema version 1.0
*
- * @param dirPath case directory path
- * @param caseName the name of the config file to be located in the case
- * directory
- * @param examiner examiner for the case (optional, can be empty string
- * @param caseNumber case number (optional), can be empty
+ * @param dirPath case directory path
+ * @param caseName the name of the config file to be located in the
+ * case directory
+ * @param examiner examiner for the case (optional, can be empty string
+ * @param caseNumber case number (optional), can be empty
+ * @param dbName the name of the database. Could be a local path,
+ * could be a Postgre db name.
+ * @param textIndexName The name of the index where extracted text is
+ * stored.
*/
- protected void create(String dirPath, String caseName, String examiner, String caseNumber) throws CaseActionException {
+ public void create(String dirPath, String caseName, String examiner, String caseNumber, CaseType caseType, String dbName, String textIndexName) throws CaseActionException {
clear(); // clear the previous data
// set the case Name and Directory and the parent directory
@@ -451,6 +561,9 @@ class XMLCaseManagement implements CaseConfigFileInterface {
setName(caseName);
setExaminer(examiner);
setNumber(caseNumber);
+ setCaseType(caseType);
+ setDatabaseName(dbName);
+ setTextIndexName(textIndexName);
DocumentBuilder docBuilder;
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
@@ -522,6 +635,18 @@ class XMLCaseManagement implements CaseConfigFileInterface {
cacheElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS
caseElement.appendChild(cacheElement);
+ Element typeElement = doc.createElement(CASE_TYPE); // ...
+ typeElement.appendChild(doc.createTextNode(caseType.toString()));
+ caseElement.appendChild(typeElement);
+
+ Element dbNameElement = doc.createElement(DATABASE_NAME); // ...
+ dbNameElement.appendChild(doc.createTextNode(dbName));
+ caseElement.appendChild(dbNameElement);
+
+ Element indexNameElement = doc.createElement(CASE_TEXT_INDEX_NAME); // ...
+ indexNameElement.appendChild(doc.createTextNode(textIndexName));
+ caseElement.appendChild(indexNameElement);
+
// write more code if needed ...
}
@@ -597,19 +722,11 @@ class XMLCaseManagement implements CaseConfigFileInterface {
File file = new File(conFilePath);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- DocumentBuilder db = null;
+ DocumentBuilder db;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(file);
- } catch (ParserConfigurationException ex) {
- throw new CaseActionException(
- NbBundle.getMessage(this.getClass(), "XMLCaseManagement.open.exception.errReadXMLFile.msg",
- conFilePath), ex);
- } catch (SAXException ex) {
- throw new CaseActionException(
- NbBundle.getMessage(this.getClass(), "XMLCaseManagement.open.exception.errReadXMLFile.msg",
- conFilePath), ex);
- } catch (IOException ex) {
+ } catch (ParserConfigurationException | SAXException | IOException ex) {
throw new CaseActionException(
NbBundle.getMessage(this.getClass(), "XMLCaseManagement.open.exception.errReadXMLFile.msg",
conFilePath), ex);
@@ -618,10 +735,10 @@ class XMLCaseManagement implements CaseConfigFileInterface {
doc.getDocumentElement().normalize();
doc.getDocumentElement().normalize();
- if (!XMLUtil.xmlIsValid(doc, XMLCaseManagement.class, XSDFILE)) {
- logger.log(Level.WARNING, "Could not validate against [" + XSDFILE + "], results may not accurate"); //NON-NLS
- }
-
+ // TODO: Restore later
+// if (!XMLUtil.xmlIsValid(doc, XMLCaseManagement.class, XSDFILE)) {
+// logger.log(Level.WARNING, "Could not validate against [" + XSDFILE + "], results may not accurate"); //NON-NLS
+// }
Element rootEl = doc.getDocumentElement();
String rootName = rootEl.getNodeName();
@@ -629,13 +746,16 @@ class XMLCaseManagement implements CaseConfigFileInterface {
if (!rootName.equals(TOP_ROOT_NAME)) {
// throw an error ...
clear();
- JOptionPane.showMessageDialog(caller,
- NbBundle.getMessage(this.getClass(),
- "XMLCaseManagement.open.msgDlg.notAutCase.msg",
- file.getName(), className),
- NbBundle.getMessage(this.getClass(),
- "XMLCaseManagement.open.msgDlg.notAutCase.title"),
- JOptionPane.ERROR_MESSAGE);
+ if (RuntimeProperties.coreComponentsAreActive()) {
+
+ JOptionPane.showMessageDialog(caller,
+ NbBundle.getMessage(this.getClass(),
+ "XMLCaseManagement.open.msgDlg.notAutCase.msg",
+ file.getName(), className),
+ NbBundle.getMessage(this.getClass(),
+ "XMLCaseManagement.open.msgDlg.notAutCase.title"),
+ JOptionPane.ERROR_MESSAGE);
+ }
} else {
/*
* Autopsy Created Version
@@ -687,5 +807,8 @@ class XMLCaseManagement implements CaseConfigFileInterface {
caseName = "";
caseNumber = "";
examiner = "";
+ caseType = CaseType.SINGLE_USER_CASE;
+ dbName = "";
+ textIndexName = "";
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceEvent.java
new file mode 100644
index 0000000000..0df9c8151c
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceEvent.java
@@ -0,0 +1,61 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import java.util.UUID;
+import javax.annotation.concurrent.Immutable;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+
+/**
+ * Event published when a data source is being added to a case.
+ */
+@Immutable
+public final class AddingDataSourceEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private final UUID dataSourceId;
+
+ /**
+ * Constructs an event published when a data source is being added to a
+ * case.
+ *
+ * @param dataSourceId A unique identifier associated with the data source.
+ * Used to pair this AddingDataSourceEvent with a
+ * DataSourceAddedEvent or a
+ * AddingDataSourceFailedEvent.
+ */
+ public AddingDataSourceEvent(UUID dataSourceId) {
+ super(Case.Events.ADDING_DATA_SOURCE.toString(), null, null);
+ this.dataSourceId = dataSourceId;
+ }
+
+ /**
+ * Gets the unique id for the data source used to pair this
+ * AddindDataSourceEvent with a a DataSourceAddedEvent or a
+ * AddingDataSourceFailedEvent.
+ *
+ * @return The unique id.
+ */
+ public UUID getDataSourceId() {
+ return dataSourceId;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceFailedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceFailedEvent.java
new file mode 100644
index 0000000000..d67570bd79
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceFailedEvent.java
@@ -0,0 +1,59 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import java.util.UUID;
+import javax.annotation.concurrent.Immutable;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+
+/**
+ * Event published when an attempt to add a data source to a case fails.
+ */
+@Immutable
+public final class AddingDataSourceFailedEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private final UUID dataSourceId;
+
+ /**
+ * Constructs an event published when an attempt to add a data source to a
+ * case fails.
+ *
+ * @param dataSourceId A unique identifier associated with the data source.
+ * Used to pair this AddingDataSourceFailedEvent with a
+ * AddingDataSourceEvent.
+ */
+ public AddingDataSourceFailedEvent(UUID dataSourceId) {
+ super(Case.Events.ADDING_DATA_SOURCE_FAILED.toString(), null, null);
+ this.dataSourceId = dataSourceId;
+ }
+
+ /**
+ * Gets the unique id for the data source used to pair this
+ * AddingDataSourceFailedEvent with a AddingDataSourceEvent.
+ *
+ * @return The unique id.
+ */
+ public UUID getDataSourceId() {
+ return dataSourceId;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/events/BlackBoardArtifactTagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java
similarity index 55%
rename from Core/src/org/sleuthkit/autopsy/events/BlackBoardArtifactTagAddedEvent.java
rename to Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java
index d50bd2237b..3c3d15aec8 100644
--- a/Core/src/org/sleuthkit/autopsy/events/BlackBoardArtifactTagAddedEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java
@@ -16,18 +16,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.sleuthkit.autopsy.events;
+package org.sleuthkit.autopsy.casemodule.events;
+import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
+import org.sleuthkit.datamodel.TskCoreException;
/**
- *
+ * Event sent when a black board artifact tag is added.
*/
-public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent {
+@Immutable
+public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
public BlackBoardArtifactTagAddedEvent(BlackboardArtifactTag newTag) {
- super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag
- );
+ super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag);
+ }
+
+ /**
+ * get the BlackboardArtifactTag that was added by its id
+ *
+ * @return BlackboardArtifactTag that was added
+ *
+ * @throws IllegalStateException
+ * @throws TskCoreException
+ */
+ @Override
+ BlackboardArtifactTag getTagByID() throws IllegalStateException, TskCoreException {
+ return Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagByTagID(getTagID());
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagDeletedEvent.java
new file mode 100644
index 0000000000..9afef66f37
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagDeletedEvent.java
@@ -0,0 +1,76 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.datamodel.BlackboardArtifactTag;
+
+/**
+ * Event that is fired when a black board artifact tag is deleted.
+ */
+@Immutable
+public class BlackBoardArtifactTagDeletedEvent extends TagDeletedEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public BlackBoardArtifactTagDeletedEvent(BlackboardArtifactTag deletedTag) {
+ super(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString(), new DeletedBlackboardArtifactTagInfo(deletedTag));
+ }
+
+ /**
+ * {@inheritDoc }
+ *
+ * @return the DeletedBlackboardArtifactTagInfo for the deleted tag
+ */
+ @Override
+ public DeletedBlackboardArtifactTagInfo getDeletedTagInfo() {
+ return (DeletedBlackboardArtifactTagInfo) getOldValue();
+ }
+
+ /**
+ * Extension of {@link DeletedTagInfo} for BlackBoardArtifactTags that
+ * includes artifact related info.
+ */
+ @Immutable
+ public static class DeletedBlackboardArtifactTagInfo extends DeletedTagInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final long contentID;
+
+ private final long artifactID;
+
+ private DeletedBlackboardArtifactTagInfo(BlackboardArtifactTag deletedTag) {
+ super(deletedTag);
+ artifactID = deletedTag.getArtifact().getArtifactID();
+ contentID = deletedTag.getContent().getId();
+ }
+
+ @Override
+ public long getContentID() {
+ return contentID;
+ }
+
+ public long getArtifactID() {
+ return artifactID;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/events/ContentTagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java
similarity index 64%
rename from Core/src/org/sleuthkit/autopsy/events/ContentTagAddedEvent.java
rename to Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java
index ffd196a4c7..966138434a 100644
--- a/Core/src/org/sleuthkit/autopsy/events/ContentTagAddedEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java
@@ -16,20 +16,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.sleuthkit.autopsy.events;
+package org.sleuthkit.autopsy.casemodule.events;
+import java.io.Serializable;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.ContentTag;
+import org.sleuthkit.datamodel.TskCoreException;
/**
* An event that is fired when a ContentTag is added.
*/
@Immutable
-public class ContentTagAddedEvent extends TagAddedEvent {
+public class ContentTagAddedEvent extends TagAddedEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
public ContentTagAddedEvent(ContentTag newTag) {
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag);
}
+ /**
+ * get the ContentTag that was added by its id
+ *
+ * @return ContentTag that was added
+ *
+ * @throws IllegalStateException
+ * @throws TskCoreException
+ */
+ ContentTag getTagByID() throws IllegalStateException, TskCoreException {
+ return Case.getCurrentCase().getServices().getTagsManager().getContentTagByTagID(getTagID());
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagDeletedEvent.java
new file mode 100644
index 0000000000..406ef58a0b
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagDeletedEvent.java
@@ -0,0 +1,82 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.datamodel.ContentTag;
+
+/**
+ * An event that is fired when a ContentTag is deleted.
+ */
+@Immutable
+public class ContentTagDeletedEvent extends TagDeletedEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public ContentTagDeletedEvent(ContentTag deletedTag) {
+ super(Case.Events.CONTENT_TAG_DELETED.toString(), new DeletedContentTagInfo(deletedTag));
+ }
+
+ /**
+ * {@inheritDoc }
+ *
+ * @return the DeletedContentTagInfo for the deleted tag
+ */
+ @Override
+ public DeletedContentTagInfo getDeletedTagInfo() {
+ return (DeletedContentTagInfo) getOldValue();
+ }
+
+ /**
+ * Extension of {@link DeletedTagInfo} for BlackBoardArtifactTags that
+ * includes byte offset related info.
+ */
+ @Immutable
+ public static class DeletedContentTagInfo extends DeletedTagInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final long contentID;
+ private final long beginByteOffset;
+ private final long endByteOffset;
+
+ private DeletedContentTagInfo(ContentTag deletedTag) {
+ super(deletedTag);
+ beginByteOffset = deletedTag.getBeginByteOffset();
+ endByteOffset = deletedTag.getEndByteOffset();
+ contentID = deletedTag.getContent().getId();
+
+ }
+
+ @Override
+ public long getContentID() {
+ return contentID;
+ }
+
+ public long getBeginByteOffset() {
+ return beginByteOffset;
+ }
+
+ public long getEndByteOffset() {
+ return endByteOffset;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/DataSourceAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/DataSourceAddedEvent.java
new file mode 100644
index 0000000000..bba2fba8c3
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/DataSourceAddedEvent.java
@@ -0,0 +1,108 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import java.util.UUID;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Event published when a data source is added to a case.
+ */
+public final class DataSourceAddedEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private static final Logger logger = Logger.getLogger(DataSourceAddedEvent.class.getName());
+ private transient Content dataSource;
+ private final UUID dataSourceId;
+
+ /**
+ * Constructs an event published when a data source is added to a case.
+ *
+ * @param dataSource The data source that was added.
+ * @param dataSourceId A unique identifier associated with the data source.
+ * Used to pair this DataSourceAddedEvent with a
+ * AddindDataSourceEvent.
+ */
+ public DataSourceAddedEvent(Content dataSource, UUID dataSourceId) {
+ /**
+ * Putting the object id of the data source into newValue to allow for
+ * lazy loading of the Content object. This bypasses the issues related
+ * to the serialization and de-serialization of Content objects when the
+ * event is published over a network.
+ */
+ super(Case.Events.DATA_SOURCE_ADDED.toString(), null, dataSource.getId());
+ this.dataSource = dataSource;
+ this.dataSourceId = dataSourceId;
+ }
+
+ /**
+ * Gets the data source that was added.
+ *
+ * @return The data source or null if there is an error retrieving the data
+ * source.
+ */
+ @Override
+ public Object getNewValue() {
+ /**
+ * The dataSource field is set in the constructor, but it is transient
+ * so it will become null when the event is serialized for publication
+ * over a network. Doing a lazy load of the Content object bypasses the
+ * issues related to the serialization and de-serialization of Content
+ * objects and may also save database round trips from other nodes since
+ * subscribers to this event are often not interested in the event data.
+ */
+ if (null != dataSource) {
+ return dataSource;
+ }
+ try {
+ long id = (Long) super.getNewValue();
+ dataSource = Case.getCurrentCase().getSleuthkitCase().getContentById(id);
+ return dataSource;
+ } catch (IllegalStateException | TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error doing lazy load for remote event", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the data source that was added.
+ *
+ * @return The data source.
+ */
+ public Content getDataSource() {
+ return (Content) getNewValue();
+ }
+
+ /**
+ * Gets the unique id for the data source used to pair this
+ * DataSourceAddedEvent with a AddingDataSourceEvent.
+ *
+ * @return The unique id.
+ */
+ public UUID getDataSourceId() {
+ return dataSourceId;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java
new file mode 100644
index 0000000000..84121ad2de
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java
@@ -0,0 +1,86 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+import org.sleuthkit.datamodel.Report;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Event published when a report is added to a case.
+ */
+public final class ReportAddedEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private static final Logger logger = Logger.getLogger(DataSourceAddedEvent.class.getName());
+ private transient Report report;
+
+ /**
+ * Constructs an event published when a report is added to a case.
+ *
+ * @param report The data source that was added.
+ */
+ public ReportAddedEvent(Report report) {
+ /**
+ * Putting the object id of the report into newValue to allow for lazy
+ * loading of the Report object.
+ */
+ super(Case.Events.REPORT_ADDED.toString(), null, report.getId());
+ this.report = report;
+ }
+
+ /**
+ * Gets the data source that was added.
+ *
+ * @return The data source.
+ */
+ @Override
+ public Object getNewValue() {
+ /**
+ * The report field is set in the constructor, but it is transient so it
+ * will become null when the event is serialized for publication over a
+ * network. Doing a lazy load of the Report object may save database
+ * round trips from other nodes since subscribers to this event are
+ * often not interested in the event data.
+ */
+ if (null != report) {
+ return report;
+ }
+ try {
+ long id = (Long) super.getNewValue();
+ List reports = Case.getCurrentCase().getSleuthkitCase().getAllReports();
+ for (Report thisReport : reports) {
+ if (thisReport.getId() == id) {
+ report = thisReport;
+ break;
+ }
+ }
+ return report;
+ } catch (IllegalStateException | TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error doing lazy load for remote event", ex);
+ return null;
+ }
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java
new file mode 100644
index 0000000000..88cbd023bb
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java
@@ -0,0 +1,105 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+import org.sleuthkit.datamodel.Tag;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Base Class for events that are fired when a Tag is added
+ */
+abstract class TagAddedEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The tag that was added. This will be lost during serialization and
+ * re-loaded from the database in getNewValue()
+ */
+ private transient T tag;
+
+ /**
+ * The id of the tag that was added. This will bu used to re-load the
+ * transient tag from the database.
+ */
+ private final Long tagID;
+
+ TagAddedEvent(String propertyName, T addedTag) {
+ super(propertyName, null, null);
+ tag = addedTag;
+ tagID = addedTag.getId();
+ }
+
+ /**
+ * get the id of the Tag that was added
+ *
+ * @return the id of the Tag that was added
+ */
+ Long getTagID() {
+ return tagID;
+ }
+
+ /**
+ * get the Tag that was added
+ *
+ * @return the tTag
+ */
+ public T getAddedTag() {
+ return getNewValue();
+ }
+
+ @Override
+ public T getNewValue() {
+ /**
+ * The tag field is set in the constructor, but it is transient so it
+ * will become null when the event is serialized for publication over a
+ * network. Doing a lazy load of the Tag object bypasses the issues
+ * related to the serialization and de-serialization of Tag objects and
+ * may also save database round trips from other nodes since subscribers
+ * to this event are often not interested in the event data.
+ */
+ if (null != tag) {
+ return tag;
+ }
+ try {
+ tag = getTagByID();
+ return tag;
+ } catch (IllegalStateException | TskCoreException ex) {
+ Logger.getLogger(TagAddedEvent.class.getName()).log(Level.SEVERE, "Error doing lazy load for remote event", ex);
+ return null;
+ }
+ }
+
+ /**
+ * implementors should override this to lookup the appropriate kind of tag
+ * (Content/BlackBoardArtifact) during the lazy load of the transient tag
+ * field
+ *
+ *
+ * @return the Tag based on the saved tag id
+ *
+ * @throws IllegalStateException
+ * @throws TskCoreException
+ */
+ abstract T getTagByID() throws IllegalStateException, TskCoreException;
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagDeletedEvent.java
new file mode 100644
index 0000000000..a0ba651f53
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagDeletedEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.events;
+
+import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+import org.sleuthkit.datamodel.Tag;
+import org.sleuthkit.datamodel.TagName;
+
+/**
+ * Base Class for events that are fired when a Tag is deleted
+ */
+@Immutable
+abstract class TagDeletedEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ TagDeletedEvent(String propertyName, DeletedTagInfo deletedTagInfo) {
+ super(propertyName, deletedTagInfo, null);
+ }
+
+ /**
+ * get info about the Tag that was deleted.
+ *
+ * Since we don't serialize the deleted tag, and we can't look it up by id
+ * after it is deleted, we send this info to represent it.
+ *
+ * @return info about the Tag that was deleted.
+ */
+ @SuppressWarnings("unchecked")
+ abstract public DeletedTagInfo getDeletedTagInfo();
+
+ /**
+ * Base Class for info about a deleted tag. This is sent as the old value in
+ * the event, since we are not serializing the Tag it self, and we can't
+ * look it up by id, like we do for added tags, because it doesn't exist in
+ * the db any more.
+ *
+ * @param the subtype of Tag, BlackBoardArtifactTag or ContentTag
+ */
+ @Immutable
+ abstract static class DeletedTagInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String comment;
+ private final long tagID;
+ private final TagName name;
+
+ DeletedTagInfo(T deletedTag) {
+ comment = deletedTag.getComment();
+ tagID = deletedTag.getId();
+ name = deletedTag.getName();
+ }
+
+ abstract public long getContentID();
+
+ public String getComment() {
+ return comment;
+ }
+
+ public long getTagID() {
+ return tagID;
+ }
+
+ public TagName getName() {
+ return name;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties
index b267fde0a2..67ed2fe8cd 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties
@@ -15,7 +15,7 @@ TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0
TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} out of content size range (0 - {1})
TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset
TagsManager.predefTagNames.bookmark.text=Bookmark
-TagsManager.addContentTag.noCaseWarning=Failed to add publish new content tag event. There is no case open.
-TagsManager.deleteContentTag.noCaseWarning=Failed to add publish content tag deleted event. There is no case open.
-TagsManager.addBlackboardArtifactTag.noCaseWarning=Failed to add publish new blackboard artifact tag event. There is no case open.
-TagsManager.deleteBlackboardArtifactTag.noCaseWarning=Failed to add publish blackboard artifact tag deleted event. There is no case open.
+TagsManager.addContentTag.noCaseWarning=Failed to publish new content tag event. There is no case open.
+TagsManager.deleteContentTag.noCaseWarning=Failed to publish content tag deleted event. There is no case open.
+TagsManager.addBlackboardArtifactTag.noCaseWarning=Failed to publish new blackboard artifact tag event. There is no case open.
+TagsManager.deleteBlackboardArtifactTag.noCaseWarning=Failed to publish blackboard artifact tag deleted event. There is no case open.
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
index aa79469d4e..7501cf052a 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
@@ -272,7 +272,7 @@ public class TagsManager implements Closeable {
tskCase.deleteContentTag(tag);
try {
Case.getCurrentCase().notifyContentTagDeleted(tag);
- } catch (IllegalArgumentException e) {
+ } catch (IllegalStateException e) {
Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.deleteContentTag.noCaseWarning"));
}
}
@@ -311,6 +311,24 @@ public class TagsManager implements Closeable {
return tskCase.getContentTagsCountByTagName(tagName);
}
+ /**
+ * Gets a content tag by tag id.
+ *
+ * @param tagID The tag id of interest.
+ *
+ * @return the content tag with the specified tag id.
+ *
+ * @throws TskCoreException
+ */
+ public synchronized ContentTag getContentTagByTagID(long tagID) throws TskCoreException {
+ // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
+ if (!tagNamesInitialized) {
+ getExistingTagNames();
+ }
+
+ return tskCase.getContentTagByID(tagID);
+ }
+
/**
* Gets content tags by tag name.
*
@@ -385,7 +403,7 @@ public class TagsManager implements Closeable {
BlackboardArtifactTag addBlackboardArtifactTag = tskCase.addBlackboardArtifactTag(artifact, tagName, comment);
try {
Case.getCurrentCase().notifyBlackBoardArtifactTagAdded(addBlackboardArtifactTag);
- } catch (IllegalArgumentException e) {
+ } catch (IllegalStateException e) {
Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.addBlackboardArtifactTag.noCaseWarning"));
}
return addBlackboardArtifactTag;
@@ -407,7 +425,7 @@ public class TagsManager implements Closeable {
tskCase.deleteBlackboardArtifactTag(tag);
try {
Case.getCurrentCase().notifyBlackBoardArtifactTagDeleted(tag);
- } catch (IllegalArgumentException e) {
+ } catch (IllegalStateException e) {
Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.deleteBlackboardArtifactTag.noCaseWarning"));
}
}
@@ -447,6 +465,24 @@ public class TagsManager implements Closeable {
return tskCase.getBlackboardArtifactTagsCountByTagName(tagName);
}
+ /**
+ * Gets a blackboard artifact tag by tag id.
+ *
+ * @param tagID The tag id of interest.
+ *
+ * @return the blackboard artifact tag with the specified tag id.
+ *
+ * @throws TskCoreException
+ */
+ public synchronized BlackboardArtifactTag getBlackboardArtifactTagByTagID(long tagID) throws TskCoreException {
+ // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
+ if (!tagNamesInitialized) {
+ getExistingTagNames();
+ }
+
+ return tskCase.getBlackboardArtifactTagByID(tagID);
+ }
+
/**
* Gets blackboard artifact tags by tag name.
*
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
index 6fd097e565..38eb6a3599 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
@@ -12,7 +12,7 @@ Metadata.tableRowTitle.hashLookupResults=Hash Lookup Results
Metadata.tableRowTitle.internalid=Internal ID
Metadata.tableRowTitle.localPath=Local Path
Metadata.tableRowTitle.type=Type
-Metadata.title=Metadata
+Metadata.title=File Metadata
Metadata.toolTip=Displays metadata about the file.
Metadata.nodeText.nonFilePassedIn=Non-file passed in
Metadata.nodeText.text=From The Sleuth Kit istat Tool\:
diff --git a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties
index ff08a90d89..913f6d8c82 100644
--- a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties
@@ -12,3 +12,15 @@ org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xm
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).
+ServicesMonitor.failedService.notify.title=Service Is Down
+ServicesMonitor.failedService.notify.msg=Connection to {0} is down
+ServicesMonitor.restoredService.notify.title=Service Is Up
+ServicesMonitor.restoredService.notify.msg=Connection to {0} is up
+ServicesMonitor.statusChange.notify.title=Service Status Update
+ServicesMonitor.statusChange.notify.msg=Status for {0} is {1}
+ServicesMonitor.nullServiceName.excepton.txt=Requested service name is null
+ServicesMonitor.unknownServiceName.excepton.txt=Requested service name {0} is unknown
+TextConverter.convert.exception.txt=Unable to convert text {0} to hex text
+TextConverter.convertFromHex.exception.txt=Unable to convert hex text to text
+ServicesMonitor.KeywordSearchNull=Cannot find Keyword Search service
+ServicesMonitor.InvalidPortNumber=Invalid port number.
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java
index 8f43e21eef..3c42f3aac4 100644
--- a/Core/src/org/sleuthkit/autopsy/core/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java
@@ -66,9 +66,10 @@ public class Installer extends ModuleInstall {
//We should update this if we officially switch to a new version of CRT/compiler
System.loadLibrary("msvcr100"); //NON-NLS
System.loadLibrary("msvcp100"); //NON-NLS
- logger.log(Level.INFO, "MS CRT libraries loaded"); //NON-NLS
+ System.loadLibrary("msvcr120"); //NON-NLS
+ logger.log(Level.INFO, "MSVCR100 and MSVCP100 libraries loaded"); //NON-NLS
} catch (UnsatisfiedLinkError e) {
- logger.log(Level.SEVERE, "Error loading ms crt libraries, ", e); //NON-NLS
+ logger.log(Level.SEVERE, "Error loading MSVCR100 and MSVCP100 libraries, ", e); //NON-NLS
}
try {
@@ -84,6 +85,34 @@ public class Installer extends ModuleInstall {
} catch (UnsatisfiedLinkError e) {
logger.log(Level.SEVERE, "Error loading EWF library, ", e); //NON-NLS
}
+
+ try {
+ System.loadLibrary("libeay32"); //NON-NLS
+ logger.log(Level.INFO, "LIBEAY32 library loaded"); //NON-NLS
+ } catch (UnsatisfiedLinkError e) {
+ logger.log(Level.SEVERE, "Error loading LIBEAY32 library, ", e); //NON-NLS
+ }
+
+ try {
+ System.loadLibrary("ssleay32"); //NON-NLS
+ logger.log(Level.INFO, "SSLEAY32 library loaded"); //NON-NLS
+ } catch (UnsatisfiedLinkError e) {
+ logger.log(Level.SEVERE, "Error loading SSLEAY32 library, ", e); //NON-NLS
+ }
+
+ try {
+ System.loadLibrary("libintl-8"); //NON-NLS
+ logger.log(Level.INFO, "libintl-8 library loaded"); //NON-NLS
+ } catch (UnsatisfiedLinkError e) {
+ logger.log(Level.SEVERE, "Error loading libintl-8 library, ", e); //NON-NLS
+ }
+
+ try {
+ System.loadLibrary("libpq"); //NON-NLS
+ logger.log(Level.INFO, "LIBPQ library loaded"); //NON-NLS
+ } catch (UnsatisfiedLinkError e) {
+ logger.log(Level.SEVERE, "Error loading LIBPQ library, ", e); //NON-NLS
+ }
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java
new file mode 100644
index 0000000000..c2c74bfff3
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java
@@ -0,0 +1,62 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2013-2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.core;
+
+/**
+ * Application properties that are set once at runtime and are not saved between
+ * invocations of Autopsy.
+ */
+public class RuntimeProperties {
+
+ private static boolean coreComponentsActive = true;
+ private static boolean coreComponentsActiveSet = false;
+
+ /**
+ * Sets or unsets a flag indicating whether or not the core Autopsy UI
+ * components and user interactions with those components via menus, message
+ * boxes, NetBeans progress handles, etc., are enabled.
+ *
+ * This flag exists as a mechanism to allow use of Autopsy as a platform
+ * with the core Autopsy user interface disabled, until such time as the
+ * user interface is made separable and optional.
+ *
+ * @param coreComponentsActive True or false.
+ */
+ public static void setCoreComponentsActive(boolean coreComponentsActive) {
+ if (!coreComponentsActiveSet) {
+ RuntimeProperties.coreComponentsActive = coreComponentsActive;
+ coreComponentsActiveSet = true;
+ }
+ }
+
+ /**
+ * Gets a flag indicating whether or not the core Autopsy UI components and
+ * user interactions with those components via menus, message boxes,
+ * NetBeans progress handles, etc., are enabled.
+ *
+ * This flag exists as a mechanism to allow use of Autopsy as a platform
+ * with the core Autopsy user interface disabled, until such time as the
+ * user interface is made separable and optional.
+ *
+ * @return True or false.
+ */
+ public static boolean coreComponentsAreActive() {
+ return coreComponentsActive;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java b/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java
new file mode 100644
index 0000000000..4c1fb92989
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java
@@ -0,0 +1,411 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2013-2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.core;
+
+import org.sleuthkit.autopsy.core.events.ServiceEvent;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.beans.PropertyChangeListener;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
+import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
+import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
+import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo;
+import org.sleuthkit.autopsy.events.MessageServiceException;
+import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
+import org.sleuthkit.datamodel.CaseDbConnectionInfo;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * This class periodically checks availability of collaboration resources -
+ * remote database, remote keyword search server, messaging service - and
+ * reports status updates to the user in case of a gap in service.
+ */
+public class ServicesMonitor {
+
+ private AutopsyEventPublisher eventPublisher;
+ private static final Logger logger = Logger.getLogger(ServicesMonitor.class.getName());
+ private final ScheduledThreadPoolExecutor periodicTasksExecutor;
+
+ private static final String PERIODIC_TASK_THREAD_NAME = "services-monitor-periodic-task-%d";
+ private static final int NUMBER_OF_PERIODIC_TASK_THREADS = 1;
+ private static final long CRASH_DETECTION_INTERVAL_MINUTES = 2;
+
+ private static final Set servicesList = Stream.of(ServicesMonitor.Service.values())
+ .map(Service::toString)
+ .collect(Collectors.toSet());
+
+ /**
+ * The service monitor maintains a mapping of each service to it's last
+ * status update.
+ */
+ private final ConcurrentHashMap statusByService;
+
+ /**
+ * Call constructor on start-up so that the first check of services is done
+ * as soon as possible.
+ */
+ private static ServicesMonitor instance = new ServicesMonitor();
+
+ /**
+ * List of services that are being monitored. The service names should be
+ * representative of the service functionality and readable as they get
+ * logged when service outage occurs.
+ */
+ public enum Service {
+
+ /**
+ * Property change event fired when remote case database service status
+ * changes. New value is set to updated ServiceStatus, old value is
+ * null.
+ */
+ REMOTE_CASE_DATABASE("Multi-user case database service"),
+ /**
+ * Property change event fired when remote keyword search service status
+ * changes. New value is set to updated ServiceStatus, old value is
+ * null.
+ */
+ REMOTE_KEYWORD_SEARCH("Multi-user keyword search service"),
+ /**
+ * Property change event fired when messaging service status changes.
+ * New value is set to updated ServiceStatus, old value is null.
+ */
+ MESSAGING("Messaging service");
+
+ private final String displayName;
+
+ private Service(String name) {
+ this.displayName = name;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+ };
+
+ /**
+ * List of possible service statuses.
+ */
+ public enum ServiceStatus {
+
+ /**
+ * Service is currently up.
+ */
+ UP,
+ /**
+ * Service is currently down.
+ */
+ DOWN
+ };
+
+ public synchronized static ServicesMonitor getInstance() {
+ if (instance == null) {
+ instance = new ServicesMonitor();
+ }
+ return instance;
+ }
+
+ private ServicesMonitor() {
+
+ this.eventPublisher = new AutopsyEventPublisher();
+ this.statusByService = new ConcurrentHashMap<>();
+
+ // First check is triggered immediately on current thread.
+ checkAllServices();
+
+ /**
+ * Start periodic task that check the availability of key collaboration
+ * services.
+ */
+ periodicTasksExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_PERIODIC_TASK_THREADS, new ThreadFactoryBuilder().setNameFormat(PERIODIC_TASK_THREAD_NAME).build());
+ periodicTasksExecutor.scheduleAtFixedRate(new CrashDetectionTask(), CRASH_DETECTION_INTERVAL_MINUTES, CRASH_DETECTION_INTERVAL_MINUTES, TimeUnit.MINUTES);
+ }
+
+ /**
+ * Updates service status and publishes the service status update if it is
+ * different from previous status. Event is published locally. Logs status
+ * changes.
+ *
+ * @param service Name of the service.
+ * @param status Updated status for the service.
+ * @param details Details of the event.
+ *
+ */
+ public void setServiceStatus(String service, String status, String details) {
+ // if the status update is for an existing service who's status hasn't changed - do nothing.
+ if (statusByService.containsKey(service) && status.equals(statusByService.get(service))) {
+ return;
+ }
+
+ // new service or status has changed - identify service's display name
+ String serviceDisplayName;
+ try {
+ serviceDisplayName = ServicesMonitor.Service.valueOf(service).getDisplayName();
+ } catch (IllegalArgumentException ignore) {
+ // custom service that is not listed in ServicesMonitor.Service enum. Use service name as display name.
+ serviceDisplayName = service;
+ }
+
+ if (status.equals(ServiceStatus.UP.toString())) {
+ logger.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS
+ MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"),
+ NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.msg", serviceDisplayName));
+ } else if (status.equals(ServiceStatus.DOWN.toString())) {
+ logger.log(Level.SEVERE, "Failed to connect to {0}", serviceDisplayName); //NON-NLS
+ MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"),
+ NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.msg", serviceDisplayName));
+ } else {
+ logger.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS
+ MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.title"),
+ NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.msg", new Object[]{serviceDisplayName, status}));
+ }
+
+ // update and publish new status
+ statusByService.put(service, status);
+ eventPublisher.publishLocally(new ServiceEvent(service, status, details));
+ }
+
+ /**
+ * Get last status update for a service.
+ *
+ * @param service Name of the service.
+ *
+ * @return ServiceStatus Status for the service.
+ *
+ * @throws ServicesMonitorException If service name is null or service
+ * doesn't exist.
+ */
+ public String getServiceStatus(String service) throws ServicesMonitorException {
+
+ if (service == null) {
+ throw new ServicesMonitorException(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.nullServiceName.excepton.txt"));
+ }
+
+ // if request is for one of our "core" services - perform an on demand check
+ // to make sure we have the latest status.
+ if (servicesList.contains(service)) {
+ checkServiceStatus(service);
+ }
+
+ String status = statusByService.get(service);
+ if (status == null) {
+ // no such service
+ throw new ServicesMonitorException(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.unknownServiceName.excepton.txt", service));
+ }
+ return status;
+ }
+
+ /**
+ * Performs service availability status check.
+ *
+ * @param service Name of the service.
+ */
+ private void checkServiceStatus(String service) {
+ if (service.equals(Service.REMOTE_CASE_DATABASE.toString())) {
+ checkDatabaseConnectionStatus();
+ } else if (service.equals(Service.REMOTE_KEYWORD_SEARCH.toString())) {
+ checkKeywordSearchServerConnectionStatus();
+ } else if (service.equals(Service.MESSAGING.toString())) {
+ checkMessagingServerConnectionStatus();
+ }
+ }
+
+ /**
+ * Performs case database service availability status check.
+ */
+ private void checkDatabaseConnectionStatus() {
+ CaseDbConnectionInfo info;
+ try {
+ info = UserPreferences.getDatabaseConnectionInfo();
+ } catch (UserPreferencesException ex) {
+ logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
+ setServiceStatus(Service.REMOTE_CASE_DATABASE.toString(), ServiceStatus.DOWN.toString(), "Error accessing case database connection info");
+ return;
+ }
+ try {
+ SleuthkitCase.tryConnect(info);
+ setServiceStatus(Service.REMOTE_CASE_DATABASE.toString(), ServiceStatus.UP.toString(), "");
+ } catch (TskCoreException ex) {
+ setServiceStatus(Service.REMOTE_CASE_DATABASE.toString(), ServiceStatus.DOWN.toString(), ex.getMessage());
+ }
+ }
+
+ /**
+ * Performs keyword search service availability status check.
+ */
+ private void checkKeywordSearchServerConnectionStatus() {
+ KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class);
+ try {
+ if (kwsService != null) {
+ int port = Integer.parseUnsignedInt(UserPreferences.getIndexingServerPort());
+ kwsService.tryConnect(UserPreferences.getIndexingServerHost(), port);
+ setServiceStatus(Service.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.UP.toString(), "");
+ } else {
+ setServiceStatus(Service.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.DOWN.toString(),
+ NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.KeywordSearchNull"));
+ }
+ } catch (NumberFormatException ex) {
+ String rootCause = NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.InvalidPortNumber");
+ logger.log(Level.SEVERE, "Unable to connect to messaging server: " + rootCause, ex); //NON-NLS
+ setServiceStatus(Service.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.DOWN.toString(), rootCause);
+ } catch (KeywordSearchServiceException ex) {
+ String rootCause = ex.getMessage();
+ logger.log(Level.SEVERE, "Unable to connect to messaging server: " + rootCause, ex); //NON-NLS
+ setServiceStatus(Service.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.DOWN.toString(), rootCause);
+ }
+ }
+
+ /**
+ * Performs messaging service availability status check.
+ */
+ private void checkMessagingServerConnectionStatus() {
+ MessageServiceConnectionInfo info;
+ try {
+ info = UserPreferences.getMessageServiceConnectionInfo();
+ } catch (UserPreferencesException ex) {
+ logger.log(Level.SEVERE, "Error accessing messaging service connection info", ex); //NON-NLS
+ setServiceStatus(Service.MESSAGING.toString(), ServiceStatus.DOWN.toString(), "Error accessing messaging service connection info");
+ return;
+ }
+
+ try {
+ info.tryConnect();
+ setServiceStatus(Service.MESSAGING.toString(), ServiceStatus.UP.toString(), "");
+ } catch (MessageServiceException ex) {
+ String rootCause = ex.getMessage();
+ logger.log(Level.SEVERE, "Unable to connect to messaging server: " + rootCause, ex); //NON-NLS
+ setServiceStatus(Service.MESSAGING.toString(), ServiceStatus.DOWN.toString(), rootCause);
+ }
+ }
+
+ /**
+ * Adds an event subscriber to this publisher. Subscriber will be subscribed
+ * to all events from this publisher.
+ *
+ * @param subscriber The subscriber to add.
+ */
+ public void addSubscriber(PropertyChangeListener subscriber) {
+ eventPublisher.addSubscriber(servicesList, subscriber);
+ }
+
+ /**
+ * Adds an event subscriber to this publisher.
+ *
+ * @param eventNames The events the subscriber is interested in.
+ * @param subscriber The subscriber to add.
+ */
+ public void addSubscriber(Set eventNames, PropertyChangeListener subscriber) {
+ eventPublisher.addSubscriber(eventNames, subscriber);
+ }
+
+ /**
+ * Adds an event subscriber to this publisher.
+ *
+ * @param eventName The event the subscriber is interested in.
+ * @param subscriber The subscriber to add.
+ */
+ public void addSubscriber(String eventName, PropertyChangeListener subscriber) {
+ eventPublisher.addSubscriber(eventName, subscriber);
+ }
+
+ /**
+ * Removes an event subscriber from this publisher.
+ *
+ * @param eventNames The events the subscriber is no longer interested in.
+ * @param subscriber The subscriber to remove.
+ */
+ public void removeSubscriber(Set eventNames, PropertyChangeListener subscriber) {
+ eventPublisher.removeSubscriber(eventNames, subscriber);
+ }
+
+ /**
+ * Removes an event subscriber from this publisher.
+ *
+ * @param eventName The event the subscriber is no longer interested in.
+ * @param subscriber The subscriber to remove.
+ */
+ public void removeSubscriber(String eventName, PropertyChangeListener subscriber) {
+ eventPublisher.removeSubscriber(eventName, subscriber);
+ }
+
+ /**
+ * Removes an event subscriber to this publisher. Subscriber will be removed
+ * from all event notifications from this publisher.
+ *
+ * @param subscriber The subscriber to remove.
+ */
+ public void removeSubscriber(PropertyChangeListener subscriber) {
+ eventPublisher.removeSubscriber(servicesList, subscriber);
+ }
+
+ /**
+ * Verifies connectivity to all services.
+ */
+ private void checkAllServices() {
+ if (!UserPreferences.getIsMultiUserModeEnabled()) {
+ return;
+ }
+
+ for (String service : servicesList) {
+ checkServiceStatus(service);
+ }
+ }
+
+ /**
+ * A Runnable task that periodically checks the availability of
+ * collaboration resources (remote database, remote keyword search service,
+ * message broker) and reports status to the user in case of a gap in
+ * service.
+ */
+ private final class CrashDetectionTask implements Runnable {
+
+ /**
+ * Monitor the availability of collaboration resources
+ */
+ @Override
+ public void run() {
+ checkAllServices();
+ }
+ }
+
+ /**
+ * Exception thrown when service status query results in an error.
+ */
+ public class ServicesMonitorException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public ServicesMonitorException(String message) {
+ super(message);
+ }
+
+ public ServicesMonitorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
index 5349915871..a9909593b0 100755
--- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
+++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
@@ -18,9 +18,20 @@
*/
package org.sleuthkit.autopsy.core;
+import java.util.Base64;
+import java.util.prefs.BackingStoreException;
+import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
+import org.sleuthkit.datamodel.CaseDbConnectionInfo;
+import org.sleuthkit.datamodel.TskData.DbType;
/**
* Provides convenient access to a Preferences node for user preferences with
@@ -34,11 +45,50 @@ public final class UserPreferences {
public static final String HIDE_KNOWN_FILES_IN_VIEWS_TREE = "HideKnownFilesInViewsTree"; //NON-NLS
public static final String DISPLAY_TIMES_IN_LOCAL_TIME = "DisplayTimesInLocalTime"; //NON-NLS
public static final String NUMBER_OF_FILE_INGEST_THREADS = "NumberOfFileIngestThreads"; //NON-NLS
+ public static final String IS_MULTI_USER_MODE_ENABLED = "IsMultiUserModeEnabled"; //NON-NLS
+ public static final String EXTERNAL_DATABASE_HOSTNAME_OR_IP = "ExternalDatabaseHostnameOrIp"; //NON-NLS
+ public static final String EXTERNAL_DATABASE_PORTNUMBER = "ExternalDatabasePortNumber"; //NON-NLS
+ public static final String EXTERNAL_DATABASE_NAME = "ExternalDatabaseName"; //NON-NLS
+ public static final String EXTERNAL_DATABASE_USER = "ExternalDatabaseUsername"; //NON-NLS
+ public static final String EXTERNAL_DATABASE_PASSWORD = "ExternalDatabasePassword"; //NON-NLS
+ public static final String EXTERNAL_DATABASE_TYPE = "ExternalDatabaseType"; //NON-NLS
+ public static final String INDEXING_SERVER_HOST = "IndexingServerHost"; //NON-NLS
+ public static final String INDEXING_SERVER_PORT = "IndexingServerPort"; //NON-NLS
+ private static final String MESSAGE_SERVICE_PASSWORD = "MessageServicePassword"; //NON-NLS
+ private static final String MESSAGE_SERVICE_USER = "MessageServiceUser"; //NON-NLS
+ private static final String MESSAGE_SERVICE_HOST = "MessageServiceHost"; //NON-NLS
+ private static final String MESSAGE_SERVICE_PORT = "MessageServicePort"; //NON-NLS
+ public static final String PROCESS_TIME_OUT_ENABLED = "ProcessTimeOutEnabled"; //NON-NLS
+ public static final String PROCESS_TIME_OUT_HOURS = "ProcessTimeOutHours"; //NON-NLS
+ private static final int DEFAULT_PROCESS_TIMEOUT_HR = 60;
+ private static final String DEFAULT_PORT_STRING = "61616";
+ private static final int DEFAULT_PORT_INT = 61616;
// Prevent instantiation.
private UserPreferences() {
}
+ /**
+ * Reload all preferences from disk. This is only needed if the preferences
+ * file is being directly modified on disk while Autopsy is running.
+ *
+ * @throws BackingStoreException
+ */
+ public static void reloadFromStorage() throws BackingStoreException {
+ preferences.sync();
+ }
+
+ /**
+ * Saves the current preferences to storage. This is only needed if the
+ * preferences files are going to be copied to another location while
+ * Autopsy is running.
+ *
+ * @throws BackingStoreException
+ */
+ public static void saveToStorage() throws BackingStoreException {
+ preferences.flush();
+ }
+
public static void addChangeListener(PreferenceChangeListener listener) {
preferences.addPreferenceChangeListener(listener);
}
@@ -87,4 +137,209 @@ public final class UserPreferences {
preferences.putInt(NUMBER_OF_FILE_INGEST_THREADS, value);
}
+ /**
+ * Reads persisted case database connection info.
+ * @return An object encapsulating the database connection info.
+ * @throws org.sleuthkit.autopsy.core.UserPreferencesException
+ */
+ public static CaseDbConnectionInfo getDatabaseConnectionInfo() throws UserPreferencesException {
+ DbType dbType;
+ try {
+ dbType = DbType.valueOf(preferences.get(EXTERNAL_DATABASE_TYPE, "POSTGRESQL"));
+ } catch (Exception ex) {
+ dbType = DbType.SQLITE;
+ }
+ return new CaseDbConnectionInfo(
+ preferences.get(EXTERNAL_DATABASE_HOSTNAME_OR_IP, ""),
+ preferences.get(EXTERNAL_DATABASE_PORTNUMBER, "5432"),
+ preferences.get(EXTERNAL_DATABASE_USER, ""),
+ TextConverter.convertHexTextToText(preferences.get(EXTERNAL_DATABASE_PASSWORD, "")),
+ dbType);
+ }
+
+ /**
+ * Persists case database connection info.
+ *
+ * @param connectionInfo An object encapsulating the database connection
+ * info.
+ * @throws org.sleuthkit.autopsy.core.UserPreferencesException
+ */
+ public static void setDatabaseConnectionInfo(CaseDbConnectionInfo connectionInfo) throws UserPreferencesException {
+ preferences.put(EXTERNAL_DATABASE_HOSTNAME_OR_IP, connectionInfo.getHost());
+ preferences.put(EXTERNAL_DATABASE_PORTNUMBER, connectionInfo.getPort());
+ preferences.put(EXTERNAL_DATABASE_USER, connectionInfo.getUserName());
+ preferences.put(EXTERNAL_DATABASE_PASSWORD, TextConverter.convertTextToHexText(connectionInfo.getPassword()));
+ preferences.put(EXTERNAL_DATABASE_TYPE, connectionInfo.getDbType().toString());
+ }
+
+ public static void setIsMultiUserModeEnabled(boolean enabled) {
+ preferences.putBoolean(IS_MULTI_USER_MODE_ENABLED, enabled);
+ }
+
+ public static boolean getIsMultiUserModeEnabled() {
+ return preferences.getBoolean(IS_MULTI_USER_MODE_ENABLED, false);
+ }
+
+ public static String getIndexingServerHost() {
+ return preferences.get(INDEXING_SERVER_HOST, "");
+ }
+
+ public static void setIndexingServerHost(String hostName) {
+ preferences.put(INDEXING_SERVER_HOST, hostName);
+ }
+
+ public static String getIndexingServerPort() {
+ return preferences.get(INDEXING_SERVER_PORT, "8983");
+ }
+
+ public static void setIndexingServerPort(int port) {
+ preferences.putInt(INDEXING_SERVER_PORT, port);
+ }
+
+ /**
+ * Persists message service connection info.
+ *
+ * @param info An object encapsulating the message service info.
+ * @throws org.sleuthkit.autopsy.core.UserPreferencesException
+ */
+ public static void setMessageServiceConnectionInfo(MessageServiceConnectionInfo info) throws UserPreferencesException {
+ preferences.put(MESSAGE_SERVICE_HOST, info.getHost());
+ preferences.put(MESSAGE_SERVICE_PORT, Integer.toString(info.getPort()));
+ preferences.put(MESSAGE_SERVICE_USER, info.getUserName());
+ preferences.put(MESSAGE_SERVICE_PASSWORD, TextConverter.convertTextToHexText(info.getPassword()));
+ }
+
+ /**
+ * Reads persisted message service connection info.
+ *
+ * @return An object encapsulating the message service info.
+ * @throws org.sleuthkit.autopsy.core.UserPreferencesException
+ */
+ public static MessageServiceConnectionInfo getMessageServiceConnectionInfo() throws UserPreferencesException {
+ int port;
+ try {
+ port = Integer.parseInt(preferences.get(MESSAGE_SERVICE_PORT, DEFAULT_PORT_STRING));
+ } catch (NumberFormatException ex) {
+ // if there is an error parsing the port number, use the default port number
+ port = DEFAULT_PORT_INT;
+ }
+
+ return new MessageServiceConnectionInfo(
+ preferences.get(MESSAGE_SERVICE_HOST, ""),
+ port,
+ preferences.get(MESSAGE_SERVICE_USER, ""),
+ TextConverter.convertHexTextToText(preferences.get(MESSAGE_SERVICE_PASSWORD, "")));
+ }
+
+ /**
+ * Reads persisted process time out value.
+ *
+ * @return int Process time out value (hours).
+ */
+ public static int getProcessTimeOutHrs() {
+ int timeOut = preferences.getInt(PROCESS_TIME_OUT_HOURS, DEFAULT_PROCESS_TIMEOUT_HR);
+ if (timeOut < 0) {
+ timeOut = 0;
+ }
+ return timeOut;
+ }
+
+ /**
+ * Stores persisted process time out value.
+ *
+ * @param value Persisted process time out value (hours).
+ */
+ public static void setProcessTimeOutHrs(int value) {
+ if (value < 0) {
+ value = 0;
+ }
+ preferences.putInt(PROCESS_TIME_OUT_HOURS, value);
+ }
+
+ /**
+ * Reads persisted setting of whether process time out functionality is
+ * enabled.
+ *
+ * @return boolean True if process time out is functionality enabled, false
+ * otherwise.
+ */
+ public static boolean getIsTimeOutEnabled() {
+ boolean enabled = preferences.getBoolean(PROCESS_TIME_OUT_ENABLED, false);
+ return enabled;
+ }
+
+ /**
+ * Stores persisted setting of whether process time out functionality is
+ * enabled.
+ *
+ * @param enabled Persisted setting of whether process time out
+ * functionality is enabled.
+ */
+ public static void setIsTimeOutEnabled(boolean enabled) {
+ preferences.putBoolean(PROCESS_TIME_OUT_ENABLED, enabled);
+ }
+
+
+ /**
+ * Provides ability to convert text to hex text.
+ */
+ static final class TextConverter {
+
+ private static final char[] TMP = "hgleri21auty84fwe".toCharArray();
+ private static final byte[] SALT = {
+ (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
+ (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,};
+
+ /**
+ * Convert text to hex text.
+ *
+ * @param property Input text string.
+ *
+ * @return Converted hex string.
+ *
+ * @throws org.sleuthkit.autopsy.core.UserPreferencesException
+ */
+ static String convertTextToHexText(String property) throws UserPreferencesException {
+ try {
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
+ SecretKey key = keyFactory.generateSecret(new PBEKeySpec(TMP));
+ Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
+ pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
+ return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8")));
+ } catch (Exception ex) {
+ throw new UserPreferencesException(
+ NbBundle.getMessage(TextConverter.class, "TextConverter.convert.exception.txt"));
+ }
+ }
+
+ private static String base64Encode(byte[] bytes) {
+ return Base64.getEncoder().encodeToString(bytes);
+ }
+
+ /**
+ * Convert hex text back to text.
+ *
+ * @param property Input hex text string.
+ *
+ * @return Converted text string.
+ *
+ * @throws org.sleuthkit.autopsy.core.UserPreferencesException
+ */
+ static String convertHexTextToText(String property) throws UserPreferencesException {
+ try {
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
+ SecretKey key = keyFactory.generateSecret(new PBEKeySpec(TMP));
+ Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
+ pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
+ return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
+ } catch (Exception ex) {
+ throw new UserPreferencesException(
+ NbBundle.getMessage(TextConverter.class, "TextConverter.convertFromHex.exception.txt"));
+ }
+ }
+
+ private static byte[] base64Decode(String property) {
+ return Base64.getDecoder().decode(property);
+ }
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/events/ContentTagDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferencesException.java
similarity index 60%
rename from Core/src/org/sleuthkit/autopsy/events/ContentTagDeletedEvent.java
rename to Core/src/org/sleuthkit/autopsy/core/UserPreferencesException.java
index 6c6d11745f..7d9c22fe0b 100644
--- a/Core/src/org/sleuthkit/autopsy/events/ContentTagDeletedEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferencesException.java
@@ -16,19 +16,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.sleuthkit.autopsy.events;
-
-import javax.annotation.concurrent.Immutable;
-import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.datamodel.ContentTag;
+package org.sleuthkit.autopsy.core;
/**
- * An event that is fired when a ContentTag is deleted.
+ * Exception thrown when text conversion (such as from text to hex text or vice versa) resulted in
+ * an error
*/
-@Immutable
-public class ContentTagDeletedEvent extends TagDeletedEvent {
-
- public ContentTagDeletedEvent(ContentTag deletedTag) {
- super(Case.Events.CONTENT_TAG_DELETED.toString(), deletedTag);
+public class UserPreferencesException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public UserPreferencesException(String message) {
+ super(message);
}
+
+ public UserPreferencesException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/core/events/ServiceEvent.java b/Core/src/org/sleuthkit/autopsy/core/events/ServiceEvent.java
new file mode 100644
index 0000000000..6b09f80908
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/core/events/ServiceEvent.java
@@ -0,0 +1,49 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2013-2015 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.core.events;
+
+import java.io.Serializable;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+
+/**
+ * A class for events to be published to registered subscribers of Service
+ * Monitor on this Autopsy node. The class extends PropertyChangeEvent (via
+ * AutopsyEvent) to integrate with legacy use of JavaBeans PropertyChangeEvents
+ * and PropertyChangeListeners as an application event system, and implements
+ * Serializable to allow it to be published over a network in serialized form.
+ */
+public final class ServiceEvent extends AutopsyEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private final String details;
+
+ public ServiceEvent(String serviceName, String status, String details) {
+ super(serviceName, null, status);
+ this.details = details;
+ }
+
+ /**
+ * Gets details string passed as input to ServiceEvent constructor.
+ *
+ * @return String Details of the event.
+ */
+ public String getDetails() {
+ return details;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml
index 2f36280e7a..4c4b88125a 100644
--- a/Core/src/org/sleuthkit/autopsy/core/layer.xml
+++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml
@@ -19,7 +19,7 @@
-
+
@@ -44,7 +44,6 @@
-
@@ -203,6 +202,13 @@
+
+
+
+
+
+
+
@@ -215,6 +221,8 @@
-->
+
+
@@ -227,6 +235,7 @@
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowPanel.java
index abba7ce94b..640cc45409 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowPanel.java
@@ -26,7 +26,7 @@ import java.net.URL;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.logging.Level;
-import java.util.logging.Logger;
+import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
index 153a137748..14f2a4f112 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
@@ -6,8 +6,6 @@
-
-
@@ -58,6 +56,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -89,7 +101,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
@@ -205,5 +227,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
index e683de4c0d..9fb5b41089 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
@@ -18,7 +18,9 @@
*/
package org.sleuthkit.autopsy.corecomponents;
+import java.text.NumberFormat;
import javax.swing.DefaultComboBoxModel;
+import javax.swing.JFormattedTextField;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.UserPreferences;
@@ -81,6 +83,19 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
useLocalTimeRB.setSelected(useLocalTime);
useGMTTimeRB.setSelected(!useLocalTime);
numberOfFileIngestThreadsComboBox.setSelectedItem(UserPreferences.numberOfFileIngestThreads());
+ if (UserPreferences.getIsTimeOutEnabled()) {
+ // user specified time out
+ jCheckBoxEnableProcTimeout.setSelected(true);
+ jFormattedTextFieldProcTimeOutHrs.setEditable(true);
+ int timeOutHrs = UserPreferences.getProcessTimeOutHrs();
+ jFormattedTextFieldProcTimeOutHrs.setValue((long) timeOutHrs);
+ } else {
+ // never time out
+ jCheckBoxEnableProcTimeout.setSelected(false);
+ jFormattedTextFieldProcTimeOutHrs.setEditable(false);
+ int timeOutHrs = UserPreferences.getProcessTimeOutHrs();
+ jFormattedTextFieldProcTimeOutHrs.setValue((long) timeOutHrs);
+ }
}
void store() {
@@ -89,6 +104,13 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
UserPreferences.setHideKnownFilesInViewsTree(viewsHideKnownCB.isSelected());
UserPreferences.setDisplayTimesInLocalTime(useLocalTimeRB.isSelected());
UserPreferences.setNumberOfFileIngestThreads((Integer) numberOfFileIngestThreadsComboBox.getSelectedItem());
+
+ UserPreferences.setIsTimeOutEnabled(jCheckBoxEnableProcTimeout.isSelected());
+ if (jCheckBoxEnableProcTimeout.isSelected()) {
+ // only store time out if it is enabled
+ long timeOutHrs = (long) jFormattedTextFieldProcTimeOutHrs.getValue();
+ UserPreferences.setProcessTimeOutHrs((int) timeOutHrs);
+ }
}
boolean valid() {
@@ -105,7 +127,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
buttonGroup1 = new javax.swing.ButtonGroup();
buttonGroup3 = new javax.swing.ButtonGroup();
- buttonGroupProcTimeOut = new javax.swing.ButtonGroup();
useBestViewerRB = new javax.swing.JRadioButton();
keepCurrentViewerRB = new javax.swing.JRadioButton();
jLabelSelectFile = new javax.swing.JLabel();
@@ -118,6 +139,10 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
jLabelNumThreads = new javax.swing.JLabel();
numberOfFileIngestThreadsComboBox = new javax.swing.JComboBox();
restartRequiredLabel = new javax.swing.JLabel();
+ jLabelSetProcessTimeOut = new javax.swing.JLabel();
+ jCheckBoxEnableProcTimeout = new javax.swing.JCheckBox();
+ jLabelProcessTimeOutUnits = new javax.swing.JLabel();
+ jFormattedTextFieldProcTimeOutHrs = new JFormattedTextField(NumberFormat.getIntegerInstance());
buttonGroup1.add(useBestViewerRB);
useBestViewerRB.setSelected(true);
@@ -150,6 +175,19 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
restartRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(restartRequiredLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.restartRequiredLabel.text")); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(jLabelSetProcessTimeOut, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelSetProcessTimeOut.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(jCheckBoxEnableProcTimeout, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jCheckBoxEnableProcTimeout.text")); // NOI18N
+ jCheckBoxEnableProcTimeout.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ jCheckBoxEnableProcTimeoutActionPerformed(evt);
+ }
+ });
+
+ org.openide.awt.Mnemonics.setLocalizedText(jLabelProcessTimeOutUnits, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelProcessTimeOutUnits.text")); // NOI18N
+
+ jFormattedTextFieldProcTimeOutHrs.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jFormattedTextFieldProcTimeOutHrs.text")); // NOI18N
+
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@@ -180,7 +218,18 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addComponent(useGMTTimeRB)))
.addComponent(jLabelSelectFile)
.addComponent(jLabelNumThreads))
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(10, 10, 10)
+ .addComponent(jCheckBoxEnableProcTimeout)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jFormattedTextFieldProcTimeOutHrs, javax.swing.GroupLayout.PREFERRED_SIZE, 27, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jLabelProcessTimeOutUnits))
+ .addComponent(jLabelSetProcessTimeOut))
+ .addGap(0, 0, Short.MAX_VALUE))))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -208,18 +257,33 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(restartRequiredLabel))
- .addContainerGap(155, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(jLabelSetProcessTimeOut)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jCheckBoxEnableProcTimeout)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(jFormattedTextFieldProcTimeOutHrs, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(jLabelProcessTimeOutUnits)))
+ .addContainerGap(103, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
+ private void jCheckBoxEnableProcTimeoutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBoxEnableProcTimeoutActionPerformed
+ jFormattedTextFieldProcTimeOutHrs.setEditable(jCheckBoxEnableProcTimeout.isSelected());
+ }//GEN-LAST:event_jCheckBoxEnableProcTimeoutActionPerformed
+
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.ButtonGroup buttonGroup3;
- private javax.swing.ButtonGroup buttonGroupProcTimeOut;
private javax.swing.JCheckBox dataSourcesHideKnownCB;
+ private javax.swing.JCheckBox jCheckBoxEnableProcTimeout;
+ private javax.swing.JFormattedTextField jFormattedTextFieldProcTimeOutHrs;
private javax.swing.JLabel jLabelHideKnownFiles;
private javax.swing.JLabel jLabelNumThreads;
+ private javax.swing.JLabel jLabelProcessTimeOutUnits;
private javax.swing.JLabel jLabelSelectFile;
+ private javax.swing.JLabel jLabelSetProcessTimeOut;
private javax.swing.JLabel jLabelTimeDisplay;
private javax.swing.JRadioButton keepCurrentViewerRB;
private javax.swing.JComboBox numberOfFileIngestThreadsComboBox;
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java
index c0de3942ea..9253da5df5 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanelController.java
@@ -30,7 +30,7 @@ import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
@OptionsPanelController.TopLevelRegistration(categoryName = "#OptionsCategory_Name_General",
- iconBase = "org/sleuthkit/autopsy/corecomponents/checkbox.png",
+ iconBase = "org/sleuthkit/autopsy/corecomponents/checkbox32.png",
position = 1,
keywords = "#OptionsCategory_Keywords_General",
keywordsCategory = "General")
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index caa5ecd386..06146d2d8f 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -147,7 +147,52 @@ AutopsyOptionsPanel.jLabelNumThreads.text=Number of threads to use for file inge
FXVideoPanel.progress.bufferingCancelled=media buffering was canceled
FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted
FXVideoPanel.progress.errorWritingVideoToDisk=Error writing video to disk
+OptionsCategory_Name_Multi_User_Settings=Multi-user
+OptionsCategory_Keywords_Multi_User_Options=Multi-user Options
+MultiUserSettingsPanel.lbSolrSettings.text=Solr Settings
+MultiUserSettingsPanel.cbEnableMultiUser.text=Enable Multi-user cases
+MultiUserSettingsPanel.lbDatabaseSettings.text=Database Settings
+MultiUserSettingsPanel.validationErrMsg.incomplete=Fill in all values
+MultiUserSettingsPanel.validationErrMsg.invalidDatabasePort=Invalid database port number
+MultiUserSettingsPanel.validationErrMsg.invalidMessageServicePort=Invalid message service port number
+MultiUserSettingsPanel.validationErrMsg.invalidIndexingServerPort=Invalid Solr server port number
+MultiUserSettingsPanel.validationErrMsg.invalidMessgeServiceURI=Message service host and/or port not valid
+AutopsyOptionsPanel.jCheckBoxEnableProcTimeout.text=
+AutopsyOptionsPanel.jFormattedTextFieldProcTimeOutHrs.text=60
+AutopsyOptionsPanel.jLabelProcessTimeOutUnits.text=hour(s)
+AutopsyOptionsPanel.jLabelSetProcessTimeOut.text=Enable timeout to allow modules to automatically terminate after a set amount of time:
DataContentViewerHex.goToOffsetLabel.text=Jump to Offset
DataContentViewerHex.goToOffsetTextField.text=
DataContentViewerHex.goToOffsetTextField.msgDlg=Invalid Offset: {0}
-DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue=Cannot jump to the resultant offset
\ No newline at end of file
+DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue=Cannot jump to the resultant offset
+MultiUserSettingsPanel.tbOops.text=
+MultiUserSettingsPanel.lbTestDatabase.text=
+MultiUserSettingsPanel.bnTestDatabase.text=Test
+MultiUserSettingsPanel.tbDbHostname.toolTipText=Hostname or IP Address
+MultiUserSettingsPanel.tbDbHostname.text=
+MultiUserSettingsPanel.tbDbPort.toolTipText=Port Number
+MultiUserSettingsPanel.tbDbPort.text=
+MultiUserSettingsPanel.tbDbUsername.toolTipText=User Name
+MultiUserSettingsPanel.tbDbUsername.text=
+MultiUserSettingsPanel.tbDbPassword.toolTipText=Password
+MultiUserSettingsPanel.tbDbPassword.text=
+MultiUserSettingsPanel.lbTestSolr.text=
+MultiUserSettingsPanel.bnTestSolr.text=Test
+MultiUserSettingsPanel.tbSolrHostname.toolTipText=Hostname or IP Address
+MultiUserSettingsPanel.tbSolrPort.toolTipText=Port Number
+MultiUserSettingsPanel.lbTestMessageService.text=
+MultiUserSettingsPanel.bnTestMessageService.text=Test
+MultiUserSettingsPanel.lbMessageServiceSettings.text=ActiveMQ Message Service Settings
+MultiUserSettingsPanel.tbMsgPort.toolTipText=Port Number
+MultiUserSettingsPanel.tbMsgPort.text=
+MultiUserSettingsPanel.tbMsgUsername.toolTipText=User Name
+MultiUserSettingsPanel.tbMsgUsername.text=
+MultiUserSettingsPanel.tbMsgPassword.toolTipText=Password
+MultiUserSettingsPanel.tbMsgPassword.text=
+MultiUserSettingsPanel.tbMsgHostname.toolTipText=Hostname or IP Address
+MultiUserSettingsPanel.tbMsgHostname.text=
+MultiUserSettingsPanel.lbTestMessageWarning.text=
+MultiUserSettingsPanel.lbTestSolrWarning.text=
+MultiUserSettingsPanel.lbTestDbWarning.text=
+MultiUserSettingsPanel.KeywordSearchNull=Cannot find Keyword Search service
+MultiUserSettingsPanel.InvalidPortNumber=Invalid port number.
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java
index c3749660bf..9c1efe9b21 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java
@@ -171,7 +171,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
}
/**
- * is the given file a video we can display?
+ * Is the given file a video we can display?
*
* @param file
*
@@ -185,14 +185,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
}
/**
- * is the given file an image that we can display?
+ * Is the given file an image that we can display?
*
* @param file
*
* @return True if an image file that can be displayed
*/
private boolean isImageSupported(AbstractFile file) {
-
if (null == file || file.getSize() == 0) {
return false;
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form
index e86a2a5daf..f471d230a7 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form
@@ -45,11 +45,7 @@
-
-
-
-
-
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
index f1e790f0f8..4f3910357d 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
@@ -578,6 +578,5 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
}
}
-
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java
index 66a672c16b..c2e989635e 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -69,25 +69,26 @@ public class Installer extends ModuleInstall {
/*
* Open the passed in case, if an aut file was double clicked.
*/
- WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
- @Override
- public void run() {
- Collection extends OptionProcessor> processors = Lookup.getDefault().lookupAll(OptionProcessor.class);
- for (OptionProcessor processor : processors) {
- if (processor instanceof OpenFromArguments) {
- OpenFromArguments argsProcessor = (OpenFromArguments) processor;
- String caseFile = argsProcessor.getDefaultArg();
- if (caseFile != null && !caseFile.equals("") && caseFile.endsWith(".aut") && new File(caseFile).exists()) { //NON-NLS
+ WindowManager.getDefault().invokeWhenUIReady(() -> {
+ Collection extends OptionProcessor> processors = Lookup.getDefault().lookupAll(OptionProcessor.class);
+ for (OptionProcessor processor : processors) {
+ if (processor instanceof OpenFromArguments) {
+ OpenFromArguments argsProcessor = (OpenFromArguments) processor;
+ final String caseFile = argsProcessor.getDefaultArg();
+ if (caseFile != null && !caseFile.equals("") && caseFile.endsWith(".aut") && new File(caseFile).exists()) { //NON-NLS
+ new Thread(() -> {
+ // Create case.
try {
Case.open(caseFile);
- return;
- } catch (Exception e) {
+ } catch (Exception ex) {
+ logger.log(Level.SEVERE, "Error opening case: ", ex); //NON-NLS
}
- }
+ }).start();
+ return;
}
}
- Case.invokeStartupDialog(); // bring up the startup dialog
}
+ Case.invokeStartupDialog(); // bring up the startup dialog
});
}
@@ -95,18 +96,19 @@ public class Installer extends ModuleInstall {
@Override
public void uninstalled() {
super.uninstalled();
-
}
@Override
public void close() {
- try {
- if (Case.isCaseOpen()) {
- Case.getCurrentCase().closeCase();
+ new Thread(() -> {
+ try {
+ if (Case.isCaseOpen()) {
+ Case.getCurrentCase().closeCase();
+ }
+ } catch (CaseActionException | IllegalStateException unused) {
+ // Exception already logged. Shutting down, no need to do popup.
}
- } catch (CaseActionException ex) {
- logger.log(Level.WARNING, "Error closing case. ", ex); //NON-NLS
- }
+ }).start();
}
private void setupLAF() {
@@ -157,8 +159,8 @@ public class Installer extends ModuleInstall {
}
// Overwrite the Metal menu item keys to use the Aqua versions
- for (Map.Entry